From 95caef86926306c5e8d3b893addaf704d13a010d Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 5 May 2016 23:29:11 +0200 Subject: [PATCH 001/328] start to implement SVM with libsvm --- src/Phpml/Classification/SVC.php | 53 ++++++++++++ .../Classification/SupportVectorMachine.php | 78 ----------------- src/Phpml/Dataset/Dataset.php | 1 - .../SupportVectorMachine/DataTransformer.php | 59 +++++++++++++ src/Phpml/SupportVectorMachine/Kernel.php | 28 +++++++ .../SupportVectorMachine.php | 83 +++++++++++++++++++ src/Phpml/SupportVectorMachine/Type.php | 33 ++++++++ .../DataTransformerTest.php | 25 ++++++ .../SupportVectorMachineTest.php | 36 ++++++++ var/.gitkeep | 0 10 files changed, 317 insertions(+), 79 deletions(-) create mode 100644 src/Phpml/Classification/SVC.php delete mode 100644 src/Phpml/Classification/SupportVectorMachine.php create mode 100644 src/Phpml/SupportVectorMachine/DataTransformer.php create mode 100644 src/Phpml/SupportVectorMachine/Kernel.php create mode 100644 src/Phpml/SupportVectorMachine/SupportVectorMachine.php create mode 100644 src/Phpml/SupportVectorMachine/Type.php create mode 100644 tests/Phpml/SupportVectorMachine/DataTransformerTest.php create mode 100644 tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php create mode 100644 var/.gitkeep diff --git a/src/Phpml/Classification/SVC.php b/src/Phpml/Classification/SVC.php new file mode 100644 index 00000000..52795399 --- /dev/null +++ b/src/Phpml/Classification/SVC.php @@ -0,0 +1,53 @@ +kernel = $kernel; + $this->cost = $cost; + } + + /** + * @param array $samples + * @param array $labels + */ + public function train(array $samples, array $labels) + { + $this->samples = $samples; + $this->labels = $labels; + } + + /** + * @param array $sample + * + * @return mixed + */ + protected function predictSample(array $sample) + { + } +} diff --git a/src/Phpml/Classification/SupportVectorMachine.php b/src/Phpml/Classification/SupportVectorMachine.php deleted file mode 100644 index 5eb84e64..00000000 --- a/src/Phpml/Classification/SupportVectorMachine.php +++ /dev/null @@ -1,78 +0,0 @@ -kernel = $kernel; - $this->C = $C; - $this->tolerance = $tolerance; - $this->upperBound = $upperBound; - - $this->binPath = realpath(implode(DIRECTORY_SEPARATOR, array(dirname(__FILE__), '..', '..', '..', 'bin'))) . DIRECTORY_SEPARATOR; - } - - /** - * @param array $samples - * @param array $labels - */ - public function train(array $samples, array $labels) - { - $this->samples = $samples; - $this->labels = $labels; - } - - /** - * @param array $sample - * - * @return mixed - */ - protected function predictSample(array $sample) - { - } -} diff --git a/src/Phpml/Dataset/Dataset.php b/src/Phpml/Dataset/Dataset.php index 2bc4043c..4e049316 100644 --- a/src/Phpml/Dataset/Dataset.php +++ b/src/Phpml/Dataset/Dataset.php @@ -6,7 +6,6 @@ interface Dataset { - const SOME = 'z'; /** * @return array */ diff --git a/src/Phpml/SupportVectorMachine/DataTransformer.php b/src/Phpml/SupportVectorMachine/DataTransformer.php new file mode 100644 index 00000000..4e01fc8c --- /dev/null +++ b/src/Phpml/SupportVectorMachine/DataTransformer.php @@ -0,0 +1,59 @@ + $label) { + $set .= sprintf('%s %s %s', $numericLabels[$label], self::sampleRow($samples[$index]), PHP_EOL); + } + + return $set; + } + + /** + * @param array $labels + * + * @return array + */ + public static function numericLabels(array $labels): array + { + $numericLabels = []; + foreach ($labels as $label) { + if (isset($numericLabels[$label])) { + continue; + } + + $numericLabels[$label] = count($numericLabels); + } + + return $numericLabels; + } + + /** + * @param array $sample + * + * @return string + */ + private static function sampleRow(array $sample): string + { + $row = []; + foreach ($sample as $index => $feature) { + $row[] = sprintf('%s:%s', $index, $feature); + } + + return implode(' ', $row); + } +} diff --git a/src/Phpml/SupportVectorMachine/Kernel.php b/src/Phpml/SupportVectorMachine/Kernel.php new file mode 100644 index 00000000..4dddef61 --- /dev/null +++ b/src/Phpml/SupportVectorMachine/Kernel.php @@ -0,0 +1,28 @@ +type = $type; + $this->kernel = $kernel; + $this->cost = $cost; + + $rootPath = realpath(implode(DIRECTORY_SEPARATOR, [dirname(__FILE__), '..', '..', '..'])).DIRECTORY_SEPARATOR; + + $this->binPath = $rootPath.'bin'.DIRECTORY_SEPARATOR.'libsvm'.DIRECTORY_SEPARATOR; + $this->varPath = $rootPath.'var'.DIRECTORY_SEPARATOR; + } + + /** + * @param array $samples + * @param array $labels + */ + public function train(array $samples, array $labels) + { + $trainingSet = DataTransformer::trainingSet($samples, $labels); + file_put_contents($trainingSetFileName = $this->varPath.uniqid(), $trainingSet); + $modelFileName = $trainingSetFileName.'-model'; + + $command = sprintf('%ssvm-train -s %s -t %s -c %s %s %s', $this->binPath, $this->type, $this->kernel, $this->cost, $trainingSetFileName, $modelFileName); + $output = ''; + exec(escapeshellcmd($command), $output); + + $this->model = file_get_contents($modelFileName); + + unlink($trainingSetFileName); + unlink($modelFileName); + } + + /** + * @return string + */ + public function getModel() + { + return $this->model; + } +} diff --git a/src/Phpml/SupportVectorMachine/Type.php b/src/Phpml/SupportVectorMachine/Type.php new file mode 100644 index 00000000..49b03a0d --- /dev/null +++ b/src/Phpml/SupportVectorMachine/Type.php @@ -0,0 +1,33 @@ +assertEquals($trainingSet, DataTransformer::trainingSet($samples, $labels)); + } +} diff --git a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php new file mode 100644 index 00000000..e06f7156 --- /dev/null +++ b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php @@ -0,0 +1,36 @@ +train($samples, $labels); + + $this->assertEquals($model, $svm->getModel()); + } +} diff --git a/var/.gitkeep b/var/.gitkeep new file mode 100644 index 00000000..e69de29b From 4ac2ac8a353e45c91125cec58ff5c9cab0e0adbb Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 6 May 2016 22:33:04 +0200 Subject: [PATCH 002/328] fix index for trainging set --- src/Phpml/SupportVectorMachine/DataTransformer.php | 2 +- src/Phpml/SupportVectorMachine/SupportVectorMachine.php | 6 +++++- tests/Phpml/SupportVectorMachine/DataTransformerTest.php | 8 ++++---- .../SupportVectorMachine/SupportVectorMachineTest.php | 4 ++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Phpml/SupportVectorMachine/DataTransformer.php b/src/Phpml/SupportVectorMachine/DataTransformer.php index 4e01fc8c..5b418e2c 100644 --- a/src/Phpml/SupportVectorMachine/DataTransformer.php +++ b/src/Phpml/SupportVectorMachine/DataTransformer.php @@ -51,7 +51,7 @@ private static function sampleRow(array $sample): string { $row = []; foreach ($sample as $index => $feature) { - $row[] = sprintf('%s:%s', $index, $feature); + $row[] = sprintf('%s:%s', $index + 1, $feature); } return implode(' ', $row); diff --git a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php index 6e2be209..68427776 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -27,7 +27,7 @@ class SupportVectorMachine private $binPath; /** - * @var + * @var string */ private $varPath; @@ -80,4 +80,8 @@ public function getModel() { return $this->model; } + + public function predict(array $samples) + { + } } diff --git a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php b/tests/Phpml/SupportVectorMachine/DataTransformerTest.php index 84fd0015..1429511c 100644 --- a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php +++ b/tests/Phpml/SupportVectorMachine/DataTransformerTest.php @@ -14,10 +14,10 @@ public function testTransformDatasetToTrainingSet() $labels = ['a', 'a', 'b', 'b']; $trainingSet = - '0 0:1 1:1 '.PHP_EOL. - '0 0:2 1:1 '.PHP_EOL. - '1 0:3 1:2 '.PHP_EOL. - '1 0:4 1:5 '.PHP_EOL + '0 1:1 2:1 '.PHP_EOL. + '0 1:2 2:1 '.PHP_EOL. + '1 1:3 2:2 '.PHP_EOL. + '1 1:4 2:5 '.PHP_EOL ; $this->assertEquals($trainingSet, DataTransformer::trainingSet($samples, $labels)); diff --git a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php index e06f7156..e4a88576 100644 --- a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php @@ -24,8 +24,8 @@ public function testTrainCSVCModelWithLinearKernel() label 0 1 nr_sv 1 1 SV -0.25 0:2 1:4 --0.25 0:4 1:2 +0.25 1:2 2:4 +-0.25 1:4 2:2 '; $svm = new SupportVectorMachine(Type::C_SVC, Kernel::LINEAR, 100.0); From dfb7b6b108deb457bda393b007769202d09b6a54 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 6 May 2016 22:38:50 +0200 Subject: [PATCH 003/328] datatransformer test set --- .../SupportVectorMachine/DataTransformer.php | 15 +++++++++++++++ .../SupportVectorMachine/SupportVectorMachine.php | 1 + .../SupportVectorMachine/DataTransformerTest.php | 15 +++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/src/Phpml/SupportVectorMachine/DataTransformer.php b/src/Phpml/SupportVectorMachine/DataTransformer.php index 5b418e2c..05599f1a 100644 --- a/src/Phpml/SupportVectorMachine/DataTransformer.php +++ b/src/Phpml/SupportVectorMachine/DataTransformer.php @@ -23,6 +23,21 @@ public static function trainingSet(array $samples, array $labels): string return $set; } + /** + * @param array $samples + * + * @return string + */ + public static function testSet(array $samples): string + { + $set = ''; + foreach ($samples as $sample) { + $set .= sprintf('0 %s %s', self::sampleRow($sample), PHP_EOL); + } + + return $set; + } + /** * @param array $labels * diff --git a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php index 68427776..325fcf36 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -83,5 +83,6 @@ public function getModel() public function predict(array $samples) { + $testSet = DataTransformer::testSet(); } } diff --git a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php b/tests/Phpml/SupportVectorMachine/DataTransformerTest.php index 1429511c..ff2a7c76 100644 --- a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php +++ b/tests/Phpml/SupportVectorMachine/DataTransformerTest.php @@ -22,4 +22,19 @@ public function testTransformDatasetToTrainingSet() $this->assertEquals($trainingSet, DataTransformer::trainingSet($samples, $labels)); } + + public function testTransformSamplesToTestSet() + { + $samples = [[1, 1], [2, 1], [3, 2], [4, 5]]; + + $testSet = + '0 1:1 2:1 '.PHP_EOL. + '0 1:2 2:1 '.PHP_EOL. + '0 1:3 2:2 '.PHP_EOL. + '0 1:4 2:5 '.PHP_EOL + ; + + $this->assertEquals($testSet, DataTransformer::testSet($samples)); + } + } From 7b5b6418f42f743aa747629fc95487877853312b Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 6 May 2016 22:55:41 +0200 Subject: [PATCH 004/328] libsvm predict program implementation --- .../SupportVectorMachine/DataTransformer.php | 17 +++++++++++ .../SupportVectorMachine.php | 29 ++++++++++++++++++- .../DataTransformerTest.php | 1 - .../SupportVectorMachineTest.php | 19 ++++++++++++ 4 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/Phpml/SupportVectorMachine/DataTransformer.php b/src/Phpml/SupportVectorMachine/DataTransformer.php index 05599f1a..1ce4bee5 100644 --- a/src/Phpml/SupportVectorMachine/DataTransformer.php +++ b/src/Phpml/SupportVectorMachine/DataTransformer.php @@ -38,6 +38,23 @@ public static function testSet(array $samples): string return $set; } + /** + * @param string $resultString + * @param array $labels + * + * @return array + */ + public static function results(string $resultString, array $labels): array + { + $numericLabels = self::numericLabels($labels); + $results = []; + foreach (explode(PHP_EOL, $resultString) as $result) { + $results[] = array_search($result, $numericLabels); + } + + return $results; + } + /** * @param array $labels * diff --git a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php index 325fcf36..f14d534e 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -36,6 +36,11 @@ class SupportVectorMachine */ private $model; + /** + * @var array + */ + private $labels; + /** * @param int $type * @param int $kernel @@ -59,6 +64,7 @@ public function __construct(int $type, int $kernel, float $cost) */ public function train(array $samples, array $labels) { + $this->labels = $labels; $trainingSet = DataTransformer::trainingSet($samples, $labels); file_put_contents($trainingSetFileName = $this->varPath.uniqid(), $trainingSet); $modelFileName = $trainingSetFileName.'-model'; @@ -81,8 +87,29 @@ public function getModel() return $this->model; } + /** + * @param array $samples + * + * @return array + */ public function predict(array $samples) { - $testSet = DataTransformer::testSet(); + $testSet = DataTransformer::testSet($samples); + file_put_contents($testSetFileName = $this->varPath.uniqid(), $testSet); + $modelFileName = $testSetFileName.'-model'; + file_put_contents($modelFileName, $this->model); + $outputFileName = $testSetFileName.'-output'; + + $command = sprintf('%ssvm-predict %s %s %s', $this->binPath, $testSetFileName, $modelFileName, $outputFileName); + $output = ''; + exec(escapeshellcmd($command), $output); + + $predictions = file_get_contents($outputFileName); + + unlink($testSetFileName); + unlink($modelFileName); + unlink($outputFileName); + + return DataTransformer::results($predictions, $this->labels); } } diff --git a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php b/tests/Phpml/SupportVectorMachine/DataTransformerTest.php index ff2a7c76..c07948ae 100644 --- a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php +++ b/tests/Phpml/SupportVectorMachine/DataTransformerTest.php @@ -36,5 +36,4 @@ public function testTransformSamplesToTestSet() $this->assertEquals($testSet, DataTransformer::testSet($samples)); } - } diff --git a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php index e4a88576..330f7f08 100644 --- a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php @@ -33,4 +33,23 @@ public function testTrainCSVCModelWithLinearKernel() $this->assertEquals($model, $svm->getModel()); } + + public function testPredictCSVCModelWithLinearKernel() + { + $samples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; + $labels = ['a', 'a', 'a', 'b', 'b', 'b']; + + $svm = new SupportVectorMachine(Type::C_SVC, Kernel::LINEAR, 100.0); + $svm->train($samples, $labels); + + $predictions = $svm->predict([ + [3, 2], + [2, 3], + [4, -5], + ]); + + $this->assertEquals('b', $predictions[0]); + $this->assertEquals('a', $predictions[1]); + $this->assertEquals('b', $predictions[2]); + } } From 95bfc890cd0ab0fdada44057a50dfbbf500fed40 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sat, 7 May 2016 11:22:37 +0200 Subject: [PATCH 005/328] add windows libsvm binaries --- bin/libsvm/svm-predict.exe | Bin 0 -> 107008 bytes bin/libsvm/svm-scale.exe | Bin 0 -> 80384 bytes bin/libsvm/svm-train.exe | Bin 0 -> 135680 bytes .../SupportVectorMachine.php | 17 +++++++++++++++-- 4 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 bin/libsvm/svm-predict.exe create mode 100644 bin/libsvm/svm-scale.exe create mode 100644 bin/libsvm/svm-train.exe diff --git a/bin/libsvm/svm-predict.exe b/bin/libsvm/svm-predict.exe new file mode 100644 index 0000000000000000000000000000000000000000..29760b09f7ed30d894cafbd4d91b55db1f639a24 GIT binary patch literal 107008 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

qUDKC70K*9g;xaS5W+mC9KSdNSyN@8)Z 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 literal 0 HcmV?d00001 diff --git a/bin/libsvm/svm-scale.exe b/bin/libsvm/svm-scale.exe new file mode 100644 index 0000000000000000000000000000000000000000..75489ae97d24eecfef35b4f88d3472c4dccb1c81 GIT binary patch literal 80384 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 K5`q5_0{;NVwOd30 literal 0 HcmV?d00001 diff --git a/bin/libsvm/svm-train.exe b/bin/libsvm/svm-train.exe new file mode 100644 index 0000000000000000000000000000000000000000..23368b5b23b6b98a7efaeafba85525bf06b47150 GIT binary patch literal 135680 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 literal 0 HcmV?d00001 diff --git a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php index f14d534e..a8120656 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -69,7 +69,7 @@ public function train(array $samples, array $labels) file_put_contents($trainingSetFileName = $this->varPath.uniqid(), $trainingSet); $modelFileName = $trainingSetFileName.'-model'; - $command = sprintf('%ssvm-train -s %s -t %s -c %s %s %s', $this->binPath, $this->type, $this->kernel, $this->cost, $trainingSetFileName, $modelFileName); + $command = sprintf('%ssvm-train%s -s %s -t %s -c %s %s %s', $this->binPath, $this->getOSExtension(), $this->type, $this->kernel, $this->cost, $trainingSetFileName, $modelFileName); $output = ''; exec(escapeshellcmd($command), $output); @@ -100,7 +100,7 @@ public function predict(array $samples) file_put_contents($modelFileName, $this->model); $outputFileName = $testSetFileName.'-output'; - $command = sprintf('%ssvm-predict %s %s %s', $this->binPath, $testSetFileName, $modelFileName, $outputFileName); + $command = sprintf('%ssvm-predict%s %s %s %s', $this->binPath, $this->getOSExtension(), $testSetFileName, $modelFileName, $outputFileName); $output = ''; exec(escapeshellcmd($command), $output); @@ -112,4 +112,17 @@ public function predict(array $samples) return DataTransformer::results($predictions, $this->labels); } + + /** + * @return string + */ + private function getOSExtension() + { + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + return '.exe'; + } + + return ''; + } + } From 6cf6c5e768d0ce5e3922263f09045efc9965d559 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sat, 7 May 2016 14:08:09 +0200 Subject: [PATCH 006/328] add multi class svm test --- .../SupportVectorMachine.php | 1 - .../SupportVectorMachineTest.php | 29 ++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php index a8120656..e9bd8c8a 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -124,5 +124,4 @@ private function getOSExtension() return ''; } - } diff --git a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php index 330f7f08..d14f7774 100644 --- a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php @@ -34,7 +34,7 @@ public function testTrainCSVCModelWithLinearKernel() $this->assertEquals($model, $svm->getModel()); } - public function testPredictCSVCModelWithLinearKernel() + public function testPredictSampleWithLinearKernel() { $samples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; $labels = ['a', 'a', 'a', 'b', 'b', 'b']; @@ -52,4 +52,31 @@ public function testPredictCSVCModelWithLinearKernel() $this->assertEquals('a', $predictions[1]); $this->assertEquals('b', $predictions[2]); } + + public function testPredictSampleFromMultipleClassWithRbfKernel() + { + $samples = [ + [1, 3], [1, 4], [1, 4], + [3, 1], [4, 1], [4, 2], + [-3, -1], [-4, -1], [-4, -2], + ]; + $labels = [ + 'a', 'a', 'a', + 'b', 'b', 'b', + 'c', 'c', 'c', + ]; + + $svm = new SupportVectorMachine(Type::C_SVC, Kernel::RBF, 100.0); + $svm->train($samples, $labels); + + $predictions = $svm->predict([ + [1, 5], + [4, 3], + [-4, -3], + ]); + + $this->assertEquals('a', $predictions[0]); + $this->assertEquals('b', $predictions[1]); + $this->assertEquals('c', $predictions[2]); + } } From c40965848308c19638ecde78a6246a5016f5c23f Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sat, 7 May 2016 22:17:12 +0200 Subject: [PATCH 007/328] support vector classifier implementation --- src/Phpml/Classification/SVC.php | 59 +++------ .../SupportVectorMachine/DataTransformer.php | 14 ++- .../SupportVectorMachine.php | 119 ++++++++++++++++-- tests/Phpml/Classification/SVCTest.php | 45 +++++++ 4 files changed, 182 insertions(+), 55 deletions(-) create mode 100644 tests/Phpml/Classification/SVCTest.php diff --git a/src/Phpml/Classification/SVC.php b/src/Phpml/Classification/SVC.php index 52795399..8dcb28f7 100644 --- a/src/Phpml/Classification/SVC.php +++ b/src/Phpml/Classification/SVC.php @@ -4,50 +4,27 @@ namespace Phpml\Classification; -use Phpml\Classification\Traits\Predictable; -use Phpml\Classification\Traits\Trainable; -use Phpml\Math\Kernel; +use Phpml\SupportVectorMachine\SupportVectorMachine; +use Phpml\SupportVectorMachine\Type; -class SVC implements Classifier +class SVC extends SupportVectorMachine implements Classifier { - use Trainable, Predictable; - - /** - * @var int - */ - private $kernel; - - /** - * @var float - */ - private $cost; - - /** - * @param int $kernel - * @param float $cost - */ - public function __construct(int $kernel, float $cost) - { - $this->kernel = $kernel; - $this->cost = $cost; - } - - /** - * @param array $samples - * @param array $labels - */ - public function train(array $samples, array $labels) - { - $this->samples = $samples; - $this->labels = $labels; - } - /** - * @param array $sample - * - * @return mixed + * @param int $kernel + * @param float $cost + * @param int $degree + * @param float|null $gamma + * @param float $coef0 + * @param float $tolerance + * @param int $cacheSize + * @param bool $shrinking + * @param bool $probabilityEstimates */ - protected function predictSample(array $sample) - { + public function __construct( + int $kernel, float $cost = 1.0, int $degree = 3, float $gamma = null, float $coef0 = 0.0, + float $tolerance = 0.001, int $cacheSize = 100, bool $shrinking = true, + bool $probabilityEstimates = false + ) { + parent::__construct(Type::C_SVC, $kernel, $cost, 0.5, $degree, $gamma, $coef0, 0.1, $tolerance, $cacheSize, $shrinking, $probabilityEstimates); } } diff --git a/src/Phpml/SupportVectorMachine/DataTransformer.php b/src/Phpml/SupportVectorMachine/DataTransformer.php index 1ce4bee5..155f564e 100644 --- a/src/Phpml/SupportVectorMachine/DataTransformer.php +++ b/src/Phpml/SupportVectorMachine/DataTransformer.php @@ -30,6 +30,10 @@ public static function trainingSet(array $samples, array $labels): string */ public static function testSet(array $samples): string { + if (!is_array($samples[0])) { + $samples = [$samples]; + } + $set = ''; foreach ($samples as $sample) { $set .= sprintf('0 %s %s', self::sampleRow($sample), PHP_EOL); @@ -39,17 +43,19 @@ public static function testSet(array $samples): string } /** - * @param string $resultString + * @param string $rawPredictions * @param array $labels * * @return array */ - public static function results(string $resultString, array $labels): array + public static function predictions(string $rawPredictions, array $labels): array { $numericLabels = self::numericLabels($labels); $results = []; - foreach (explode(PHP_EOL, $resultString) as $result) { - $results[] = array_search($result, $numericLabels); + foreach (explode(PHP_EOL, $rawPredictions) as $result) { + if (strlen($result) > 0) { + $results[] = array_search($result, $numericLabels); + } } return $results; diff --git a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php index e9bd8c8a..7a47db77 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -21,6 +21,51 @@ class SupportVectorMachine */ private $cost; + /** + * @var float + */ + private $nu; + + /** + * @var int + */ + private $degree; + + /** + * @var float + */ + private $gamma; + + /** + * @var float + */ + private $coef0; + + /** + * @var float + */ + private $epsilon; + + /** + * @var float + */ + private $tolerance; + + /** + * @var int + */ + private $cacheSize; + + /** + * @var bool + */ + private $shrinking; + + /** + * @var bool + */ + private $probabilityEstimates; + /** * @var string */ @@ -42,15 +87,36 @@ class SupportVectorMachine private $labels; /** - * @param int $type - * @param int $kernel - * @param float $cost + * @param int $type + * @param int $kernel + * @param float $cost + * @param float $nu + * @param int $degree + * @param float|null $gamma + * @param float $coef0 + * @param float $epsilon + * @param float $tolerance + * @param int $cacheSize + * @param bool $shrinking + * @param bool $probabilityEstimates */ - public function __construct(int $type, int $kernel, float $cost) - { + public function __construct( + int $type, int $kernel, float $cost = 1.0, float $nu = 0.5, int $degree = 3, + float $gamma = null, float $coef0 = 0.0, float $epsilon = 0.1, float $tolerance = 0.001, + int $cacheSize = 100, bool $shrinking = true, bool $probabilityEstimates = false + ) { $this->type = $type; $this->kernel = $kernel; $this->cost = $cost; + $this->nu = $nu; + $this->degree = $degree; + $this->gamma = $gamma; + $this->coef0 = $coef0; + $this->epsilon = $epsilon; + $this->tolerance = $tolerance; + $this->cacheSize = $cacheSize; + $this->shrinking = $shrinking; + $this->probabilityEstimates = $probabilityEstimates; $rootPath = realpath(implode(DIRECTORY_SEPARATOR, [dirname(__FILE__), '..', '..', '..'])).DIRECTORY_SEPARATOR; @@ -69,7 +135,7 @@ public function train(array $samples, array $labels) file_put_contents($trainingSetFileName = $this->varPath.uniqid(), $trainingSet); $modelFileName = $trainingSetFileName.'-model'; - $command = sprintf('%ssvm-train%s -s %s -t %s -c %s %s %s', $this->binPath, $this->getOSExtension(), $this->type, $this->kernel, $this->cost, $trainingSetFileName, $modelFileName); + $command = $this->buildTrainCommand($trainingSetFileName, $modelFileName); $output = ''; exec(escapeshellcmd($command), $output); @@ -96,21 +162,26 @@ public function predict(array $samples) { $testSet = DataTransformer::testSet($samples); file_put_contents($testSetFileName = $this->varPath.uniqid(), $testSet); - $modelFileName = $testSetFileName.'-model'; - file_put_contents($modelFileName, $this->model); + file_put_contents($modelFileName = $testSetFileName.'-model', $this->model); $outputFileName = $testSetFileName.'-output'; $command = sprintf('%ssvm-predict%s %s %s %s', $this->binPath, $this->getOSExtension(), $testSetFileName, $modelFileName, $outputFileName); $output = ''; exec(escapeshellcmd($command), $output); - $predictions = file_get_contents($outputFileName); + $rawPredictions = file_get_contents($outputFileName); unlink($testSetFileName); unlink($modelFileName); unlink($outputFileName); - return DataTransformer::results($predictions, $this->labels); + $predictions = DataTransformer::predictions($rawPredictions, $this->labels); + + if (!is_array($samples[0])) { + return $predictions[0]; + } + + return $predictions; } /** @@ -124,4 +195,32 @@ private function getOSExtension() return ''; } + + /** + * @param $trainingSetFileName + * @param $modelFileName + * + * @return string + */ + private function buildTrainCommand(string $trainingSetFileName, string $modelFileName): string + { + return sprintf('%ssvm-train%s -s %s -t %s -c %s -n %s -d %s%s -r %s -p %s -m %s -e %s -h %d -b %d \'%s\' \'%s\'', + $this->binPath, + $this->getOSExtension(), + $this->type, + $this->kernel, + $this->cost, + $this->nu, + $this->degree, + $this->gamma !== null ? ' -g '.$this->gamma : '', + $this->coef0, + $this->epsilon, + $this->cacheSize, + $this->tolerance, + $this->shrinking, + $this->probabilityEstimates, + $trainingSetFileName, + $modelFileName + ); + } } diff --git a/tests/Phpml/Classification/SVCTest.php b/tests/Phpml/Classification/SVCTest.php new file mode 100644 index 00000000..1ddcdd09 --- /dev/null +++ b/tests/Phpml/Classification/SVCTest.php @@ -0,0 +1,45 @@ +train($samples, $labels); + + $this->assertEquals('b', $classifier->predict([3, 2])); + $this->assertEquals('b', $classifier->predict([5, 1])); + $this->assertEquals('b', $classifier->predict([4, 3])); + $this->assertEquals('b', $classifier->predict([4, -5])); + + $this->assertEquals('a', $classifier->predict([2, 3])); + $this->assertEquals('a', $classifier->predict([1, 2])); + $this->assertEquals('a', $classifier->predict([1, 5])); + $this->assertEquals('a', $classifier->predict([3, 10])); + } + + public function testPredictArrayOfSamplesWithLinearKernel() + { + $trainSamples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; + $trainLabels = ['a', 'a', 'a', 'b', 'b', 'b']; + + $testSamples = [[3, 2], [5, 1], [4, 3], [4, -5], [2, 3], [1, 2], [1, 5], [3, 10]]; + $testLabels = ['b', 'b', 'b', 'b', 'a', 'a', 'a', 'a']; + + $classifier = new SVC(Kernel::LINEAR, $cost = 1000); + $classifier->train($trainSamples, $trainLabels); + $predictions = $classifier->predict($testSamples); + + $this->assertEquals($testLabels, $predictions); + } +} From 430c1078cfbbdfe2343d1d057dbdb1b2beb7d5b5 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sat, 7 May 2016 23:04:58 +0200 Subject: [PATCH 008/328] implement support vector regression --- .../Classification/KNearestNeighbors.php | 4 +- src/Phpml/Classification/NaiveBayes.php | 4 +- src/Phpml/Classification/SVC.php | 3 +- .../Traits => Helper}/Predictable.php | 2 +- .../Traits => Helper}/Trainable.php | 2 +- src/Phpml/Regression/LeastSquares.php | 4 +- src/Phpml/Regression/Regression.php | 4 +- src/Phpml/Regression/SVR.php | 31 ++++++++++++ .../SupportVectorMachine/DataTransformer.php | 10 ++-- .../SupportVectorMachine.php | 10 ++-- tests/Phpml/Regression/SVRTest.php | 50 +++++++++++++++++++ 11 files changed, 108 insertions(+), 16 deletions(-) rename src/Phpml/{Classification/Traits => Helper}/Predictable.php (94%) rename src/Phpml/{Classification/Traits => Helper}/Trainable.php (90%) create mode 100644 src/Phpml/Regression/SVR.php create mode 100644 tests/Phpml/Regression/SVRTest.php diff --git a/src/Phpml/Classification/KNearestNeighbors.php b/src/Phpml/Classification/KNearestNeighbors.php index 93991aea..f1a87cf1 100644 --- a/src/Phpml/Classification/KNearestNeighbors.php +++ b/src/Phpml/Classification/KNearestNeighbors.php @@ -4,8 +4,8 @@ namespace Phpml\Classification; -use Phpml\Classification\Traits\Predictable; -use Phpml\Classification\Traits\Trainable; +use Phpml\Helper\Predictable; +use Phpml\Helper\Trainable; use Phpml\Math\Distance; use Phpml\Math\Distance\Euclidean; diff --git a/src/Phpml/Classification/NaiveBayes.php b/src/Phpml/Classification/NaiveBayes.php index ae98e1db..9726b405 100644 --- a/src/Phpml/Classification/NaiveBayes.php +++ b/src/Phpml/Classification/NaiveBayes.php @@ -4,8 +4,8 @@ namespace Phpml\Classification; -use Phpml\Classification\Traits\Predictable; -use Phpml\Classification\Traits\Trainable; +use Phpml\Helper\Predictable; +use Phpml\Helper\Trainable; class NaiveBayes implements Classifier { diff --git a/src/Phpml/Classification/SVC.php b/src/Phpml/Classification/SVC.php index 8dcb28f7..2350d5d8 100644 --- a/src/Phpml/Classification/SVC.php +++ b/src/Phpml/Classification/SVC.php @@ -4,6 +4,7 @@ namespace Phpml\Classification; +use Phpml\SupportVectorMachine\Kernel; use Phpml\SupportVectorMachine\SupportVectorMachine; use Phpml\SupportVectorMachine\Type; @@ -21,7 +22,7 @@ class SVC extends SupportVectorMachine implements Classifier * @param bool $probabilityEstimates */ public function __construct( - int $kernel, float $cost = 1.0, int $degree = 3, float $gamma = null, float $coef0 = 0.0, + int $kernel = Kernel::LINEAR, float $cost = 1.0, int $degree = 3, float $gamma = null, float $coef0 = 0.0, float $tolerance = 0.001, int $cacheSize = 100, bool $shrinking = true, bool $probabilityEstimates = false ) { diff --git a/src/Phpml/Classification/Traits/Predictable.php b/src/Phpml/Helper/Predictable.php similarity index 94% rename from src/Phpml/Classification/Traits/Predictable.php rename to src/Phpml/Helper/Predictable.php index 804b54a0..4bf2a2e4 100644 --- a/src/Phpml/Classification/Traits/Predictable.php +++ b/src/Phpml/Helper/Predictable.php @@ -2,7 +2,7 @@ declare (strict_types = 1); -namespace Phpml\Classification\Traits; +namespace Phpml\Helper; trait Predictable { diff --git a/src/Phpml/Classification/Traits/Trainable.php b/src/Phpml/Helper/Trainable.php similarity index 90% rename from src/Phpml/Classification/Traits/Trainable.php rename to src/Phpml/Helper/Trainable.php index 8fa97f2f..36b8993e 100644 --- a/src/Phpml/Classification/Traits/Trainable.php +++ b/src/Phpml/Helper/Trainable.php @@ -2,7 +2,7 @@ declare (strict_types = 1); -namespace Phpml\Classification\Traits; +namespace Phpml\Helper; trait Trainable { diff --git a/src/Phpml/Regression/LeastSquares.php b/src/Phpml/Regression/LeastSquares.php index cd0251f9..83a6a654 100644 --- a/src/Phpml/Regression/LeastSquares.php +++ b/src/Phpml/Regression/LeastSquares.php @@ -4,10 +4,12 @@ namespace Phpml\Regression; +use Phpml\Helper\Predictable; use Phpml\Math\Matrix; class LeastSquares implements Regression { + use Predictable; /** * @var array */ @@ -45,7 +47,7 @@ public function train(array $samples, array $targets) * * @return mixed */ - public function predict($sample) + public function predictSample(array $sample) { $result = $this->intercept; foreach ($this->coefficients as $index => $coefficient) { diff --git a/src/Phpml/Regression/Regression.php b/src/Phpml/Regression/Regression.php index a7837d43..12d2f528 100644 --- a/src/Phpml/Regression/Regression.php +++ b/src/Phpml/Regression/Regression.php @@ -13,9 +13,9 @@ interface Regression public function train(array $samples, array $targets); /** - * @param float $sample + * @param array $samples * * @return mixed */ - public function predict($sample); + public function predict(array $samples); } diff --git a/src/Phpml/Regression/SVR.php b/src/Phpml/Regression/SVR.php new file mode 100644 index 00000000..07b14598 --- /dev/null +++ b/src/Phpml/Regression/SVR.php @@ -0,0 +1,31 @@ + $label) { - $set .= sprintf('%s %s %s', $numericLabels[$label], self::sampleRow($samples[$index]), PHP_EOL); + $set .= sprintf('%s %s %s', ($targets ? $label : $numericLabels[$label]), self::sampleRow($samples[$index]), PHP_EOL); } return $set; diff --git a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php index 7a47db77..ef52d293 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -131,7 +131,7 @@ public function __construct( public function train(array $samples, array $labels) { $this->labels = $labels; - $trainingSet = DataTransformer::trainingSet($samples, $labels); + $trainingSet = DataTransformer::trainingSet($samples, $labels, in_array($this->type, [Type::EPSILON_SVR, Type::NU_SVR])); file_put_contents($trainingSetFileName = $this->varPath.uniqid(), $trainingSet); $modelFileName = $trainingSetFileName.'-model'; @@ -169,13 +169,17 @@ public function predict(array $samples) $output = ''; exec(escapeshellcmd($command), $output); - $rawPredictions = file_get_contents($outputFileName); + $predictions = file_get_contents($outputFileName); unlink($testSetFileName); unlink($modelFileName); unlink($outputFileName); - $predictions = DataTransformer::predictions($rawPredictions, $this->labels); + if (in_array($this->type, [Type::C_SVC, Type::NU_SVC])) { + $predictions = DataTransformer::predictions($predictions, $this->labels); + } else { + $predictions = explode(PHP_EOL, trim($predictions)); + } if (!is_array($samples[0])) { return $predictions[0]; diff --git a/tests/Phpml/Regression/SVRTest.php b/tests/Phpml/Regression/SVRTest.php new file mode 100644 index 00000000..d7940627 --- /dev/null +++ b/tests/Phpml/Regression/SVRTest.php @@ -0,0 +1,50 @@ +train($samples, $targets); + + $this->assertEquals(4.03, $regression->predict([64]), '', $delta); + + $samples = [[9300], [10565], [15000], [15000], [17764], [57000], [65940], [73676], [77006], [93739], [146088], [153260]]; + $targets = [7100, 15500, 4400, 4400, 5900, 4600, 8800, 2000, 2750, 2550, 960, 1025]; + + $regression = new SVR(Kernel::LINEAR); + $regression->train($samples, $targets); + + $this->assertEquals(6236.12, $regression->predict([9300]), '', $delta); + $this->assertEquals(4718.29, $regression->predict([57000]), '', $delta); + $this->assertEquals(4081.69, $regression->predict([77006]), '', $delta); + $this->assertEquals(6236.12, $regression->predict([9300]), '', $delta); + $this->assertEquals(1655.26, $regression->predict([153260]), '', $delta); + } + + public function testPredictMultiFeaturesSamples() + { + $delta = 0.01; + + $samples = [[73676, 1996], [77006, 1998], [10565, 2000], [146088, 1995], [15000, 2001], [65940, 2000], [9300, 2000], [93739, 1996], [153260, 1994], [17764, 2002], [57000, 1998], [15000, 2000]]; + $targets = [2000, 2750, 15500, 960, 4400, 8800, 7100, 2550, 1025, 5900, 4600, 4400]; + + $regression = new SVR(Kernel::LINEAR); + $regression->train($samples, $targets); + + $this->assertEquals(4109.82, $regression->predict([60000, 1996]), '', $delta); + $this->assertEquals(4112.28, $regression->predict([60000, 2000]), '', $delta); + } +} From 078f543146847f477f5ce85fc159be66ccce77bd Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sat, 7 May 2016 23:17:46 +0200 Subject: [PATCH 009/328] add word tokenizer --- tests/Phpml/Regression/SVRTest.php | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/tests/Phpml/Regression/SVRTest.php b/tests/Phpml/Regression/SVRTest.php index d7940627..dd3518df 100644 --- a/tests/Phpml/Regression/SVRTest.php +++ b/tests/Phpml/Regression/SVRTest.php @@ -20,18 +20,6 @@ public function testPredictSingleFeatureSamples() $regression->train($samples, $targets); $this->assertEquals(4.03, $regression->predict([64]), '', $delta); - - $samples = [[9300], [10565], [15000], [15000], [17764], [57000], [65940], [73676], [77006], [93739], [146088], [153260]]; - $targets = [7100, 15500, 4400, 4400, 5900, 4600, 8800, 2000, 2750, 2550, 960, 1025]; - - $regression = new SVR(Kernel::LINEAR); - $regression->train($samples, $targets); - - $this->assertEquals(6236.12, $regression->predict([9300]), '', $delta); - $this->assertEquals(4718.29, $regression->predict([57000]), '', $delta); - $this->assertEquals(4081.69, $regression->predict([77006]), '', $delta); - $this->assertEquals(6236.12, $regression->predict([9300]), '', $delta); - $this->assertEquals(1655.26, $regression->predict([153260]), '', $delta); } public function testPredictMultiFeaturesSamples() @@ -44,7 +32,6 @@ public function testPredictMultiFeaturesSamples() $regression = new SVR(Kernel::LINEAR); $regression->train($samples, $targets); - $this->assertEquals(4109.82, $regression->predict([60000, 1996]), '', $delta); - $this->assertEquals(4112.28, $regression->predict([60000, 2000]), '', $delta); + $this->assertEquals([4109.82, 4112.28], $regression->predict([[60000, 1996], [60000, 2000]]), '', $delta); } } From 46197eba7b53c9eb48d16b3420ca0b091aaf0a31 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sat, 7 May 2016 23:17:52 +0200 Subject: [PATCH 010/328] add word tokenizer --- src/Phpml/Tokenization/WordTokenizer.php | 21 ++++++++++ .../Phpml/Tokenization/WordTokenizerTest.php | 40 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 src/Phpml/Tokenization/WordTokenizer.php create mode 100644 tests/Phpml/Tokenization/WordTokenizerTest.php diff --git a/src/Phpml/Tokenization/WordTokenizer.php b/src/Phpml/Tokenization/WordTokenizer.php new file mode 100644 index 00000000..c384c394 --- /dev/null +++ b/src/Phpml/Tokenization/WordTokenizer.php @@ -0,0 +1,21 @@ +assertEquals($tokens, $tokenizer->tokenize($text)); + } + + public function testTokenizationOnUtf8() + { + $tokenizer = new WordTokenizer(); + + $text = '鋍鞎 鳼 鞮鞢騉 袟袘觕, 炟砏 蒮 謺貙蹖 偢偣唲 蒛 箷箯緷 鑴鱱爧 覮轀, + 剆坲 煘煓瑐 鬐鶤鶐 飹勫嫢 銪 餀 枲柊氠 鍎鞚韕 焲犈, + 殍涾烰 齞齝囃 蹅輶 鄜, 孻憵 擙樲橚 藒襓謥 岯岪弨 蒮 廞徲 孻憵懥 趡趛踠 槏'; + + $tokens = ['鋍鞎', '鞮鞢騉', '袟袘觕', '炟砏', '謺貙蹖', '偢偣唲', '箷箯緷', '鑴鱱爧', '覮轀', + '剆坲', '煘煓瑐', '鬐鶤鶐', '飹勫嫢', '枲柊氠', '鍎鞚韕', '焲犈', + '殍涾烰', '齞齝囃', '蹅輶', '孻憵', '擙樲橚', '藒襓謥', '岯岪弨', '廞徲', '孻憵懥', '趡趛踠', ]; + + $this->assertEquals($tokens, $tokenizer->tokenize($text)); + } +} From 365a9baeca2c4394fff8a1dabdbe8d1c78185187 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sat, 7 May 2016 23:53:42 +0200 Subject: [PATCH 011/328] update docs --- README.md | 4 ++ docs/index.md | 6 ++- docs/machine-learning/classification/svc.md | 47 +++++++++++++++++ .../token-count-vectorizer.md | 50 +++++++++++++++++++ docs/machine-learning/regression/svr.md | 44 ++++++++++++++++ mkdocs.yml | 4 ++ 6 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 docs/machine-learning/classification/svc.md create mode 100644 docs/machine-learning/feature-extraction/token-count-vectorizer.md create mode 100644 docs/machine-learning/regression/svr.md diff --git a/README.md b/README.md index 4b0e6a89..20cb9cac 100644 --- a/README.md +++ b/README.md @@ -37,15 +37,19 @@ composer require php-ai/php-ml ## Features * Classification + * [SVC](http://php-ml.readthedocs.io/en/latest/machine-learning/classification/svc/) * [k-Nearest Neighbors](http://php-ml.readthedocs.io/en/latest/machine-learning/classification/k-nearest-neighbors/) * [Naive Bayes](http://php-ml.readthedocs.io/en/latest/machine-learning/classification/naive-bayes/) * Regression * [Least Squares](http://php-ml.readthedocs.io/en/latest/machine-learning/regression/least-squares/) + * [SVR](http://php-ml.readthedocs.io/en/latest/machine-learning/regression/svr/) * Clustering * [k-Means](http://php-ml.readthedocs.io/en/latest/machine-learning/clustering/k-means) * [DBSCAN](http://php-ml.readthedocs.io/en/latest/machine-learning/clustering/dbscan) * Cross Validation * [Random Split](http://php-ml.readthedocs.io/en/latest/machine-learning/cross-validation/random-split) +* Feature Extraction + * [Token Count Vectorizer](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-extraction/token-count-vectorizer) * Datasets * [CSV](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/csv-dataset) * Ready to use: diff --git a/docs/index.md b/docs/index.md index d3f65b7e..20cb9cac 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,4 +1,4 @@ -# PHP Machine Learning library +# PHP-ML - Machine Learning library for PHP [![Build Status](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/build.png?b=develop)](https://scrutinizer-ci.com/g/php-ai/php-ml/build-status/develop) [![Documentation Status](https://readthedocs.org/projects/php-ml/badge/?version=develop)](http://php-ml.readthedocs.org/en/develop/?badge=develop) @@ -37,15 +37,19 @@ composer require php-ai/php-ml ## Features * Classification + * [SVC](http://php-ml.readthedocs.io/en/latest/machine-learning/classification/svc/) * [k-Nearest Neighbors](http://php-ml.readthedocs.io/en/latest/machine-learning/classification/k-nearest-neighbors/) * [Naive Bayes](http://php-ml.readthedocs.io/en/latest/machine-learning/classification/naive-bayes/) * Regression * [Least Squares](http://php-ml.readthedocs.io/en/latest/machine-learning/regression/least-squares/) + * [SVR](http://php-ml.readthedocs.io/en/latest/machine-learning/regression/svr/) * Clustering * [k-Means](http://php-ml.readthedocs.io/en/latest/machine-learning/clustering/k-means) * [DBSCAN](http://php-ml.readthedocs.io/en/latest/machine-learning/clustering/dbscan) * Cross Validation * [Random Split](http://php-ml.readthedocs.io/en/latest/machine-learning/cross-validation/random-split) +* Feature Extraction + * [Token Count Vectorizer](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-extraction/token-count-vectorizer) * Datasets * [CSV](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/csv-dataset) * Ready to use: diff --git a/docs/machine-learning/classification/svc.md b/docs/machine-learning/classification/svc.md new file mode 100644 index 00000000..d502dac2 --- /dev/null +++ b/docs/machine-learning/classification/svc.md @@ -0,0 +1,47 @@ +# Support Vector Classification + +Classifier implementing Support Vector Machine based on libsvm. + +### Constructor Parameters + +* $kernel (int) - kernel type to be used in the algorithm (default Kernel::LINEAR) +* $cost (float) - parameter C of C-SVC (default 1.0) +* $degree (int) - degree of the Kernel::POLYNOMIAL function (default 3) +* $gamma (float) - kernel coefficient for ‘Kernel::RBF’, ‘Kernel::POLYNOMIAL’ and ‘Kernel::SIGMOID’. If gamma is ‘null’ then 1/features will be used instead. +* $coef0 (float) - independent term in kernel function. It is only significant in ‘Kernel::POLYNOMIAL’ and ‘Kernel::SIGMOID’ (default 0.0) +* $tolerance (float) - tolerance of termination criterion (default 0.001) +* $cacheSize (int) - cache memory size in MB (default 100) +* $shrinking (bool) - whether to use the shrinking heuristics (default true) +* $probabilityEstimates (bool) - whether to enable probability estimates (default false) + +``` +$classifier = new SVC(Kernel::LINEAR, $cost = 1000); +$classifier = new SVC(Kernel::RBF, $cost = 1000, $degree = 3, $gamma = 6); +``` + +### Train + +To train a classifier simply provide train samples and labels (as `array`). Example: + +``` +use Phpml\Classification\SVC; +use Phpml\SupportVectorMachine\Kernel; + +$samples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; +$labels = ['a', 'a', 'a', 'b', 'b', 'b']; + +$classifier = new SVC(Kernel::LINEAR, $cost = 1000); +$classifier->train($samples, $labels); +``` + +### Predict + +To predict sample label use `predict` method. You can provide one sample or array of samples: + +``` +$classifier->predict([3, 2]); +// return 'b' + +$classifier->predict([[3, 2], [1, 5]]); +// return ['b', 'a'] +``` diff --git a/docs/machine-learning/feature-extraction/token-count-vectorizer.md b/docs/machine-learning/feature-extraction/token-count-vectorizer.md new file mode 100644 index 00000000..83c6aaa3 --- /dev/null +++ b/docs/machine-learning/feature-extraction/token-count-vectorizer.md @@ -0,0 +1,50 @@ +# Token Count Vectorizer + +Transform a collection of text samples to a vector of token counts. + +### Constructor Parameters + +* $tokenizer (Tokenizer) - tokenizer object (see below) +* $minDF (float) - ignore tokens that have a samples frequency strictly lower than the given threshold. This value is also called cut-off in the literature. (default 0) + +``` +use Phpml\FeatureExtraction\TokenCountVectorizer; +use Phpml\Tokenization\WhitespaceTokenizer; + +$vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer()); +``` + +### Transformation + +To transform a collection of text samples use `transform` method. Example: + +``` +$samples = [ + 'Lorem ipsum dolor sit amet dolor', + 'Mauris placerat ipsum dolor', + 'Mauris diam eros fringilla diam', +]; + +$vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer()); +$vectorizer->transform($samples) +// return $vector = [ +// [0 => 1, 1 => 1, 2 => 2, 3 => 1, 4 => 1], +// [5 => 1, 6 => 1, 1 => 1, 2 => 1], +// [5 => 1, 7 => 2, 8 => 1, 9 => 1], +//]; + +``` + +### Vocabulary + +You can extract vocabulary using `getVocabulary()` method. Example: + +``` +$vectorizer->getVocabulary(); +// return $vocabulary = ['Lorem', 'ipsum', 'dolor', 'sit', 'amet', 'Mauris', 'placerat', 'diam', 'eros', 'fringilla']; +``` + +### Tokenizers + +* WhitespaceTokenizer - select tokens by whitespace. +* WordTokenizer - select tokens of 2 or more alphanumeric characters (punctuation is completely ignored and always treated as a token separator). diff --git a/docs/machine-learning/regression/svr.md b/docs/machine-learning/regression/svr.md new file mode 100644 index 00000000..ed2d10ff --- /dev/null +++ b/docs/machine-learning/regression/svr.md @@ -0,0 +1,44 @@ +# Support Vector Regression + +Class implementing Epsilon-Support Vector Regression based on libsvm. + +### Constructor Parameters + +* $kernel (int) - kernel type to be used in the algorithm (default Kernel::LINEAR) +* $degree (int) - degree of the Kernel::POLYNOMIAL function (default 3) +* $epsilon (float) - epsilon in loss function of epsilon-SVR (default 0.1) +* $cost (float) - parameter C of C-SVC (default 1.0) +* $gamma (float) - kernel coefficient for ‘Kernel::RBF’, ‘Kernel::POLYNOMIAL’ and ‘Kernel::SIGMOID’. If gamma is ‘null’ then 1/features will be used instead. +* $coef0 (float) - independent term in kernel function. It is only significant in ‘Kernel::POLYNOMIAL’ and ‘Kernel::SIGMOID’ (default 0.0) +* $tolerance (float) - tolerance of termination criterion (default 0.001) +* $cacheSize (int) - cache memory size in MB (default 100) +* $shrinking (bool) - whether to use the shrinking heuristics (default true) + +``` +$regression = new SVR(Kernel::LINEAR); +$regression = new SVR(Kernel::LINEAR, $degree = 3, $epsilon=10.0); +``` + +### Train + +To train a model simply provide train samples and targets values (as `array`). Example: + +``` +use Phpml\Regression\SVR; +use Phpml\SupportVectorMachine\Kernel; + +$samples = [[60], [61], [62], [63], [65]]; +$targets = [3.1, 3.6, 3.8, 4, 4.1]; + +$regression = new SVR(Kernel::LINEAR); +$regression->train($samples, $targets); +``` + +### Predict + +To predict sample target value use `predict` method. You can provide one sample or array of samples: + +``` +$regression->predict([64]) +// return 4.03 +``` diff --git a/mkdocs.yml b/mkdocs.yml index a596d913..f833fc37 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -3,15 +3,19 @@ pages: - Home: index.md - Machine Learning: - Classification: + - SVC: machine-learning/classification/svc.md - KNearestNeighbors: machine-learning/classification/k-nearest-neighbors.md - NaiveBayes: machine-learning/classification/naive-bayes.md - Regression: - LeastSquares: machine-learning/regression/least-squares.md + - SVR: machine-learning/regression/svr.md - Clustering: - KMeans: machine-learning/clustering/k-means.md - DBSCAN: machine-learning/clustering/dbscan.md - Cross Validation: - RandomSplit: machine-learning/cross-validation/random-split.md + - Feature Extraction: + - Token Count Vectorizer: machine-learning/feature-extraction/token-count-vectorizer.md - Datasets: - Array Dataset: machine-learning/datasets/array-dataset.md - CSV Dataset: machine-learning/datasets/csv-dataset.md From b0ab236ab9cdefe5c1822449a03391807556b50b Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 8 May 2016 14:47:17 +0200 Subject: [PATCH 012/328] create imputer tool for completing missing values --- src/Phpml/Preprocessing/Imputer.php | 86 +++++++++++++++++++ src/Phpml/Preprocessing/Imputer/Strategy.php | 15 ++++ .../Imputer/Strategy/MeanStrategy.php | 16 ++++ src/Phpml/Preprocessing/Preprocessor.php | 13 +++ tests/Phpml/Preprocessing/ImputerTest.php | 55 ++++++++++++ 5 files changed, 185 insertions(+) create mode 100644 src/Phpml/Preprocessing/Imputer.php create mode 100644 src/Phpml/Preprocessing/Imputer/Strategy.php create mode 100644 src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php create mode 100644 src/Phpml/Preprocessing/Preprocessor.php create mode 100644 tests/Phpml/Preprocessing/ImputerTest.php diff --git a/src/Phpml/Preprocessing/Imputer.php b/src/Phpml/Preprocessing/Imputer.php new file mode 100644 index 00000000..355ff148 --- /dev/null +++ b/src/Phpml/Preprocessing/Imputer.php @@ -0,0 +1,86 @@ +missingValue = $missingValue; + $this->strategy = $strategy; + $this->axis = $axis; + } + + /** + * @param array $samples + */ + public function preprocess(array &$samples) + { + foreach ($samples as &$sample) { + $this->preprocessSample($sample, $samples); + } + } + + /** + * @param array $sample + * @param array $samples + */ + private function preprocessSample(array &$sample, array $samples) + { + foreach ($sample as $column => &$value) { + if ($value === $this->missingValue) { + $value = $this->strategy->replaceValue($this->getAxis($column, $sample, $samples)); + } + } + } + + /** + * @param int $column + * @param array $currentSample + * @param array $samples + * + * @return array + */ + private function getAxis(int $column, array $currentSample, array $samples): array + { + if (self::AXIS_ROW === $this->axis) { + return array_diff($currentSample, [$this->missingValue]); + } + + $axis = []; + foreach ($samples as $sample) { + if ($sample[$column] !== $this->missingValue) { + $axis[] = $sample[$column]; + } + } + + return $axis; + } +} diff --git a/src/Phpml/Preprocessing/Imputer/Strategy.php b/src/Phpml/Preprocessing/Imputer/Strategy.php new file mode 100644 index 00000000..2cf11440 --- /dev/null +++ b/src/Phpml/Preprocessing/Imputer/Strategy.php @@ -0,0 +1,15 @@ +preprocess($data); + + $this->assertEquals($imputeData, $data, '', $delta = 0.01); + } + + public function testCompletingMissingValuesWithMeanStrategyOnRowAxis() + { + $data = [ + [1, null, 3, 4], + [4, 3, 2, 1], + [null, 6, 7, 8], + [8, 7, null, 5], + ]; + + $imputeData = [ + [1, 2.66, 3, 4], + [4, 3, 2, 1], + [7, 6, 7, 8], + [8, 7, 6.66, 5], + ]; + + $imputer = new Imputer(null, new MeanStrategy(), Imputer::AXIS_ROW); + $imputer->preprocess($data); + + $this->assertEquals($imputeData, $data, '', $delta = 0.01); + } +} From ed1e07e803bda538c6295bb882d0259674a932f6 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 8 May 2016 19:12:39 +0200 Subject: [PATCH 013/328] median function in statistic --- src/Phpml/Math/Statistic/Mean.php | 33 +++++++++++++++++-- .../Imputer/Strategy/MeanStrategy.php | 5 +++ tests/Phpml/Math/Statistic/MeanTest.php | 23 +++++++++++++ 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/Phpml/Math/Statistic/Mean.php b/src/Phpml/Math/Statistic/Mean.php index 2716b785..0988e01d 100644 --- a/src/Phpml/Math/Statistic/Mean.php +++ b/src/Phpml/Math/Statistic/Mean.php @@ -4,15 +4,42 @@ namespace Phpml\Math\Statistic; +use Phpml\Exception\InvalidArgumentException; + class Mean { /** - * @param array $a + * @param array $numbers * * @return float */ - public static function arithmetic(array $a) + public static function arithmetic(array $numbers) { - return array_sum($a) / count($a); + return array_sum($numbers) / count($numbers); } + + /** + * @param array $numbers + * + * @return float|mixed + * + * @throws InvalidArgumentException + */ + public static function median(array $numbers) { + $count = count($numbers); + if (0 == $count) { + throw InvalidArgumentException::arrayCantBeEmpty(); + } + + $middleIndex = floor($count / 2); + sort($numbers, SORT_NUMERIC); + $median = $numbers[$middleIndex]; + + if (0 == $count % 2) { + $median = ($median + $numbers[$middleIndex - 1]) / 2; + } + + return $median; + } + } diff --git a/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php b/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php index b9ffdecd..12a732b4 100644 --- a/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php +++ b/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php @@ -9,6 +9,11 @@ class MeanStrategy implements Strategy { + /** + * @param array $currentAxis + * + * @return float + */ public function replaceValue(array $currentAxis) { return Mean::arithmetic($currentAxis); diff --git a/tests/Phpml/Math/Statistic/MeanTest.php b/tests/Phpml/Math/Statistic/MeanTest.php index f0dca3be..d56f9654 100644 --- a/tests/Phpml/Math/Statistic/MeanTest.php +++ b/tests/Phpml/Math/Statistic/MeanTest.php @@ -15,4 +15,27 @@ public function testArithmeticMean() $this->assertEquals(41.16, Mean::arithmetic([43, 21, 25, 42, 57, 59]), '', $delta); $this->assertEquals(1.7, Mean::arithmetic([0.5, 0.5, 1.5, 2.5, 3.5]), '', $delta); } + + /** + * @expectedException \Phpml\Exception\InvalidArgumentException + */ + public function testThrowExceptionOnEmptyArrayMedian() + { + Mean::median([]); + } + + public function testMedianOnOddLengthArray() + { + $numbers = [5, 2, 6, 1, 3]; + + $this->assertEquals(3, Mean::median($numbers)); + } + + public function testMedianOnEvenLengthArray() + { + $numbers = [5, 2, 6, 1, 3, 4]; + + $this->assertEquals(3.5, Mean::median($numbers)); + } + } From a761d0e8f2286b832345147fbd545c6e687615a6 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 8 May 2016 19:23:54 +0200 Subject: [PATCH 014/328] mode (dominant) from numbers --- src/Phpml/Math/Statistic/Mean.php | 39 ++++++++++++++++++++++--- tests/Phpml/Math/Statistic/MeanTest.php | 26 ++++++++++++++++- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/Phpml/Math/Statistic/Mean.php b/src/Phpml/Math/Statistic/Mean.php index 0988e01d..9d87a978 100644 --- a/src/Phpml/Math/Statistic/Mean.php +++ b/src/Phpml/Math/Statistic/Mean.php @@ -12,9 +12,13 @@ class Mean * @param array $numbers * * @return float + * + * @throws InvalidArgumentException */ public static function arithmetic(array $numbers) { + self::checkArrayLength($numbers); + return array_sum($numbers) / count($numbers); } @@ -26,11 +30,10 @@ public static function arithmetic(array $numbers) * @throws InvalidArgumentException */ public static function median(array $numbers) { - $count = count($numbers); - if (0 == $count) { - throw InvalidArgumentException::arrayCantBeEmpty(); - } + self::checkArrayLength($numbers); + + $count = count($numbers); $middleIndex = floor($count / 2); sort($numbers, SORT_NUMERIC); $median = $numbers[$middleIndex]; @@ -42,4 +45,32 @@ public static function median(array $numbers) { return $median; } + /** + * @param array $numbers + * + * @return mixed + * + * @throws InvalidArgumentException + */ + public static function mode(array $numbers) + { + self::checkArrayLength($numbers); + + $values = array_count_values($numbers); + + return array_search(max($values), $values); + } + + /** + * @param array $array + * + * @throws InvalidArgumentException + */ + private static function checkArrayLength(array $array) + { + if (0 == count($array)) { + throw InvalidArgumentException::arrayCantBeEmpty(); + } + } + } diff --git a/tests/Phpml/Math/Statistic/MeanTest.php b/tests/Phpml/Math/Statistic/MeanTest.php index d56f9654..289f9a37 100644 --- a/tests/Phpml/Math/Statistic/MeanTest.php +++ b/tests/Phpml/Math/Statistic/MeanTest.php @@ -8,6 +8,15 @@ class MeanTest extends \PHPUnit_Framework_TestCase { + + /** + * @expectedException \Phpml\Exception\InvalidArgumentException + */ + public function testArithmeticThrowExceptionOnEmptyArray() + { + Mean::arithmetic([]); + } + public function testArithmeticMean() { $delta = 0.01; @@ -19,7 +28,7 @@ public function testArithmeticMean() /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnEmptyArrayMedian() + public function testMedianThrowExceptionOnEmptyArray() { Mean::median([]); } @@ -38,4 +47,19 @@ public function testMedianOnEvenLengthArray() $this->assertEquals(3.5, Mean::median($numbers)); } + /** + * @expectedException \Phpml\Exception\InvalidArgumentException + */ + public function testModeThrowExceptionOnEmptyArray() + { + Mean::mode([]); + } + + public function testModeOnArray() + { + $numbers = [5, 2, 6, 1, 3, 4, 6, 6, 5]; + + $this->assertEquals(6, Mean::mode($numbers)); + } + } From 65cdfe64b2c307b223fd90f160e49d90e9cc4321 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 8 May 2016 19:33:39 +0200 Subject: [PATCH 015/328] implement Median and MostFrequent strategy for imputer --- src/Phpml/Math/Statistic/Mean.php | 5 +- .../Imputer/Strategy/MedianStrategy.php | 21 ++++ .../Imputer/Strategy/MostFrequentStrategy.php | 21 ++++ tests/Phpml/Math/Statistic/MeanTest.php | 2 - tests/Phpml/Preprocessing/ImputerTest.php | 98 ++++++++++++++++++- 5 files changed, 140 insertions(+), 7 deletions(-) create mode 100644 src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php create mode 100644 src/Phpml/Preprocessing/Imputer/Strategy/MostFrequentStrategy.php diff --git a/src/Phpml/Math/Statistic/Mean.php b/src/Phpml/Math/Statistic/Mean.php index 9d87a978..3804848f 100644 --- a/src/Phpml/Math/Statistic/Mean.php +++ b/src/Phpml/Math/Statistic/Mean.php @@ -29,8 +29,8 @@ public static function arithmetic(array $numbers) * * @throws InvalidArgumentException */ - public static function median(array $numbers) { - + public static function median(array $numbers) + { self::checkArrayLength($numbers); $count = count($numbers); @@ -72,5 +72,4 @@ private static function checkArrayLength(array $array) throw InvalidArgumentException::arrayCantBeEmpty(); } } - } diff --git a/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php b/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php new file mode 100644 index 00000000..3746760b --- /dev/null +++ b/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php @@ -0,0 +1,21 @@ +assertEquals(6, Mean::mode($numbers)); } - } diff --git a/tests/Phpml/Preprocessing/ImputerTest.php b/tests/Phpml/Preprocessing/ImputerTest.php index 630f4b1e..9da97655 100644 --- a/tests/Phpml/Preprocessing/ImputerTest.php +++ b/tests/Phpml/Preprocessing/ImputerTest.php @@ -6,10 +6,12 @@ use Phpml\Preprocessing\Imputer; use Phpml\Preprocessing\Imputer\Strategy\MeanStrategy; +use Phpml\Preprocessing\Imputer\Strategy\MedianStrategy; +use Phpml\Preprocessing\Imputer\Strategy\MostFrequentStrategy; class ImputerTest extends \PHPUnit_Framework_TestCase { - public function testCompletingMissingValuesWithMeanStrategyOnColumnAxis() + public function testComplementsMissingValuesWithMeanStrategyOnColumnAxis() { $data = [ [1, null, 3, 4], @@ -31,7 +33,7 @@ public function testCompletingMissingValuesWithMeanStrategyOnColumnAxis() $this->assertEquals($imputeData, $data, '', $delta = 0.01); } - public function testCompletingMissingValuesWithMeanStrategyOnRowAxis() + public function testComplementsMissingValuesWithMeanStrategyOnRowAxis() { $data = [ [1, null, 3, 4], @@ -52,4 +54,96 @@ public function testCompletingMissingValuesWithMeanStrategyOnRowAxis() $this->assertEquals($imputeData, $data, '', $delta = 0.01); } + + public function testComplementsMissingValuesWithMediaStrategyOnColumnAxis() + { + $data = [ + [1, null, 3, 4], + [4, 3, 2, 1], + [null, 6, 7, 8], + [8, 7, null, 5], + ]; + + $imputeData = [ + [1, 6, 3, 4], + [4, 3, 2, 1], + [4, 6, 7, 8], + [8, 7, 3, 5], + ]; + + $imputer = new Imputer(null, new MedianStrategy(), Imputer::AXIS_COLUMN); + $imputer->preprocess($data); + + $this->assertEquals($imputeData, $data, '', $delta = 0.01); + } + + public function testComplementsMissingValuesWithMediaStrategyOnRowAxis() + { + $data = [ + [1, null, 3, 4], + [4, 3, 2, 1], + [null, 6, 7, 8], + [8, 7, null, 5], + ]; + + $imputeData = [ + [1, 3, 3, 4], + [4, 3, 2, 1], + [7, 6, 7, 8], + [8, 7, 7, 5], + ]; + + $imputer = new Imputer(null, new MedianStrategy(), Imputer::AXIS_ROW); + $imputer->preprocess($data); + + $this->assertEquals($imputeData, $data, '', $delta = 0.01); + } + + public function testComplementsMissingValuesWithMostFrequentStrategyOnColumnAxis() + { + $data = [ + [1, null, 3, 4], + [4, 3, 2, 1], + [null, 6, 7, 8], + [8, 7, null, 5], + [8, 3, 2, 5], + ]; + + $imputeData = [ + [1, 3, 3, 4], + [4, 3, 2, 1], + [8, 6, 7, 8], + [8, 7, 2, 5], + [8, 3, 2, 5], + ]; + + $imputer = new Imputer(null, new MostFrequentStrategy(), Imputer::AXIS_COLUMN); + $imputer->preprocess($data); + + $this->assertEquals($imputeData, $data); + } + + public function testComplementsMissingValuesWithMostFrequentStrategyOnRowAxis() + { + $data = [ + [1, null, 3, 4, 3], + [4, 3, 2, 1, 7], + [null, 6, 7, 8, 6], + [8, 7, null, 5, 5], + [8, 3, 2, 5, 4], + ]; + + $imputeData = [ + [1, 3, 3, 4, 3], + [4, 3, 2, 1, 7], + [6, 6, 7, 8, 6], + [8, 7, 5, 5, 5], + [8, 3, 2, 5, 4], + ]; + + $imputer = new Imputer(null, new MostFrequentStrategy(), Imputer::AXIS_ROW); + $imputer->preprocess($data); + + $this->assertEquals($imputeData, $data); + } } From fb04b578535ad17f303cc508bde0e771e2c194ea Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 8 May 2016 20:35:01 +0200 Subject: [PATCH 016/328] implement data Normalizer with L1 and L2 norm --- src/Phpml/Exception/NormalizerException.php | 16 ++++ src/Phpml/Preprocessing/Normalizer.php | 83 ++++++++++++++++++++ tests/Phpml/Preprocessing/NormalizerTest.php | 58 ++++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 src/Phpml/Exception/NormalizerException.php create mode 100644 src/Phpml/Preprocessing/Normalizer.php create mode 100644 tests/Phpml/Preprocessing/NormalizerTest.php diff --git a/src/Phpml/Exception/NormalizerException.php b/src/Phpml/Exception/NormalizerException.php new file mode 100644 index 00000000..9f88f0c5 --- /dev/null +++ b/src/Phpml/Exception/NormalizerException.php @@ -0,0 +1,16 @@ +norm = $norm; + } + + /** + * @param array $samples + */ + public function preprocess(array &$samples) + { + $method = sprintf('normalizeL%s', $this->norm); + foreach ($samples as &$sample) { + $this->$method($sample); + } + } + + /** + * @param array $sample + */ + private function normalizeL1(array &$sample) + { + $norm1 = 0; + foreach ($sample as $feature) { + $norm1 += abs($feature); + } + + if (0 == $norm1) { + $count = count($sample); + $sample = array_fill(0, $count, 1.0 / $count); + } else { + foreach ($sample as &$feature) { + $feature = $feature / $norm1; + } + } + } + + /** + * @param array $sample + */ + private function normalizeL2(array &$sample) + { + $norm2 = 0; + foreach ($sample as $feature) { + $norm2 += $feature * $feature; + } + $norm2 = sqrt($norm2); + + if (0 == $norm2) { + $sample = array_fill(0, count($sample), 1); + } else { + foreach ($sample as &$feature) { + $feature = $feature / $norm2; + } + } + } +} diff --git a/tests/Phpml/Preprocessing/NormalizerTest.php b/tests/Phpml/Preprocessing/NormalizerTest.php new file mode 100644 index 00000000..b373944e --- /dev/null +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -0,0 +1,58 @@ +preprocess($samples); + + $this->assertEquals($normalized, $samples, '', $delta = 0.01); + } + + public function testNormalizeSamplesWithL1Norm() + { + $samples = [ + [1, -1, 2], + [2, 0, 0], + [0, 1, -1], + ]; + + $normalized = [ + [0.25, -0.25, 0.5], + [1.0, 0.0, 0.0], + [0.0, 0.5, -0.5], + ]; + + $normalizer = new Normalizer(Normalizer::NORM_L1); + $normalizer->preprocess($samples); + + $this->assertEquals($normalized, $samples, '', $delta = 0.01); + } +} From 77647fda45c0ff8876447b337cf33b5c39ae3db5 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Mon, 9 May 2016 23:52:09 +0200 Subject: [PATCH 017/328] update readme --- README.md | 4 ++++ docs/index.md | 4 ++++ .../preprocessing/imputation-missing-values.md | 0 docs/machine-learning/preprocessing/normalization.md | 0 docs/math/statistic.md | 7 +++++++ 5 files changed, 15 insertions(+) create mode 100644 docs/machine-learning/preprocessing/imputation-missing-values.md create mode 100644 docs/machine-learning/preprocessing/normalization.md create mode 100644 docs/math/statistic.md diff --git a/README.md b/README.md index 20cb9cac..dc65c099 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,9 @@ composer require php-ai/php-ml * [DBSCAN](http://php-ml.readthedocs.io/en/latest/machine-learning/clustering/dbscan) * Cross Validation * [Random Split](http://php-ml.readthedocs.io/en/latest/machine-learning/cross-validation/random-split) +* Preprocessing + * [Normalization](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/normalization) + * [Imputation missing values](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/imputation-missing-values) * Feature Extraction * [Token Count Vectorizer](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-extraction/token-count-vectorizer) * Datasets @@ -57,6 +60,7 @@ composer require php-ai/php-ml * Math * [Distance](http://php-ml.readthedocs.io/en/latest/math/distance/) * [Matrix](http://php-ml.readthedocs.io/en/latest/math/matrix/) + * [Statistic](http://php-ml.readthedocs.io/en/latest/math/statistic/) ## Contribute diff --git a/docs/index.md b/docs/index.md index 20cb9cac..dc65c099 100644 --- a/docs/index.md +++ b/docs/index.md @@ -48,6 +48,9 @@ composer require php-ai/php-ml * [DBSCAN](http://php-ml.readthedocs.io/en/latest/machine-learning/clustering/dbscan) * Cross Validation * [Random Split](http://php-ml.readthedocs.io/en/latest/machine-learning/cross-validation/random-split) +* Preprocessing + * [Normalization](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/normalization) + * [Imputation missing values](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/imputation-missing-values) * Feature Extraction * [Token Count Vectorizer](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-extraction/token-count-vectorizer) * Datasets @@ -57,6 +60,7 @@ composer require php-ai/php-ml * Math * [Distance](http://php-ml.readthedocs.io/en/latest/math/distance/) * [Matrix](http://php-ml.readthedocs.io/en/latest/math/matrix/) + * [Statistic](http://php-ml.readthedocs.io/en/latest/math/statistic/) ## Contribute diff --git a/docs/machine-learning/preprocessing/imputation-missing-values.md b/docs/machine-learning/preprocessing/imputation-missing-values.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/machine-learning/preprocessing/normalization.md b/docs/machine-learning/preprocessing/normalization.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/math/statistic.md b/docs/math/statistic.md new file mode 100644 index 00000000..89cc00eb --- /dev/null +++ b/docs/math/statistic.md @@ -0,0 +1,7 @@ +# Statistic + +### Correlation + +### Mean + +### Standard Deviation From ccfa38ba4d3689f3fbf4ee4a1ed7e23d5c27dd18 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 10 May 2016 23:44:28 +0200 Subject: [PATCH 018/328] wine and glass demo dataset docs --- README.md | 2 + docs/index.md | 2 + docs/machine-learning/datasets/demo/glass.md | 42 +++++++++++++++++++ docs/machine-learning/datasets/demo/iris.md | 2 + docs/machine-learning/datasets/demo/wine.md | 35 ++++++++++++++++ .../imputation-missing-values.md | 1 + .../preprocessing/normalization.md | 1 + 7 files changed, 85 insertions(+) create mode 100644 docs/machine-learning/datasets/demo/glass.md create mode 100644 docs/machine-learning/datasets/demo/wine.md diff --git a/README.md b/README.md index dc65c099..da958398 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,8 @@ composer require php-ai/php-ml * [CSV](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/csv-dataset) * Ready to use: * [Iris](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/demo/iris/) + * [Wine](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/demo/wine/) + * [Glass](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/demo/glass/) * Math * [Distance](http://php-ml.readthedocs.io/en/latest/math/distance/) * [Matrix](http://php-ml.readthedocs.io/en/latest/math/matrix/) diff --git a/docs/index.md b/docs/index.md index dc65c099..da958398 100644 --- a/docs/index.md +++ b/docs/index.md @@ -57,6 +57,8 @@ composer require php-ai/php-ml * [CSV](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/csv-dataset) * Ready to use: * [Iris](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/demo/iris/) + * [Wine](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/demo/wine/) + * [Glass](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/demo/glass/) * Math * [Distance](http://php-ml.readthedocs.io/en/latest/math/distance/) * [Matrix](http://php-ml.readthedocs.io/en/latest/math/matrix/) diff --git a/docs/machine-learning/datasets/demo/glass.md b/docs/machine-learning/datasets/demo/glass.md new file mode 100644 index 00000000..1ad84d3e --- /dev/null +++ b/docs/machine-learning/datasets/demo/glass.md @@ -0,0 +1,42 @@ +# Glass Dataset + +From USA Forensic Science Service; 6 types of glass; defined in terms of their oxide content (i.e. Na, Fe, K, etc) + +### Specification + +| Classes | 6 | +| Samples total | 214 | +| Features per sample | 9 | + +Samples per class: + * 70 float processed building windows + * 17 float processed vehicle windows + * 76 non-float processed building windows + * 13 containers + * 9 tableware + * 29 headlamps + +### Load + +To load Glass dataset simple use: + +``` +use Phpml\Dataset\Demo\Glass; + +$dataset = new Glass(); +``` + +### Several samples example + +``` +RI: refractive index,Na: Sodium,Mg: Magnesium,Al: Aluminum,Si: Silicon,K: Potassium,Ca: Calcium,Ba: Barium,Fe: Iron,type of glass +1.52101,13.64,4.49,1.10,71.78,0.06,8.75,0.00,0.00,building_windows_float_processed +1.51761,13.89,3.60,1.36,72.73,0.48,7.83,0.00,0.00,building_windows_float_processed +1.51618,13.53,3.55,1.54,72.99,0.39,7.78,0.00,0.00,building_windows_float_processed +1.51766,13.21,3.69,1.29,72.61,0.57,8.22,0.00,0.00,building_windows_float_processed +1.51742,13.27,3.62,1.24,73.08,0.55,8.07,0.00,0.00,building_windows_float_processed +1.51596,12.79,3.61,1.62,72.97,0.64,8.07,0.00,0.26,building_windows_float_processed +1.51743,13.30,3.60,1.14,73.09,0.58,8.17,0.00,0.00,building_windows_float_processed +1.51756,13.15,3.61,1.05,73.24,0.57,8.24,0.00,0.00,building_windows_float_processed +1.51918,14.04,3.58,1.37,72.08,0.56,8.30,0.00,0.00,building_windows_float_processed +``` diff --git a/docs/machine-learning/datasets/demo/iris.md b/docs/machine-learning/datasets/demo/iris.md index 5972f1bf..8baf7312 100644 --- a/docs/machine-learning/datasets/demo/iris.md +++ b/docs/machine-learning/datasets/demo/iris.md @@ -14,6 +14,8 @@ Most popular and widely available dataset of iris flower measurement and class n To load Iris dataset simple use: ``` +use Phpml\Dataset\Demo\Iris; + $dataset = new Iris(); ``` diff --git a/docs/machine-learning/datasets/demo/wine.md b/docs/machine-learning/datasets/demo/wine.md new file mode 100644 index 00000000..5b3f999b --- /dev/null +++ b/docs/machine-learning/datasets/demo/wine.md @@ -0,0 +1,35 @@ +# Wine Dataset + +These data are the results of a chemical analysis of wines grown in the same region in Italy but derived from three different cultivars. The analysis determined the quantities of 13 constituents found in each of the three types of wines. + +### Specification + +| Classes | 3 | +| Samples per class | class 1 59; class 2 71; class 3 48 | +| Samples total | 178 | +| Features per sample | 13 | + +### Load + +To load Wine dataset simple use: + +``` +use Phpml\Dataset\Demo\Wine; + +$dataset = new Wine(); +``` + +### Several samples example + +``` +alcohol,malic acid,ash,alcalinity of ash,magnesium,total phenols,flavanoids,nonflavanoid phenols,proanthocyanins,color intensity,hue,OD280/OD315 of diluted wines,proline,class +14.23,1.71,2.43,15.6,127,2.8,3.06,.28,2.29,5.64,1.04,3.92,1065,1 +13.2,1.78,2.14,11.2,100,2.65,2.76,.26,1.28,4.38,1.05,3.4,1050,1 +13.16,2.36,2.67,18.6,101,2.8,3.24,.3,2.81,5.68,1.03,3.17,1185,1 +14.37,1.95,2.5,16.8,113,3.85,3.49,.24,2.18,7.8,.86,3.45,1480,1 +13.24,2.59,2.87,21,118,2.8,2.69,.39,1.82,4.32,1.04,2.93,735,1 +14.2,1.76,2.45,15.2,112,3.27,3.39,.34,1.97,6.75,1.05,2.85,1450,1 +14.39,1.87,2.45,14.6,96,2.5,2.52,.3,1.98,5.25,1.02,3.58,1290,1 +14.06,2.15,2.61,17.6,121,2.6,2.51,.31,1.25,5.05,1.06,3.58,1295,1 +14.83,1.64,2.17,14,97,2.8,2.98,.29,1.98,5.2,1.08,2.85,1045,1 +``` diff --git a/docs/machine-learning/preprocessing/imputation-missing-values.md b/docs/machine-learning/preprocessing/imputation-missing-values.md index e69de29b..db64d8dc 100644 --- a/docs/machine-learning/preprocessing/imputation-missing-values.md +++ b/docs/machine-learning/preprocessing/imputation-missing-values.md @@ -0,0 +1 @@ +# Imputation missing values diff --git a/docs/machine-learning/preprocessing/normalization.md b/docs/machine-learning/preprocessing/normalization.md index e69de29b..a0dbc801 100644 --- a/docs/machine-learning/preprocessing/normalization.md +++ b/docs/machine-learning/preprocessing/normalization.md @@ -0,0 +1 @@ +# Normalization From 325427c72382dbe8c2ce6ba7490959f8ff6b9e35 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sat, 14 May 2016 21:30:13 +0200 Subject: [PATCH 019/328] update missing docs --- .../imputation-missing-values.md | 44 +++++++++++ .../preprocessing/normalization.md | 58 ++++++++++++++ docs/math/statistic.md | 79 ++++++++++++++++++- 3 files changed, 178 insertions(+), 3 deletions(-) diff --git a/docs/machine-learning/preprocessing/imputation-missing-values.md b/docs/machine-learning/preprocessing/imputation-missing-values.md index db64d8dc..186f4243 100644 --- a/docs/machine-learning/preprocessing/imputation-missing-values.md +++ b/docs/machine-learning/preprocessing/imputation-missing-values.md @@ -1 +1,45 @@ # Imputation missing values + +For various reasons, many real world datasets contain missing values, often encoded as blanks, NaNs or other placeholders. +To solve this problem you can use the `Imputer` class. + +## Constructor Parameters + +* $missingValue (mixed) - this value will be replaced (default null) +* $strategy (Strategy) - imputation strategy (read to use: MeanStrategy, MedianStrategy, MostFrequentStrategy) +* $axis (int) - axis for strategy, Imputer::AXIS_COLUMN or Imputer::AXIS_ROW + +``` +$imputer = new Imputer(null, new MeanStrategy(), Imputer::AXIS_COLUMN); +$imputer = new Imputer(null, new MedianStrategy(), Imputer::AXIS_ROW); +``` + +## Strategy + +* MeanStrategy - replace missing values using the mean along the axis +* MedianStrategy - replace missing values using the median along the axis +* MostFrequentStrategy - replace missing using the most frequent value along the axis + +## Example of use + +``` +$data = [ + [1, null, 3, 4], + [4, 3, 2, 1], + [null, 6, 7, 8], + [8, 7, null, 5], +]; + +$imputer = new Imputer(null, new MeanStrategy(), Imputer::AXIS_COLUMN); +$imputer->preprocess($data); + +/* +$data = [ + [1, 5.33, 3, 4], + [4, 3, 2, 1], + [4.33, 6, 7, 8], + [8, 7, 4, 5], +]; +*/ + +``` diff --git a/docs/machine-learning/preprocessing/normalization.md b/docs/machine-learning/preprocessing/normalization.md index a0dbc801..61b1a8d9 100644 --- a/docs/machine-learning/preprocessing/normalization.md +++ b/docs/machine-learning/preprocessing/normalization.md @@ -1 +1,59 @@ # Normalization + +Normalization is the process of scaling individual samples to have unit norm. + +## L2 norm + +[http://mathworld.wolfram.com/L2-Norm.html](http://mathworld.wolfram.com/L2-Norm.html) + +Example: + +``` +use Phpml\Preprocessing\Normalizer; + +$samples = [ + [1, -1, 2], + [2, 0, 0], + [0, 1, -1], +]; + +$normalizer = new Normalizer(); +$normalizer->preprocess($samples); + +/* +$samples = [ + [0.4, -0.4, 0.81], + [1.0, 0.0, 0.0], + [0.0, 0.7, -0.7], +]; +*/ + +``` + +## L1 norm + +[http://mathworld.wolfram.com/L1-Norm.html](http://mathworld.wolfram.com/L1-Norm.html) + +Example: + +``` +use Phpml\Preprocessing\Normalizer; + +$samples = [ + [1, -1, 2], + [2, 0, 0], + [0, 1, -1], +]; + +$normalizer = new Normalizer(Normalizer::NORM_L1); +$normalizer->preprocess($samples); + +/* +$samples = [ + [0.25, -0.25, 0.5], + [1.0, 0.0, 0.0], + [0.0, 0.5, -0.5], +]; +*/ + +``` diff --git a/docs/math/statistic.md b/docs/math/statistic.md index 89cc00eb..626828e9 100644 --- a/docs/math/statistic.md +++ b/docs/math/statistic.md @@ -1,7 +1,80 @@ # Statistic -### Correlation +Selected statistical methods. -### Mean +## Correlation -### Standard Deviation +Correlation coefficients are used in statistics to measure how strong a relationship is between two variables. There are several types of correlation coefficient. + +### Pearson correlation + +Pearson’s correlation or Pearson correlation is a correlation coefficient commonly used in linear regression. + +Example: + +``` +use Phpml\Math\Statistic\Correlation; + +$x = [43, 21, 25, 42, 57, 59]; +$y = [99, 65, 79, 75, 87, 82]; + +Correlation::pearson($x, $y); +// return 0.549 +``` + +## Mean + +### Arithmetic + +Example: + +``` +use Phpml\Math\Statistic\Mean; + +Mean::arithmetic([2, 5]; +// return 3.5 + +Mean::arithmetic([0.5, 0.5, 1.5, 2.5, 3.5]; +// return 1.7 +``` + +## Median + +Example: + +``` +use Phpml\Math\Statistic\Mean; + +Mean::median([5, 2, 6, 1, 3, 4]); +// return 3.5 + +Mean::median([5, 2, 6, 1, 3]); +// return 3 +``` + +## Mode + +Example: + +``` +use Phpml\Math\Statistic\Mean; + +Mean::mode([5, 2, 6, 1, 3, 4, 6, 6, 5]); +// return 6 +``` + +## Standard Deviation + +Example: + +``` +use Phpml\Math\Statistic\StandardDeviation; + +$population = [5, 6, 8, 9]; +StandardDeviation::population($population) +// return 1.825 + +$population = [7100, 15500, 4400, 4400, 5900, 4600, 8800, 2000, 2750, 2550, 960, 1025]; +StandardDeviation::population($population) +// return 4079 +``` From 7ab1ae97de8863ff9a8a0269a9ed6b3fd70714bc Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sat, 14 May 2016 21:50:48 +0200 Subject: [PATCH 020/328] update readthedocs menu --- README.md | 16 +++++++++------- docs/index.md | 16 +++++++++------- mkdocs.yml | 10 ++++++++-- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index da958398..db3c32bc 100644 --- a/README.md +++ b/README.md @@ -44,17 +44,19 @@ composer require php-ai/php-ml * [Least Squares](http://php-ml.readthedocs.io/en/latest/machine-learning/regression/least-squares/) * [SVR](http://php-ml.readthedocs.io/en/latest/machine-learning/regression/svr/) * Clustering - * [k-Means](http://php-ml.readthedocs.io/en/latest/machine-learning/clustering/k-means) - * [DBSCAN](http://php-ml.readthedocs.io/en/latest/machine-learning/clustering/dbscan) + * [k-Means](http://php-ml.readthedocs.io/en/latest/machine-learning/clustering/k-means/) + * [DBSCAN](http://php-ml.readthedocs.io/en/latest/machine-learning/clustering/dbscan/) +* Metric + * [Accuracy](http://php-ml.readthedocs.io/en/latest/machine-learning/metric/accuracy/) * Cross Validation - * [Random Split](http://php-ml.readthedocs.io/en/latest/machine-learning/cross-validation/random-split) + * [Random Split](http://php-ml.readthedocs.io/en/latest/machine-learning/cross-validation/random-split/) * Preprocessing - * [Normalization](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/normalization) - * [Imputation missing values](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/imputation-missing-values) + * [Normalization](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/normalization/) + * [Imputation missing values](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/imputation-missing-values/) * Feature Extraction - * [Token Count Vectorizer](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-extraction/token-count-vectorizer) + * [Token Count Vectorizer](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-extraction/token-count-vectorizer/) * Datasets - * [CSV](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/csv-dataset) + * [CSV](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/csv-dataset/) * Ready to use: * [Iris](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/demo/iris/) * [Wine](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/demo/wine/) diff --git a/docs/index.md b/docs/index.md index da958398..db3c32bc 100644 --- a/docs/index.md +++ b/docs/index.md @@ -44,17 +44,19 @@ composer require php-ai/php-ml * [Least Squares](http://php-ml.readthedocs.io/en/latest/machine-learning/regression/least-squares/) * [SVR](http://php-ml.readthedocs.io/en/latest/machine-learning/regression/svr/) * Clustering - * [k-Means](http://php-ml.readthedocs.io/en/latest/machine-learning/clustering/k-means) - * [DBSCAN](http://php-ml.readthedocs.io/en/latest/machine-learning/clustering/dbscan) + * [k-Means](http://php-ml.readthedocs.io/en/latest/machine-learning/clustering/k-means/) + * [DBSCAN](http://php-ml.readthedocs.io/en/latest/machine-learning/clustering/dbscan/) +* Metric + * [Accuracy](http://php-ml.readthedocs.io/en/latest/machine-learning/metric/accuracy/) * Cross Validation - * [Random Split](http://php-ml.readthedocs.io/en/latest/machine-learning/cross-validation/random-split) + * [Random Split](http://php-ml.readthedocs.io/en/latest/machine-learning/cross-validation/random-split/) * Preprocessing - * [Normalization](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/normalization) - * [Imputation missing values](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/imputation-missing-values) + * [Normalization](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/normalization/) + * [Imputation missing values](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/imputation-missing-values/) * Feature Extraction - * [Token Count Vectorizer](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-extraction/token-count-vectorizer) + * [Token Count Vectorizer](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-extraction/token-count-vectorizer/) * Datasets - * [CSV](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/csv-dataset) + * [CSV](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/csv-dataset/) * Ready to use: * [Iris](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/demo/iris/) * [Wine](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/demo/wine/) diff --git a/mkdocs.yml b/mkdocs.yml index f833fc37..68e8b974 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -12,8 +12,13 @@ pages: - Clustering: - KMeans: machine-learning/clustering/k-means.md - DBSCAN: machine-learning/clustering/dbscan.md + - Metric: + - Accuracy: machine-learning/metric/accuracy.md - Cross Validation: - RandomSplit: machine-learning/cross-validation/random-split.md + - Preprocessing: + - Normalization: machine-learning/preprocessing/normalization.md + - Imputation missing values: machine-learning/preprocessing/imputation-missing-values.md - Feature Extraction: - Token Count Vectorizer: machine-learning/feature-extraction/token-count-vectorizer.md - Datasets: @@ -21,9 +26,10 @@ pages: - CSV Dataset: machine-learning/datasets/csv-dataset.md - Ready to use datasets: - Iris: machine-learning/datasets/demo/iris.md - - Metric: - - Accuracy: machine-learning/metric/accuracy.md + - Wine: machine-learning/datasets/demo/wine.md + - Glass: machine-learning/datasets/demo/glass.md - Math: - Distance: math/distance.md - Matrix: math/matrix.md + - Statistic: math/statistic.md theme: readthedocs From d19490d62abde1d8297c343f4c35af51f5b322eb Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 31 May 2016 18:02:30 +0200 Subject: [PATCH 021/328] update docs example --- .../preprocessing/imputation-missing-values.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/machine-learning/preprocessing/imputation-missing-values.md b/docs/machine-learning/preprocessing/imputation-missing-values.md index 186f4243..5bbefec3 100644 --- a/docs/machine-learning/preprocessing/imputation-missing-values.md +++ b/docs/machine-learning/preprocessing/imputation-missing-values.md @@ -23,6 +23,9 @@ $imputer = new Imputer(null, new MedianStrategy(), Imputer::AXIS_ROW); ## Example of use ``` +use Phpml\Preprocessing\Imputer; +use Phpml\Preprocessing\Imputer\Strategy\MeanStrategy; + $data = [ [1, null, 3, 4], [4, 3, 2, 1], From 23eff0044aff427f7c48eb019a5f9006d1ee35af Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 31 May 2016 20:01:54 +0200 Subject: [PATCH 022/328] add test with dataset example --- tests/Phpml/Metric/AccuracyTest.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/Phpml/Metric/AccuracyTest.php b/tests/Phpml/Metric/AccuracyTest.php index aa68b223..1e71fcff 100644 --- a/tests/Phpml/Metric/AccuracyTest.php +++ b/tests/Phpml/Metric/AccuracyTest.php @@ -4,7 +4,11 @@ namespace tests\Phpml\Metric; +use Phpml\Classification\SVC; +use Phpml\CrossValidation\RandomSplit; +use Phpml\Dataset\Demo\Iris; use Phpml\Metric\Accuracy; +use Phpml\SupportVectorMachine\Kernel; class AccuracyTest extends \PHPUnit_Framework_TestCase { @@ -34,4 +38,19 @@ public function testCalculateNotNormalizedScore() $this->assertEquals(3, Accuracy::score($actualLabels, $predictedLabels, false)); } + + public function testAccuracyOnDemoDataset() + { + $dataset = new RandomSplit(new Iris(), 0.5, 123); + + $classifier = new SVC(Kernel::RBF); + $classifier->train($dataset->getTrainSamples(), $dataset->getTrainLabels()); + + $predicted = $classifier->predict($dataset->getTestSamples()); + + $accuracy = Accuracy::score($dataset->getTestLabels(), $predicted); + + $this->assertEquals(0.959, $accuracy, '', 0.01); + } + } From 2f5171638805bc183e872726e87029a6b8959e7a Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 14 Jun 2016 09:58:11 +0200 Subject: [PATCH 023/328] change token count vectorizer to return full token counts --- .../TokenCountVectorizer.php | 56 +++++++++++++---- .../TokenCountVectorizerTest.php | 61 ++++++++++++------- tests/Phpml/Metric/AccuracyTest.php | 1 - 3 files changed, 85 insertions(+), 33 deletions(-) diff --git a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php index 14fc69cd..cde5278b 100644 --- a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php +++ b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php @@ -23,6 +23,11 @@ class TokenCountVectorizer implements Vectorizer */ private $vocabulary; + /** + * @var array + */ + private $tokens; + /** * @var array */ @@ -47,8 +52,10 @@ public function __construct(Tokenizer $tokenizer, float $minDF = 0) */ public function transform(array $samples): array { + $this->buildVocabulary($samples); + foreach ($samples as $index => $sample) { - $samples[$index] = $this->transformSample($sample); + $samples[$index] = $this->transformSample($index); } $samples = $this->checkDocumentFrequency($samples); @@ -65,14 +72,29 @@ public function getVocabulary() } /** - * @param string $sample + * @param array $samples + */ + private function buildVocabulary(array &$samples) + { + foreach ($samples as $index => $sample) { + $tokens = $this->tokenizer->tokenize($sample); + foreach ($tokens as $token) { + $this->addTokenToVocabulary($token); + } + $this->tokens[$index] = $tokens; + } + } + + /** + * @param int $index * * @return array */ - private function transformSample(string $sample) + private function transformSample(int $index) { $counts = []; - $tokens = $this->tokenizer->tokenize($sample); + $tokens = $this->tokens[$index]; + foreach ($tokens as $token) { $index = $this->getTokenIndex($token); $this->updateFrequency($token); @@ -83,21 +105,33 @@ private function transformSample(string $sample) ++$counts[$index]; } + foreach ($this->vocabulary as $index) { + if (!isset($counts[$index])) { + $counts[$index] = 0; + } + } + return $counts; } /** * @param string $token * - * @return mixed + * @return int + */ + private function getTokenIndex(string $token): int + { + return $this->vocabulary[$token]; + } + + /** + * @param string $token */ - private function getTokenIndex(string $token) + private function addTokenToVocabulary(string $token) { if (!isset($this->vocabulary[$token])) { $this->vocabulary[$token] = count($this->vocabulary); } - - return $this->vocabulary[$token]; } /** @@ -122,7 +156,7 @@ private function checkDocumentFrequency(array $samples) if ($this->minDF > 0) { $beyondMinimum = $this->getBeyondMinimumIndexes(count($samples)); foreach ($samples as $index => $sample) { - $samples[$index] = $this->unsetBeyondMinimum($sample, $beyondMinimum); + $samples[$index] = $this->resetBeyondMinimum($sample, $beyondMinimum); } } @@ -135,10 +169,10 @@ private function checkDocumentFrequency(array $samples) * * @return array */ - private function unsetBeyondMinimum(array $sample, array $beyondMinimum) + private function resetBeyondMinimum(array $sample, array $beyondMinimum) { foreach ($beyondMinimum as $index) { - unset($sample[$index]); + $sample[$index] = 0; } return $sample; diff --git a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php index 64ac569a..5166575c 100644 --- a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php +++ b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php @@ -17,16 +17,28 @@ public function testTokenCountVectorizerWithWhitespaceTokenizer() 'Mauris diam eros fringilla diam', ]; - $vocabulary = ['Lorem', 'ipsum', 'dolor', 'sit', 'amet', 'Mauris', 'placerat', 'diam', 'eros', 'fringilla']; - $vector = [ - [0 => 1, 1 => 1, 2 => 2, 3 => 1, 4 => 1], - [5 => 1, 6 => 1, 1 => 1, 2 => 1], - [5 => 1, 7 => 2, 8 => 1, 9 => 1], + $vocabulary = [ + 0 => 'Lorem', + 1 => 'ipsum', + 2 => 'dolor', + 3 => 'sit', + 4 => 'amet', + 5 => 'Mauris', + 6 => 'placerat', + 7 => 'diam', + 8 => 'eros', + 9 => 'fringilla', + ]; + + $tokensCounts = [ + [0 => 1, 1 => 1, 2 => 2, 3 => 1, 4 => 1, 5 => 0, 6 => 0, 7 => 0, 8 => 0, 9 => 0], + [0 => 0, 1 => 1, 2 => 1, 3 => 0, 4 => 0, 5 => 1, 6 => 1, 7 => 0, 8 => 0, 9 => 0], + [0 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 1, 6 => 0, 7 => 2, 8 => 1, 9 => 1], ]; $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer()); - $this->assertEquals($vector, $vectorizer->transform($samples)); + $this->assertEquals($tokensCounts, $vectorizer->transform($samples)); $this->assertEquals($vocabulary, $vectorizer->getVocabulary()); } @@ -40,34 +52,41 @@ public function testMinimumDocumentTokenCountFrequency() 'ipsum sit amet', ]; - $vocabulary = ['Lorem', 'ipsum', 'dolor', 'sit', 'amet']; - $vector = [ - [0 => 1, 1 => 1, 3 => 1, 4 => 1], - [0 => 1, 1 => 1, 3 => 1, 4 => 1], - [1 => 1, 3 => 1, 4 => 1], - [1 => 1, 3 => 1, 4 => 1], + $vocabulary = [ + 0 => 'Lorem', + 1 => 'ipsum', + 2 => 'dolor', + 3 => 'sit', + 4 => 'amet', + ]; + + $tokensCounts = [ + [0 => 1, 1 => 1, 2 => 0, 3 => 1, 4 => 1], + [0 => 1, 1 => 1, 2 => 0, 3 => 1, 4 => 1], + [0 => 0, 1 => 1, 2 => 0, 3 => 1, 4 => 1], + [0 => 0, 1 => 1, 2 => 0, 3 => 1, 4 => 1], ]; $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), 0.5); - $this->assertEquals($vector, $vectorizer->transform($samples)); + $this->assertEquals($tokensCounts, $vectorizer->transform($samples)); $this->assertEquals($vocabulary, $vectorizer->getVocabulary()); - // word at least in all samples + // word at least once in all samples $samples = [ 'Lorem ipsum dolor sit amet', - 'Morbi quis lacinia arcu. Sed eu sagittis Lorem', - 'Suspendisse gravida consequat eros Lorem', + 'Morbi quis sagittis Lorem', + 'eros Lorem', ]; - $vector = [ - [0 => 1], - [0 => 1], - [0 => 1], + $tokensCounts = [ + [0 => 1, 1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0, 7 => 0, 8 => 0], + [0 => 1, 1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0, 7 => 0, 8 => 0], + [0 => 1, 1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0, 7 => 0, 8 => 0], ]; $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), 1); - $this->assertEquals($vector, $vectorizer->transform($samples)); + $this->assertEquals($tokensCounts, $vectorizer->transform($samples)); } } diff --git a/tests/Phpml/Metric/AccuracyTest.php b/tests/Phpml/Metric/AccuracyTest.php index 1e71fcff..6f28d946 100644 --- a/tests/Phpml/Metric/AccuracyTest.php +++ b/tests/Phpml/Metric/AccuracyTest.php @@ -52,5 +52,4 @@ public function testAccuracyOnDemoDataset() $this->assertEquals(0.959, $accuracy, '', 0.01); } - } From 1ac4b44ee46b15b04fd036ee4c7f16a38e8e2af1 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 14 Jun 2016 11:53:58 +0200 Subject: [PATCH 024/328] create stop words class --- src/Phpml/Exception/InvalidArgumentException.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Phpml/Exception/InvalidArgumentException.php b/src/Phpml/Exception/InvalidArgumentException.php index 45d532e8..798532de 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -65,4 +65,12 @@ public static function invalidClustersNumber() { return new self('Invalid clusters number'); } + + /** + * @return InvalidArgumentException + */ + public static function invalidStopWordsLanguage(string $language) + { + return new self(sprintf('Can\'t find %s language for StopWords', $language)); + } } From da6d94cc466ff19a5e53b81e8f3d6c07ca0a2d71 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 14 Jun 2016 11:54:04 +0200 Subject: [PATCH 025/328] create stop words class --- src/Phpml/FeatureExtraction/StopWords.php | 51 +++++++++++++++++++ .../FeatureExtraction/StopWords/English.php | 33 ++++++++++++ .../FeatureExtraction/StopWords/Polish.php | 30 +++++++++++ .../Phpml/FeatureExtraction/StopWordsTest.php | 47 +++++++++++++++++ 4 files changed, 161 insertions(+) create mode 100644 src/Phpml/FeatureExtraction/StopWords.php create mode 100644 src/Phpml/FeatureExtraction/StopWords/English.php create mode 100644 src/Phpml/FeatureExtraction/StopWords/Polish.php create mode 100644 tests/Phpml/FeatureExtraction/StopWordsTest.php diff --git a/src/Phpml/FeatureExtraction/StopWords.php b/src/Phpml/FeatureExtraction/StopWords.php new file mode 100644 index 00000000..cb4ccc8a --- /dev/null +++ b/src/Phpml/FeatureExtraction/StopWords.php @@ -0,0 +1,51 @@ +stopWords = array_fill_keys($stopWords, true); + } + + /** + * @param string $token + * + * @return bool + */ + public function isStopWord(string $token): bool + { + return isset($this->stopWords[$token]); + } + + /** + * @param string $language + * + * @return StopWords + * + * @throws InvalidArgumentException + */ + public static function factory($language = 'English'): StopWords + { + $className = __NAMESPACE__."\\StopWords\\$language"; + + if (!class_exists($className)) { + throw InvalidArgumentException::invalidStopWordsLanguage($language); + } + + return new $className(); + } +} diff --git a/src/Phpml/FeatureExtraction/StopWords/English.php b/src/Phpml/FeatureExtraction/StopWords/English.php new file mode 100644 index 00000000..841ef376 --- /dev/null +++ b/src/Phpml/FeatureExtraction/StopWords/English.php @@ -0,0 +1,33 @@ +stopWords); + } +} diff --git a/src/Phpml/FeatureExtraction/StopWords/Polish.php b/src/Phpml/FeatureExtraction/StopWords/Polish.php new file mode 100644 index 00000000..f5913847 --- /dev/null +++ b/src/Phpml/FeatureExtraction/StopWords/Polish.php @@ -0,0 +1,30 @@ +stopWords); + } +} diff --git a/tests/Phpml/FeatureExtraction/StopWordsTest.php b/tests/Phpml/FeatureExtraction/StopWordsTest.php new file mode 100644 index 00000000..dfb58771 --- /dev/null +++ b/tests/Phpml/FeatureExtraction/StopWordsTest.php @@ -0,0 +1,47 @@ +assertTrue($stopWords->isStopWord('lorem')); + $this->assertTrue($stopWords->isStopWord('ipsum')); + $this->assertTrue($stopWords->isStopWord('dolor')); + + $this->assertFalse($stopWords->isStopWord('consectetur')); + $this->assertFalse($stopWords->isStopWord('adipiscing')); + $this->assertFalse($stopWords->isStopWord('amet')); + } + + /** + * @expectedException \Phpml\Exception\InvalidArgumentException + */ + public function testThrowExceptionOnInvalidLanguage() + { + StopWords::factory('Lorem'); + } + + public function testEnglishStopWords() + { + $stopWords = StopWords::factory('English'); + + $this->assertTrue($stopWords->isStopWord('again')); + $this->assertFalse($stopWords->isStopWord('strategy')); + } + + public function testPolishStopWords() + { + $stopWords = StopWords::factory('Polish'); + + $this->assertTrue($stopWords->isStopWord('wam')); + $this->assertFalse($stopWords->isStopWord('transhumanizm')); + } +} From 8a6502664224be4232bc674b3b29178d0a9632e8 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 15 Jun 2016 14:09:49 +0200 Subject: [PATCH 026/328] rename interface Vectorizer to Transformer --- src/Phpml/FeatureExtraction/TokenCountVectorizer.php | 2 +- src/Phpml/FeatureExtraction/{Vectorizer.php => Transformer.php} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/Phpml/FeatureExtraction/{Vectorizer.php => Transformer.php} (90%) diff --git a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php index cde5278b..95ecc33f 100644 --- a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php +++ b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php @@ -6,7 +6,7 @@ use Phpml\Tokenization\Tokenizer; -class TokenCountVectorizer implements Vectorizer +class TokenCountVectorizer implements Transformer { /** * @var Tokenizer diff --git a/src/Phpml/FeatureExtraction/Vectorizer.php b/src/Phpml/FeatureExtraction/Transformer.php similarity index 90% rename from src/Phpml/FeatureExtraction/Vectorizer.php rename to src/Phpml/FeatureExtraction/Transformer.php index 04a8bea6..13eb9557 100644 --- a/src/Phpml/FeatureExtraction/Vectorizer.php +++ b/src/Phpml/FeatureExtraction/Transformer.php @@ -4,7 +4,7 @@ namespace Phpml\FeatureExtraction; -interface Vectorizer +interface Transformer { /** * @param array $samples From cc50d2c9b154ec7cdf751ca3ac6f622eeee9c8ec Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 15 Jun 2016 16:04:09 +0200 Subject: [PATCH 027/328] implement TfIdf transformation --- README.md | 2 +- .../FeatureExtraction/TfIdfTransformer.php | 54 +++++++++++++++++++ .../TfIdfTransformerTest.php | 29 ++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 src/Phpml/FeatureExtraction/TfIdfTransformer.php create mode 100644 tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php diff --git a/README.md b/README.md index db3c32bc..c10cb7b5 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![License](https://poser.pugx.org/php-ai/php-ml/license.svg)](https://packagist.org/packages/php-ai/php-ml) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/quality-score.png?b=develop)](https://scrutinizer-ci.com/g/php-ai/php-ml/?branch=develop) -Fresh approach to Machine Learning in PHP. Note that at the moment PHP is not the best choice for machine learning but maybe this will change ... +Fresh approach to Machine Learning in PHP. Algorithms, Cross Validation, Preprocessing, Feature Extraction and much more in one library. Simple example of classification: ```php diff --git a/src/Phpml/FeatureExtraction/TfIdfTransformer.php b/src/Phpml/FeatureExtraction/TfIdfTransformer.php new file mode 100644 index 00000000..152919e3 --- /dev/null +++ b/src/Phpml/FeatureExtraction/TfIdfTransformer.php @@ -0,0 +1,54 @@ +countTokensFrequency($samples); + + $count = count($samples); + foreach ($this->idf as &$value) { + $value = log($count / $value, 10); + } + + foreach ($samples as &$sample) { + foreach ($sample as $index => &$feature) { + $feature = $feature * $this->idf[$index]; + } + } + + return $samples; + } + + /** + * @param array $samples + * + * @return array + */ + private function countTokensFrequency(array $samples) + { + $this->idf = array_fill_keys(array_keys($samples[0]), 0); + + foreach ($samples as $sample) { + foreach ($sample as $index => $count) { + if ($count > 0) { + ++$this->idf[$index]; + } + } + } + } +} diff --git a/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php b/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php new file mode 100644 index 00000000..59d96c08 --- /dev/null +++ b/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php @@ -0,0 +1,29 @@ + 1, 1 => 1, 2 => 2, 3 => 1, 4 => 0, 5 => 0], + [0 => 1, 1 => 1, 2 => 0, 3 => 0, 4 => 2, 5 => 3], + ]; + + $tfIdfSamples = [ + [0 => 0, 1 => 0, 2 => 0.602, 3 => 0.301, 4 => 0, 5 => 0], + [0 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 0.602, 5 => 0.903], + ]; + + $transformer = new TfIdfTransformer(); + + $this->assertEquals($tfIdfSamples, $transformer->transform($samples), '', 0.001); + } +} From cab79e7e36918ce9b90427acb0f82e8721fa9299 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 16 Jun 2016 09:00:10 +0200 Subject: [PATCH 028/328] change interfaces and add Estimator and Pipeline --- src/Phpml/Classification/Classifier.php | 16 ++-------- src/Phpml/Estimator.php | 21 ++++++++++++++ .../FeatureExtraction/TfIdfTransformer.php | 2 ++ .../TokenCountVectorizer.php | 1 + src/Phpml/Pipeline.php | 29 +++++++++++++++++++ src/Phpml/Regression/Regression.php | 16 ++-------- .../{FeatureExtraction => }/Transformer.php | 2 +- 7 files changed, 60 insertions(+), 27 deletions(-) create mode 100644 src/Phpml/Estimator.php create mode 100644 src/Phpml/Pipeline.php rename src/Phpml/{FeatureExtraction => }/Transformer.php (84%) diff --git a/src/Phpml/Classification/Classifier.php b/src/Phpml/Classification/Classifier.php index 00e6779d..14779043 100644 --- a/src/Phpml/Classification/Classifier.php +++ b/src/Phpml/Classification/Classifier.php @@ -4,18 +4,8 @@ namespace Phpml\Classification; -interface Classifier -{ - /** - * @param array $samples - * @param array $labels - */ - public function train(array $samples, array $labels); +use Phpml\Estimator; - /** - * @param array $samples - * - * @return mixed - */ - public function predict(array $samples); +interface Classifier extends Estimator +{ } diff --git a/src/Phpml/Estimator.php b/src/Phpml/Estimator.php new file mode 100644 index 00000000..06316792 --- /dev/null +++ b/src/Phpml/Estimator.php @@ -0,0 +1,21 @@ +stages = $stages; + } + + /** + * @param mixed $stage + */ + public function addStage($stage) + { + $this->stages[] = $stage; + } +} diff --git a/src/Phpml/Regression/Regression.php b/src/Phpml/Regression/Regression.php index 12d2f528..27a1b83c 100644 --- a/src/Phpml/Regression/Regression.php +++ b/src/Phpml/Regression/Regression.php @@ -4,18 +4,8 @@ namespace Phpml\Regression; -interface Regression -{ - /** - * @param array $samples - * @param array $targets - */ - public function train(array $samples, array $targets); +use Phpml\Estimator; - /** - * @param array $samples - * - * @return mixed - */ - public function predict(array $samples); +interface Regression extends Estimator +{ } diff --git a/src/Phpml/FeatureExtraction/Transformer.php b/src/Phpml/Transformer.php similarity index 84% rename from src/Phpml/FeatureExtraction/Transformer.php rename to src/Phpml/Transformer.php index 13eb9557..bdc809b5 100644 --- a/src/Phpml/FeatureExtraction/Transformer.php +++ b/src/Phpml/Transformer.php @@ -2,7 +2,7 @@ declare (strict_types = 1); -namespace Phpml\FeatureExtraction; +namespace Phpml; interface Transformer { From 374182a6d4317305f79b90d2b737702b91c8abce Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 16 Jun 2016 09:58:12 +0200 Subject: [PATCH 029/328] simple pipeline test --- .../Classification/KNearestNeighbors.php | 6 +- src/Phpml/Classification/NaiveBayes.php | 2 +- src/Phpml/Helper/Trainable.php | 8 +- src/Phpml/Pipeline.php | 77 ++++++++++++++++--- 4 files changed, 75 insertions(+), 18 deletions(-) diff --git a/src/Phpml/Classification/KNearestNeighbors.php b/src/Phpml/Classification/KNearestNeighbors.php index f1a87cf1..95ebeafb 100644 --- a/src/Phpml/Classification/KNearestNeighbors.php +++ b/src/Phpml/Classification/KNearestNeighbors.php @@ -35,7 +35,7 @@ public function __construct(int $k = 3, Distance $distanceMetric = null) $this->k = $k; $this->samples = []; - $this->labels = []; + $this->targets = []; $this->distanceMetric = $distanceMetric; } @@ -48,10 +48,10 @@ protected function predictSample(array $sample) { $distances = $this->kNeighborsDistances($sample); - $predictions = array_combine(array_values($this->labels), array_fill(0, count($this->labels), 0)); + $predictions = array_combine(array_values($this->targets), array_fill(0, count($this->targets), 0)); foreach ($distances as $index => $distance) { - ++$predictions[$this->labels[$index]]; + ++$predictions[$this->targets[$index]]; } arsort($predictions); diff --git a/src/Phpml/Classification/NaiveBayes.php b/src/Phpml/Classification/NaiveBayes.php index 9726b405..3e7d819a 100644 --- a/src/Phpml/Classification/NaiveBayes.php +++ b/src/Phpml/Classification/NaiveBayes.php @@ -19,7 +19,7 @@ class NaiveBayes implements Classifier protected function predictSample(array $sample) { $predictions = []; - foreach ($this->labels as $index => $label) { + foreach ($this->targets as $index => $label) { $predictions[$label] = 0; foreach ($sample as $token => $count) { if (array_key_exists($token, $this->samples[$index])) { diff --git a/src/Phpml/Helper/Trainable.php b/src/Phpml/Helper/Trainable.php index 36b8993e..fda27d55 100644 --- a/src/Phpml/Helper/Trainable.php +++ b/src/Phpml/Helper/Trainable.php @@ -14,15 +14,15 @@ trait Trainable /** * @var array */ - private $labels; + private $targets; /** * @param array $samples - * @param array $labels + * @param array $targets */ - public function train(array $samples, array $labels) + public function train(array $samples, array $targets) { $this->samples = $samples; - $this->labels = $labels; + $this->targets = $targets; } } diff --git a/src/Phpml/Pipeline.php b/src/Phpml/Pipeline.php index 2ac0ed08..f0017fea 100644 --- a/src/Phpml/Pipeline.php +++ b/src/Phpml/Pipeline.php @@ -2,28 +2,85 @@ declare (strict_types = 1); -namespace Phpml\Pipeline; +namespace Phpml; -class Pipeline +class Pipeline implements Estimator { /** - * @var array + * @var array|Transformer[] */ - private $stages; + private $transformers; /** - * @param array $stages + * @var Estimator */ - public function __construct(array $stages) + private $estimator; + + /** + * @param array|Transformer[] $transformers + * @param Estimator $estimator + */ + public function __construct(array $transformers = [], Estimator $estimator) + { + foreach ($transformers as $transformer) { + $this->addTransformer($transformer); + } + + $this->estimator = $estimator; + } + + /** + * @param Transformer $transformer + */ + public function addTransformer(Transformer $transformer) + { + $this->transformers[] = $transformer; + } + + /** + * @param Estimator $estimator + */ + public function setEstimator(Estimator $estimator) { - $this->stages = $stages; + $this->estimator = $estimator; } /** - * @param mixed $stage + * @return array|Transformer[] */ - public function addStage($stage) + public function getTransformers() { - $this->stages[] = $stage; + return $this->transformers; } + + /** + * @return Estimator + */ + public function getEstimator() + { + return $this->estimator; + } + + /** + * @param array $samples + * @param array $targets + */ + public function train(array $samples, array $targets) + { + foreach ($this->transformers as $transformer) { + $samples = $transformer->transform($samples); + } + + $this->estimator->train($samples, $targets); + } + + /** + * @param array $samples + * @return mixed + */ + public function predict(array $samples) + { + return $this->estimator->predict($samples); + } + } From 15519ba12201cf3eca6a2c8e151c895a248262d6 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 16 Jun 2016 09:58:17 +0200 Subject: [PATCH 030/328] simple pipeline test --- tests/Phpml/PipelineTest.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/Phpml/PipelineTest.php diff --git a/tests/Phpml/PipelineTest.php b/tests/Phpml/PipelineTest.php new file mode 100644 index 00000000..270fb894 --- /dev/null +++ b/tests/Phpml/PipelineTest.php @@ -0,0 +1,27 @@ +assertEquals($transformers, $pipeline->getTransformers()); + $this->assertEquals($estimator, $pipeline->getEstimator()); + } + +} From 7c5e79d2c6f777821fceb3cd7bcfd2bdd4b87037 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 16 Jun 2016 10:01:40 +0200 Subject: [PATCH 031/328] change transformer behavior to reference --- src/Phpml/FeatureExtraction/TfIdfTransformer.php | 6 +----- src/Phpml/FeatureExtraction/TokenCountVectorizer.php | 6 +----- src/Phpml/Pipeline.php | 4 ++-- src/Phpml/Transformer.php | 4 +--- tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php | 3 ++- .../Phpml/FeatureExtraction/TokenCountVectorizerTest.php | 9 ++++++--- tests/Phpml/PipelineTest.php | 4 +--- 7 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/Phpml/FeatureExtraction/TfIdfTransformer.php b/src/Phpml/FeatureExtraction/TfIdfTransformer.php index fade5b4a..783bd468 100644 --- a/src/Phpml/FeatureExtraction/TfIdfTransformer.php +++ b/src/Phpml/FeatureExtraction/TfIdfTransformer.php @@ -15,10 +15,8 @@ class TfIdfTransformer implements Transformer /** * @param array $samples - * - * @return array */ - public function transform(array $samples): array + public function transform(array &$samples) { $this->countTokensFrequency($samples); @@ -32,8 +30,6 @@ public function transform(array $samples): array $feature = $feature * $this->idf[$index]; } } - - return $samples; } /** diff --git a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php index 5273f6b2..f8237789 100644 --- a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php +++ b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php @@ -48,10 +48,8 @@ public function __construct(Tokenizer $tokenizer, float $minDF = 0) /** * @param array $samples - * - * @return array */ - public function transform(array $samples): array + public function transform(array &$samples) { $this->buildVocabulary($samples); @@ -60,8 +58,6 @@ public function transform(array $samples): array } $samples = $this->checkDocumentFrequency($samples); - - return $samples; } /** diff --git a/src/Phpml/Pipeline.php b/src/Phpml/Pipeline.php index f0017fea..3f98f29d 100644 --- a/src/Phpml/Pipeline.php +++ b/src/Phpml/Pipeline.php @@ -18,7 +18,7 @@ class Pipeline implements Estimator /** * @param array|Transformer[] $transformers - * @param Estimator $estimator + * @param Estimator $estimator */ public function __construct(array $transformers = [], Estimator $estimator) { @@ -76,11 +76,11 @@ public function train(array $samples, array $targets) /** * @param array $samples + * * @return mixed */ public function predict(array $samples) { return $this->estimator->predict($samples); } - } diff --git a/src/Phpml/Transformer.php b/src/Phpml/Transformer.php index bdc809b5..47c2ce3f 100644 --- a/src/Phpml/Transformer.php +++ b/src/Phpml/Transformer.php @@ -8,8 +8,6 @@ interface Transformer { /** * @param array $samples - * - * @return array */ - public function transform(array $samples): array; + public function transform(array &$samples); } diff --git a/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php b/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php index 59d96c08..ca5db360 100644 --- a/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php +++ b/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php @@ -23,7 +23,8 @@ public function testTfIdfTransformation() ]; $transformer = new TfIdfTransformer(); + $transformer->transform($samples); - $this->assertEquals($tfIdfSamples, $transformer->transform($samples), '', 0.001); + $this->assertEquals($tfIdfSamples, $samples, '', 0.001); } } diff --git a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php index 5166575c..80b77231 100644 --- a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php +++ b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php @@ -37,8 +37,9 @@ public function testTokenCountVectorizerWithWhitespaceTokenizer() ]; $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer()); + $vectorizer->transform($samples); - $this->assertEquals($tokensCounts, $vectorizer->transform($samples)); + $this->assertEquals($tokensCounts, $samples); $this->assertEquals($vocabulary, $vectorizer->getVocabulary()); } @@ -68,8 +69,9 @@ public function testMinimumDocumentTokenCountFrequency() ]; $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), 0.5); + $vectorizer->transform($samples); - $this->assertEquals($tokensCounts, $vectorizer->transform($samples)); + $this->assertEquals($tokensCounts, $samples); $this->assertEquals($vocabulary, $vectorizer->getVocabulary()); // word at least once in all samples @@ -86,7 +88,8 @@ public function testMinimumDocumentTokenCountFrequency() ]; $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), 1); + $vectorizer->transform($samples); - $this->assertEquals($tokensCounts, $vectorizer->transform($samples)); + $this->assertEquals($tokensCounts, $samples); } } diff --git a/tests/Phpml/PipelineTest.php b/tests/Phpml/PipelineTest.php index 270fb894..b00a60aa 100644 --- a/tests/Phpml/PipelineTest.php +++ b/tests/Phpml/PipelineTest.php @@ -10,11 +10,10 @@ class PipelineTest extends \PHPUnit_Framework_TestCase { - public function testPipelineConstruction() { $transformers = [ - new TfIdfTransformer() + new TfIdfTransformer(), ]; $estimator = new SVC(); @@ -23,5 +22,4 @@ public function testPipelineConstruction() $this->assertEquals($transformers, $pipeline->getTransformers()); $this->assertEquals($estimator, $pipeline->getEstimator()); } - } From d21a40136524c8607df4ed34ba9ed8ed2d0f7a4d Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 16 Jun 2016 10:03:57 +0200 Subject: [PATCH 032/328] implement Tranformer interface on preprocessing classes --- src/Phpml/Preprocessing/Imputer.php | 2 +- src/Phpml/Preprocessing/Normalizer.php | 2 +- src/Phpml/Preprocessing/Preprocessor.php | 9 ++++----- tests/Phpml/Preprocessing/ImputerTest.php | 12 ++++++------ tests/Phpml/Preprocessing/NormalizerTest.php | 4 ++-- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/Phpml/Preprocessing/Imputer.php b/src/Phpml/Preprocessing/Imputer.php index 355ff148..586e3f30 100644 --- a/src/Phpml/Preprocessing/Imputer.php +++ b/src/Phpml/Preprocessing/Imputer.php @@ -41,7 +41,7 @@ public function __construct($missingValue = null, Strategy $strategy, int $axis /** * @param array $samples */ - public function preprocess(array &$samples) + public function transform(array &$samples) { foreach ($samples as &$sample) { $this->preprocessSample($sample, $samples); diff --git a/src/Phpml/Preprocessing/Normalizer.php b/src/Phpml/Preprocessing/Normalizer.php index 4f017854..832d0443 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -33,7 +33,7 @@ public function __construct(int $norm = self::NORM_L2) /** * @param array $samples */ - public function preprocess(array &$samples) + public function transform(array &$samples) { $method = sprintf('normalizeL%s', $this->norm); foreach ($samples as &$sample) { diff --git a/src/Phpml/Preprocessing/Preprocessor.php b/src/Phpml/Preprocessing/Preprocessor.php index cfbaf066..ff5530e9 100644 --- a/src/Phpml/Preprocessing/Preprocessor.php +++ b/src/Phpml/Preprocessing/Preprocessor.php @@ -4,10 +4,9 @@ namespace Phpml\Preprocessing; -interface Preprocessor +use Phpml\Transformer; + +interface Preprocessor extends Transformer { - /** - * @param array $samples - */ - public function preprocess(array &$samples); + } diff --git a/tests/Phpml/Preprocessing/ImputerTest.php b/tests/Phpml/Preprocessing/ImputerTest.php index 9da97655..a7e36f72 100644 --- a/tests/Phpml/Preprocessing/ImputerTest.php +++ b/tests/Phpml/Preprocessing/ImputerTest.php @@ -28,7 +28,7 @@ public function testComplementsMissingValuesWithMeanStrategyOnColumnAxis() ]; $imputer = new Imputer(null, new MeanStrategy(), Imputer::AXIS_COLUMN); - $imputer->preprocess($data); + $imputer->transform($data); $this->assertEquals($imputeData, $data, '', $delta = 0.01); } @@ -50,7 +50,7 @@ public function testComplementsMissingValuesWithMeanStrategyOnRowAxis() ]; $imputer = new Imputer(null, new MeanStrategy(), Imputer::AXIS_ROW); - $imputer->preprocess($data); + $imputer->transform($data); $this->assertEquals($imputeData, $data, '', $delta = 0.01); } @@ -72,7 +72,7 @@ public function testComplementsMissingValuesWithMediaStrategyOnColumnAxis() ]; $imputer = new Imputer(null, new MedianStrategy(), Imputer::AXIS_COLUMN); - $imputer->preprocess($data); + $imputer->transform($data); $this->assertEquals($imputeData, $data, '', $delta = 0.01); } @@ -94,7 +94,7 @@ public function testComplementsMissingValuesWithMediaStrategyOnRowAxis() ]; $imputer = new Imputer(null, new MedianStrategy(), Imputer::AXIS_ROW); - $imputer->preprocess($data); + $imputer->transform($data); $this->assertEquals($imputeData, $data, '', $delta = 0.01); } @@ -118,7 +118,7 @@ public function testComplementsMissingValuesWithMostFrequentStrategyOnColumnAxis ]; $imputer = new Imputer(null, new MostFrequentStrategy(), Imputer::AXIS_COLUMN); - $imputer->preprocess($data); + $imputer->transform($data); $this->assertEquals($imputeData, $data); } @@ -142,7 +142,7 @@ public function testComplementsMissingValuesWithMostFrequentStrategyOnRowAxis() ]; $imputer = new Imputer(null, new MostFrequentStrategy(), Imputer::AXIS_ROW); - $imputer->preprocess($data); + $imputer->transform($data); $this->assertEquals($imputeData, $data); } diff --git a/tests/Phpml/Preprocessing/NormalizerTest.php b/tests/Phpml/Preprocessing/NormalizerTest.php index b373944e..cd007fbd 100644 --- a/tests/Phpml/Preprocessing/NormalizerTest.php +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -31,7 +31,7 @@ public function testNormalizeSamplesWithL2Norm() ]; $normalizer = new Normalizer(); - $normalizer->preprocess($samples); + $normalizer->transform($samples); $this->assertEquals($normalized, $samples, '', $delta = 0.01); } @@ -51,7 +51,7 @@ public function testNormalizeSamplesWithL1Norm() ]; $normalizer = new Normalizer(Normalizer::NORM_L1); - $normalizer->preprocess($samples); + $normalizer->transform($samples); $this->assertEquals($normalized, $samples, '', $delta = 0.01); } From 26f2cbabc4c40b950cc17989ecaa2fbe097eed2e Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 16 Jun 2016 10:26:29 +0200 Subject: [PATCH 033/328] fix Pipeline transformation --- src/Phpml/Pipeline.php | 2 +- tests/Phpml/PipelineTest.php | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/Phpml/Pipeline.php b/src/Phpml/Pipeline.php index 3f98f29d..2c2d9848 100644 --- a/src/Phpml/Pipeline.php +++ b/src/Phpml/Pipeline.php @@ -68,7 +68,7 @@ public function getEstimator() public function train(array $samples, array $targets) { foreach ($this->transformers as $transformer) { - $samples = $transformer->transform($samples); + $transformer->transform($samples); } $this->estimator->train($samples, $targets); diff --git a/tests/Phpml/PipelineTest.php b/tests/Phpml/PipelineTest.php index b00a60aa..b1d5bf7c 100644 --- a/tests/Phpml/PipelineTest.php +++ b/tests/Phpml/PipelineTest.php @@ -7,6 +7,9 @@ use Phpml\Classification\SVC; use Phpml\FeatureExtraction\TfIdfTransformer; use Phpml\Pipeline; +use Phpml\Preprocessing\Imputer; +use Phpml\Preprocessing\Normalizer; +use Phpml\Preprocessing\Imputer\Strategy\MostFrequentStrategy; class PipelineTest extends \PHPUnit_Framework_TestCase { @@ -22,4 +25,33 @@ public function testPipelineConstruction() $this->assertEquals($transformers, $pipeline->getTransformers()); $this->assertEquals($estimator, $pipeline->getEstimator()); } + + public function testPipelineWorkflow() + { + $transformers = [ + new Imputer(null, new MostFrequentStrategy()), + new Normalizer(), + ]; + $estimator = new SVC(); + + $samples = [ + [1, -1, 2], + [2, 0, null], + [null, 1, -1], + ]; + + $targets = [ + 4, + 1, + 4 + ]; + + $pipeline = new Pipeline($transformers, $estimator); + $pipeline->train($samples, $targets); + + $predicted = $pipeline->predict([[0, 0, 0]]); + + $this->assertEquals(4, $predicted[0]); + } + } From 7f4a0b243f6cc407c9016fc99d3b316d2a336972 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 16 Jun 2016 16:10:46 +0200 Subject: [PATCH 034/328] transform samples for prediction in pipeline --- src/Phpml/Pipeline.php | 17 +++++++++++++---- src/Phpml/Preprocessing/Preprocessor.php | 1 - tests/Phpml/PipelineTest.php | 3 +-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Phpml/Pipeline.php b/src/Phpml/Pipeline.php index 2c2d9848..f230db35 100644 --- a/src/Phpml/Pipeline.php +++ b/src/Phpml/Pipeline.php @@ -67,10 +67,7 @@ public function getEstimator() */ public function train(array $samples, array $targets) { - foreach ($this->transformers as $transformer) { - $transformer->transform($samples); - } - + $this->transformSamples($samples); $this->estimator->train($samples, $targets); } @@ -81,6 +78,18 @@ public function train(array $samples, array $targets) */ public function predict(array $samples) { + $this->transformSamples($samples); + return $this->estimator->predict($samples); } + + /** + * @param array $samples + */ + private function transformSamples(array &$samples) + { + foreach ($this->transformers as $transformer) { + $transformer->transform($samples); + } + } } diff --git a/src/Phpml/Preprocessing/Preprocessor.php b/src/Phpml/Preprocessing/Preprocessor.php index ff5530e9..ae709413 100644 --- a/src/Phpml/Preprocessing/Preprocessor.php +++ b/src/Phpml/Preprocessing/Preprocessor.php @@ -8,5 +8,4 @@ interface Preprocessor extends Transformer { - } diff --git a/tests/Phpml/PipelineTest.php b/tests/Phpml/PipelineTest.php index b1d5bf7c..4e5815b9 100644 --- a/tests/Phpml/PipelineTest.php +++ b/tests/Phpml/PipelineTest.php @@ -43,7 +43,7 @@ public function testPipelineWorkflow() $targets = [ 4, 1, - 4 + 4, ]; $pipeline = new Pipeline($transformers, $estimator); @@ -53,5 +53,4 @@ public function testPipelineWorkflow() $this->assertEquals(4, $predicted[0]); } - } From 4554011899b38cdd0439528c6684588a54419ce6 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 16 Jun 2016 23:56:15 +0200 Subject: [PATCH 035/328] rename labels to targets for Dataset --- src/Phpml/CrossValidation/RandomSplit.php | 2 +- src/Phpml/Dataset/ArrayDataset.php | 14 +++++++------- src/Phpml/Dataset/CsvDataset.php | 2 +- src/Phpml/Dataset/Dataset.php | 2 +- tests/Phpml/Dataset/ArrayDatasetTest.php | 2 +- tests/Phpml/Dataset/CsvDatasetTest.php | 4 ++-- tests/Phpml/Dataset/Demo/GlassTest.php | 2 +- tests/Phpml/Dataset/Demo/IrisTest.php | 2 +- tests/Phpml/Dataset/Demo/WineTest.php | 2 +- 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Phpml/CrossValidation/RandomSplit.php b/src/Phpml/CrossValidation/RandomSplit.php index c5a24bde..92de9763 100644 --- a/src/Phpml/CrossValidation/RandomSplit.php +++ b/src/Phpml/CrossValidation/RandomSplit.php @@ -44,7 +44,7 @@ public function __construct(Dataset $dataset, float $testSize = 0.3, int $seed = $this->seedGenerator($seed); $samples = $dataset->getSamples(); - $labels = $dataset->getLabels(); + $labels = $dataset->getTargets(); $datasetSize = count($samples); for ($i = $datasetSize; $i > 0; --$i) { diff --git a/src/Phpml/Dataset/ArrayDataset.php b/src/Phpml/Dataset/ArrayDataset.php index 7c5c2b5f..fd471e1d 100644 --- a/src/Phpml/Dataset/ArrayDataset.php +++ b/src/Phpml/Dataset/ArrayDataset.php @@ -16,22 +16,22 @@ class ArrayDataset implements Dataset /** * @var array */ - protected $labels = []; + protected $targets = []; /** * @param array $samples - * @param array $labels + * @param array $targets * * @throws InvalidArgumentException */ - public function __construct(array $samples, array $labels) + public function __construct(array $samples, array $targets) { - if (count($samples) != count($labels)) { + if (count($samples) != count($targets)) { throw InvalidArgumentException::arraySizeNotMatch(); } $this->samples = $samples; - $this->labels = $labels; + $this->targets = $targets; } /** @@ -45,8 +45,8 @@ public function getSamples(): array /** * @return array */ - public function getLabels(): array + public function getTargets(): array { - return $this->labels; + return $this->targets; } } diff --git a/src/Phpml/Dataset/CsvDataset.php b/src/Phpml/Dataset/CsvDataset.php index 7d1f91e3..23ac35c1 100644 --- a/src/Phpml/Dataset/CsvDataset.php +++ b/src/Phpml/Dataset/CsvDataset.php @@ -36,7 +36,7 @@ public function __construct(string $filepath, int $features, bool $headingRow = while (($data = fgetcsv($handle, 1000, ',')) !== false) { $this->samples[] = array_slice($data, 0, $features); - $this->labels[] = $data[$features]; + $this->targets[] = $data[$features]; } fclose($handle); } diff --git a/src/Phpml/Dataset/Dataset.php b/src/Phpml/Dataset/Dataset.php index 4e049316..791dcb4a 100644 --- a/src/Phpml/Dataset/Dataset.php +++ b/src/Phpml/Dataset/Dataset.php @@ -14,5 +14,5 @@ public function getSamples(): array; /** * @return array */ - public function getLabels(): array; + public function getTargets(): array; } diff --git a/tests/Phpml/Dataset/ArrayDatasetTest.php b/tests/Phpml/Dataset/ArrayDatasetTest.php index 7244b3ec..d1ede5f2 100644 --- a/tests/Phpml/Dataset/ArrayDatasetTest.php +++ b/tests/Phpml/Dataset/ArrayDatasetTest.php @@ -24,6 +24,6 @@ public function testArrayDataset() ); $this->assertEquals($samples, $dataset->getSamples()); - $this->assertEquals($labels, $dataset->getLabels()); + $this->assertEquals($labels, $dataset->getTargets()); } } diff --git a/tests/Phpml/Dataset/CsvDatasetTest.php b/tests/Phpml/Dataset/CsvDatasetTest.php index 29945047..a2629a6a 100644 --- a/tests/Phpml/Dataset/CsvDatasetTest.php +++ b/tests/Phpml/Dataset/CsvDatasetTest.php @@ -23,7 +23,7 @@ public function testSampleCsvDatasetWithHeaderRow() $dataset = new CsvDataset($filePath, 2, true); $this->assertEquals(10, count($dataset->getSamples())); - $this->assertEquals(10, count($dataset->getLabels())); + $this->assertEquals(10, count($dataset->getTargets())); } public function testSampleCsvDatasetWithoutHeaderRow() @@ -33,6 +33,6 @@ public function testSampleCsvDatasetWithoutHeaderRow() $dataset = new CsvDataset($filePath, 2, false); $this->assertEquals(11, count($dataset->getSamples())); - $this->assertEquals(11, count($dataset->getLabels())); + $this->assertEquals(11, count($dataset->getTargets())); } } diff --git a/tests/Phpml/Dataset/Demo/GlassTest.php b/tests/Phpml/Dataset/Demo/GlassTest.php index 6f6e1774..d4f13139 100644 --- a/tests/Phpml/Dataset/Demo/GlassTest.php +++ b/tests/Phpml/Dataset/Demo/GlassTest.php @@ -14,7 +14,7 @@ public function testLoadingWineDataset() // whole dataset $this->assertEquals(214, count($glass->getSamples())); - $this->assertEquals(214, count($glass->getLabels())); + $this->assertEquals(214, count($glass->getTargets())); // one sample features count $this->assertEquals(9, count($glass->getSamples()[0])); diff --git a/tests/Phpml/Dataset/Demo/IrisTest.php b/tests/Phpml/Dataset/Demo/IrisTest.php index 1f0da90e..354329e0 100644 --- a/tests/Phpml/Dataset/Demo/IrisTest.php +++ b/tests/Phpml/Dataset/Demo/IrisTest.php @@ -14,7 +14,7 @@ public function testLoadingIrisDataset() // whole dataset $this->assertEquals(150, count($iris->getSamples())); - $this->assertEquals(150, count($iris->getLabels())); + $this->assertEquals(150, count($iris->getTargets())); // one sample features count $this->assertEquals(4, count($iris->getSamples()[0])); diff --git a/tests/Phpml/Dataset/Demo/WineTest.php b/tests/Phpml/Dataset/Demo/WineTest.php index de16483a..34c93fae 100644 --- a/tests/Phpml/Dataset/Demo/WineTest.php +++ b/tests/Phpml/Dataset/Demo/WineTest.php @@ -14,7 +14,7 @@ public function testLoadingWineDataset() // whole dataset $this->assertEquals(178, count($wine->getSamples())); - $this->assertEquals(178, count($wine->getLabels())); + $this->assertEquals(178, count($wine->getTargets())); // one sample features count $this->assertEquals(13, count($wine->getSamples()[0])); From 557f344018d912928030f605ed31caea078250c4 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 17 Jun 2016 00:08:10 +0200 Subject: [PATCH 036/328] add fit method for Transformer interface --- src/Phpml/Exception/PreprocessorException.php | 17 +++++++++++++++++ .../FeatureExtraction/TfIdfTransformer.php | 18 +++++++++++++++++- .../FeatureExtraction/TokenCountVectorizer.php | 8 ++++++++ src/Phpml/Preprocessing/Imputer.php | 8 ++++++++ src/Phpml/Preprocessing/Normalizer.php | 11 +++++++++++ src/Phpml/Transformer.php | 6 ++++++ .../FeatureExtraction/TfIdfTransformerTest.php | 2 +- 7 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 src/Phpml/Exception/PreprocessorException.php diff --git a/src/Phpml/Exception/PreprocessorException.php b/src/Phpml/Exception/PreprocessorException.php new file mode 100644 index 00000000..15e39757 --- /dev/null +++ b/src/Phpml/Exception/PreprocessorException.php @@ -0,0 +1,17 @@ +fit($samples); + } + } + + /** + * @param array $samples + */ + public function fit(array $samples) { $this->countTokensFrequency($samples); @@ -24,7 +34,13 @@ public function transform(array &$samples) foreach ($this->idf as &$value) { $value = log($count / $value, 10); } + } + /** + * @param array $samples + */ + public function transform(array &$samples) + { foreach ($samples as &$sample) { foreach ($sample as $index => &$feature) { $feature = $feature * $this->idf[$index]; diff --git a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php index f8237789..c4551bb8 100644 --- a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php +++ b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php @@ -46,6 +46,14 @@ public function __construct(Tokenizer $tokenizer, float $minDF = 0) $this->frequencies = []; } + /** + * @param array $samples + */ + public function fit(array $samples) + { + // TODO: Implement fit() method. + } + /** * @param array $samples */ diff --git a/src/Phpml/Preprocessing/Imputer.php b/src/Phpml/Preprocessing/Imputer.php index 586e3f30..fdbfaf6c 100644 --- a/src/Phpml/Preprocessing/Imputer.php +++ b/src/Phpml/Preprocessing/Imputer.php @@ -38,6 +38,14 @@ public function __construct($missingValue = null, Strategy $strategy, int $axis $this->axis = $axis; } + /** + * @param array $samples + */ + public function fit(array $samples) + { + // TODO: Implement fit() method. + } + /** * @param array $samples */ diff --git a/src/Phpml/Preprocessing/Normalizer.php b/src/Phpml/Preprocessing/Normalizer.php index 832d0443..11a02188 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -5,6 +5,7 @@ namespace Phpml\Preprocessing; use Phpml\Exception\NormalizerException; +use Phpml\Exception\PreprocessorException; class Normalizer implements Preprocessor { @@ -30,6 +31,16 @@ public function __construct(int $norm = self::NORM_L2) $this->norm = $norm; } + /** + * @param array $samples + * + * @throws PreprocessorException + */ + public function fit(array $samples) + { + throw PreprocessorException::fitNotAllowed(); + } + /** * @param array $samples */ diff --git a/src/Phpml/Transformer.php b/src/Phpml/Transformer.php index 47c2ce3f..1accdda9 100644 --- a/src/Phpml/Transformer.php +++ b/src/Phpml/Transformer.php @@ -6,6 +6,12 @@ interface Transformer { + + /** + * @param array $samples + */ + public function fit(array $samples); + /** * @param array $samples */ diff --git a/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php b/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php index ca5db360..eceaeb3e 100644 --- a/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php +++ b/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php @@ -22,7 +22,7 @@ public function testTfIdfTransformation() [0 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 0.602, 5 => 0.903], ]; - $transformer = new TfIdfTransformer(); + $transformer = new TfIdfTransformer($samples); $transformer->transform($samples); $this->assertEquals($tfIdfSamples, $samples, '', 0.001); From 3e9e70810d14818d62aac4597c9432728cbbbf33 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 17 Jun 2016 00:16:49 +0200 Subject: [PATCH 037/328] implement fit on Imputer --- src/Phpml/Exception/NormalizerException.php | 9 ++++++++ src/Phpml/Exception/PreprocessorException.php | 17 -------------- src/Phpml/Pipeline.php | 11 +++++++++ src/Phpml/Preprocessing/Imputer.php | 23 +++++++++++-------- src/Phpml/Preprocessing/Normalizer.php | 5 +--- tests/Phpml/Preprocessing/ImputerTest.php | 12 +++++----- 6 files changed, 41 insertions(+), 36 deletions(-) delete mode 100644 src/Phpml/Exception/PreprocessorException.php diff --git a/src/Phpml/Exception/NormalizerException.php b/src/Phpml/Exception/NormalizerException.php index 9f88f0c5..5c7ced19 100644 --- a/src/Phpml/Exception/NormalizerException.php +++ b/src/Phpml/Exception/NormalizerException.php @@ -13,4 +13,13 @@ public static function unknownNorm() { return new self('Unknown norm supplied.'); } + + /** + * @return NormalizerException + */ + public static function fitNotAllowed() + { + return new self('Fit is not allowed for this preprocessor.'); + } + } diff --git a/src/Phpml/Exception/PreprocessorException.php b/src/Phpml/Exception/PreprocessorException.php deleted file mode 100644 index 15e39757..00000000 --- a/src/Phpml/Exception/PreprocessorException.php +++ /dev/null @@ -1,17 +0,0 @@ -fitTransformers($samples); $this->transformSamples($samples); $this->estimator->train($samples, $targets); } @@ -83,6 +84,16 @@ public function predict(array $samples) return $this->estimator->predict($samples); } + /** + * @param array $samples + */ + private function fitTransformers(array &$samples) + { + foreach ($this->transformers as $transformer) { + $transformer->fit($samples); + } + } + /** * @param array $samples */ diff --git a/src/Phpml/Preprocessing/Imputer.php b/src/Phpml/Preprocessing/Imputer.php index fdbfaf6c..424efa4e 100644 --- a/src/Phpml/Preprocessing/Imputer.php +++ b/src/Phpml/Preprocessing/Imputer.php @@ -26,16 +26,23 @@ class Imputer implements Preprocessor */ private $axis; + /** + * @var $samples + */ + private $samples; + /** * @param mixed $missingValue * @param Strategy $strategy * @param int $axis + * @param array|null $samples */ - public function __construct($missingValue = null, Strategy $strategy, int $axis = self::AXIS_COLUMN) + public function __construct($missingValue = null, Strategy $strategy, int $axis = self::AXIS_COLUMN, array $samples = []) { $this->missingValue = $missingValue; $this->strategy = $strategy; $this->axis = $axis; + $this->samples = $samples; } /** @@ -43,7 +50,7 @@ public function __construct($missingValue = null, Strategy $strategy, int $axis */ public function fit(array $samples) { - // TODO: Implement fit() method. + $this->samples = $samples; } /** @@ -52,19 +59,18 @@ public function fit(array $samples) public function transform(array &$samples) { foreach ($samples as &$sample) { - $this->preprocessSample($sample, $samples); + $this->preprocessSample($sample); } } /** * @param array $sample - * @param array $samples */ - private function preprocessSample(array &$sample, array $samples) + private function preprocessSample(array &$sample) { foreach ($sample as $column => &$value) { if ($value === $this->missingValue) { - $value = $this->strategy->replaceValue($this->getAxis($column, $sample, $samples)); + $value = $this->strategy->replaceValue($this->getAxis($column, $sample)); } } } @@ -72,18 +78,17 @@ private function preprocessSample(array &$sample, array $samples) /** * @param int $column * @param array $currentSample - * @param array $samples * * @return array */ - private function getAxis(int $column, array $currentSample, array $samples): array + private function getAxis(int $column, array $currentSample): array { if (self::AXIS_ROW === $this->axis) { return array_diff($currentSample, [$this->missingValue]); } $axis = []; - foreach ($samples as $sample) { + foreach ($this->samples as $sample) { if ($sample[$column] !== $this->missingValue) { $axis[] = $sample[$column]; } diff --git a/src/Phpml/Preprocessing/Normalizer.php b/src/Phpml/Preprocessing/Normalizer.php index 11a02188..76479973 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -5,7 +5,6 @@ namespace Phpml\Preprocessing; use Phpml\Exception\NormalizerException; -use Phpml\Exception\PreprocessorException; class Normalizer implements Preprocessor { @@ -33,12 +32,10 @@ public function __construct(int $norm = self::NORM_L2) /** * @param array $samples - * - * @throws PreprocessorException */ public function fit(array $samples) { - throw PreprocessorException::fitNotAllowed(); + // intentionally not implemented } /** diff --git a/tests/Phpml/Preprocessing/ImputerTest.php b/tests/Phpml/Preprocessing/ImputerTest.php index a7e36f72..9aa3ea39 100644 --- a/tests/Phpml/Preprocessing/ImputerTest.php +++ b/tests/Phpml/Preprocessing/ImputerTest.php @@ -27,7 +27,7 @@ public function testComplementsMissingValuesWithMeanStrategyOnColumnAxis() [8, 7, 4, 5], ]; - $imputer = new Imputer(null, new MeanStrategy(), Imputer::AXIS_COLUMN); + $imputer = new Imputer(null, new MeanStrategy(), Imputer::AXIS_COLUMN, $data); $imputer->transform($data); $this->assertEquals($imputeData, $data, '', $delta = 0.01); @@ -49,7 +49,7 @@ public function testComplementsMissingValuesWithMeanStrategyOnRowAxis() [8, 7, 6.66, 5], ]; - $imputer = new Imputer(null, new MeanStrategy(), Imputer::AXIS_ROW); + $imputer = new Imputer(null, new MeanStrategy(), Imputer::AXIS_ROW, $data); $imputer->transform($data); $this->assertEquals($imputeData, $data, '', $delta = 0.01); @@ -71,7 +71,7 @@ public function testComplementsMissingValuesWithMediaStrategyOnColumnAxis() [8, 7, 3, 5], ]; - $imputer = new Imputer(null, new MedianStrategy(), Imputer::AXIS_COLUMN); + $imputer = new Imputer(null, new MedianStrategy(), Imputer::AXIS_COLUMN, $data); $imputer->transform($data); $this->assertEquals($imputeData, $data, '', $delta = 0.01); @@ -93,7 +93,7 @@ public function testComplementsMissingValuesWithMediaStrategyOnRowAxis() [8, 7, 7, 5], ]; - $imputer = new Imputer(null, new MedianStrategy(), Imputer::AXIS_ROW); + $imputer = new Imputer(null, new MedianStrategy(), Imputer::AXIS_ROW, $data); $imputer->transform($data); $this->assertEquals($imputeData, $data, '', $delta = 0.01); @@ -117,7 +117,7 @@ public function testComplementsMissingValuesWithMostFrequentStrategyOnColumnAxis [8, 3, 2, 5], ]; - $imputer = new Imputer(null, new MostFrequentStrategy(), Imputer::AXIS_COLUMN); + $imputer = new Imputer(null, new MostFrequentStrategy(), Imputer::AXIS_COLUMN, $data); $imputer->transform($data); $this->assertEquals($imputeData, $data); @@ -141,7 +141,7 @@ public function testComplementsMissingValuesWithMostFrequentStrategyOnRowAxis() [8, 3, 2, 5, 4], ]; - $imputer = new Imputer(null, new MostFrequentStrategy(), Imputer::AXIS_ROW); + $imputer = new Imputer(null, new MostFrequentStrategy(), Imputer::AXIS_ROW, $data); $imputer->transform($data); $this->assertEquals($imputeData, $data); From be7423350fea4a5a2f6c7fe9dd88509125172559 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 17 Jun 2016 00:23:27 +0200 Subject: [PATCH 038/328] add more tests for fit metod in preprocessors --- tests/Phpml/Preprocessing/ImputerTest.php | 27 ++++++++++++++++++++ tests/Phpml/Preprocessing/NormalizerTest.php | 25 ++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/tests/Phpml/Preprocessing/ImputerTest.php b/tests/Phpml/Preprocessing/ImputerTest.php index 9aa3ea39..fce195b3 100644 --- a/tests/Phpml/Preprocessing/ImputerTest.php +++ b/tests/Phpml/Preprocessing/ImputerTest.php @@ -146,4 +146,31 @@ public function testComplementsMissingValuesWithMostFrequentStrategyOnRowAxis() $this->assertEquals($imputeData, $data); } + + public function testImputerWorksOnFitSamples() + { + $trainData = [ + [1, 3, 4], + [6, 7, 8], + [8, 7, 5], + ]; + + $data = [ + [1, 3, null], + [6, null, 8], + [null, 7, 5], + ]; + + $imputeData = [ + [1, 3, 5.66], + [6, 5.66, 8], + [5, 7, 5], + ]; + + $imputer = new Imputer(null, new MeanStrategy(), Imputer::AXIS_COLUMN, $trainData); + $imputer->transform($data); + + $this->assertEquals($imputeData, $data, '', $delta = 0.01); + } + } diff --git a/tests/Phpml/Preprocessing/NormalizerTest.php b/tests/Phpml/Preprocessing/NormalizerTest.php index cd007fbd..d01e62c8 100644 --- a/tests/Phpml/Preprocessing/NormalizerTest.php +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -55,4 +55,29 @@ public function testNormalizeSamplesWithL1Norm() $this->assertEquals($normalized, $samples, '', $delta = 0.01); } + + public function testFitNotChangeNormalizerBehavior() + { + $samples = [ + [1, -1, 2], + [2, 0, 0], + [0, 1, -1], + ]; + + $normalized = [ + [0.4, -0.4, 0.81], + [1.0, 0.0, 0.0], + [0.0, 0.7, -0.7], + ]; + + $normalizer = new Normalizer(); + $normalizer->transform($samples); + + $this->assertEquals($normalized, $samples, '', $delta = 0.01); + + $normalizer->fit($samples); + + $this->assertEquals($normalized, $samples, '', $delta = 0.01); + } + } From 424519cd83489a6b1664d67fa244f8ad3f7d7388 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 17 Jun 2016 00:33:48 +0200 Subject: [PATCH 039/328] implement fit fot TokenCountVectorizer --- .../TokenCountVectorizer.php | 58 +++++++------------ .../TokenCountVectorizerTest.php | 14 +++-- 2 files changed, 32 insertions(+), 40 deletions(-) diff --git a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php index c4551bb8..501263d0 100644 --- a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php +++ b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php @@ -24,11 +24,6 @@ class TokenCountVectorizer implements Transformer */ private $vocabulary; - /** - * @var array - */ - private $tokens; - /** * @var array */ @@ -51,7 +46,7 @@ public function __construct(Tokenizer $tokenizer, float $minDF = 0) */ public function fit(array $samples) { - // TODO: Implement fit() method. + $this->buildVocabulary($samples); } /** @@ -59,13 +54,11 @@ public function fit(array $samples) */ public function transform(array &$samples) { - $this->buildVocabulary($samples); - - foreach ($samples as $index => $sample) { - $samples[$index] = $this->transformSample($index); + foreach ($samples as &$sample) { + $this->transformSample($sample); } - $samples = $this->checkDocumentFrequency($samples); + $this->checkDocumentFrequency($samples); } /** @@ -86,28 +79,27 @@ private function buildVocabulary(array &$samples) foreach ($tokens as $token) { $this->addTokenToVocabulary($token); } - $this->tokens[$index] = $tokens; } } /** - * @param int $index - * - * @return array + * @param string $sample */ - private function transformSample(int $index) + private function transformSample(string &$sample) { $counts = []; - $tokens = $this->tokens[$index]; + $tokens = $this->tokenizer->tokenize($sample); foreach ($tokens as $token) { $index = $this->getTokenIndex($token); - $this->updateFrequency($token); - if (!isset($counts[$index])) { - $counts[$index] = 0; - } + if(false !== $index) { + $this->updateFrequency($token); + if (!isset($counts[$index])) { + $counts[$index] = 0; + } - ++$counts[$index]; + ++$counts[$index]; + } } foreach ($this->vocabulary as $index) { @@ -116,17 +108,17 @@ private function transformSample(int $index) } } - return $counts; + $sample = $counts; } /** * @param string $token * - * @return int + * @return int|bool */ - private function getTokenIndex(string $token): int + private function getTokenIndex(string $token) { - return $this->vocabulary[$token]; + return isset($this->vocabulary[$token]) ? $this->vocabulary[$token] : false; } /** @@ -156,31 +148,25 @@ private function updateFrequency(string $token) * * @return array */ - private function checkDocumentFrequency(array $samples) + private function checkDocumentFrequency(array &$samples) { if ($this->minDF > 0) { $beyondMinimum = $this->getBeyondMinimumIndexes(count($samples)); - foreach ($samples as $index => $sample) { - $samples[$index] = $this->resetBeyondMinimum($sample, $beyondMinimum); + foreach ($samples as &$sample) { + $this->resetBeyondMinimum($sample, $beyondMinimum); } } - - return $samples; } /** * @param array $sample * @param array $beyondMinimum - * - * @return array */ - private function resetBeyondMinimum(array $sample, array $beyondMinimum) + private function resetBeyondMinimum(array &$sample, array $beyondMinimum) { foreach ($beyondMinimum as $index) { $sample[$index] = 0; } - - return $sample; } /** diff --git a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php index 80b77231..cf22b5c8 100644 --- a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php +++ b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php @@ -37,10 +37,12 @@ public function testTokenCountVectorizerWithWhitespaceTokenizer() ]; $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer()); - $vectorizer->transform($samples); - $this->assertEquals($tokensCounts, $samples); + $vectorizer->fit($samples); $this->assertEquals($vocabulary, $vectorizer->getVocabulary()); + + $vectorizer->transform($samples); + $this->assertEquals($tokensCounts, $samples); } public function testMinimumDocumentTokenCountFrequency() @@ -69,11 +71,14 @@ public function testMinimumDocumentTokenCountFrequency() ]; $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), 0.5); - $vectorizer->transform($samples); - $this->assertEquals($tokensCounts, $samples); + $vectorizer->fit($samples); $this->assertEquals($vocabulary, $vectorizer->getVocabulary()); + $vectorizer->transform($samples); + $this->assertEquals($tokensCounts, $samples); + + // word at least once in all samples $samples = [ 'Lorem ipsum dolor sit amet', @@ -88,6 +93,7 @@ public function testMinimumDocumentTokenCountFrequency() ]; $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), 1); + $vectorizer->fit($samples); $vectorizer->transform($samples); $this->assertEquals($tokensCounts, $samples); From 601ff884e87aa3850c8c16baf724010b90a0393a Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 17 Jun 2016 00:34:15 +0200 Subject: [PATCH 040/328] php-cs-fixer --- src/Phpml/Exception/NormalizerException.php | 1 - src/Phpml/FeatureExtraction/TfIdfTransformer.php | 2 +- src/Phpml/FeatureExtraction/TokenCountVectorizer.php | 2 +- src/Phpml/Preprocessing/Imputer.php | 8 ++++---- src/Phpml/Transformer.php | 1 - .../Phpml/FeatureExtraction/TokenCountVectorizerTest.php | 1 - tests/Phpml/Preprocessing/ImputerTest.php | 1 - tests/Phpml/Preprocessing/NormalizerTest.php | 1 - 8 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/Phpml/Exception/NormalizerException.php b/src/Phpml/Exception/NormalizerException.php index 5c7ced19..31abfeb1 100644 --- a/src/Phpml/Exception/NormalizerException.php +++ b/src/Phpml/Exception/NormalizerException.php @@ -21,5 +21,4 @@ public static function fitNotAllowed() { return new self('Fit is not allowed for this preprocessor.'); } - } diff --git a/src/Phpml/FeatureExtraction/TfIdfTransformer.php b/src/Phpml/FeatureExtraction/TfIdfTransformer.php index 17f5b8bb..1e222d2a 100644 --- a/src/Phpml/FeatureExtraction/TfIdfTransformer.php +++ b/src/Phpml/FeatureExtraction/TfIdfTransformer.php @@ -18,7 +18,7 @@ class TfIdfTransformer implements Transformer */ public function __construct(array $samples = null) { - if($samples) { + if ($samples) { $this->fit($samples); } } diff --git a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php index 501263d0..8d4a5ab6 100644 --- a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php +++ b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php @@ -92,7 +92,7 @@ private function transformSample(string &$sample) foreach ($tokens as $token) { $index = $this->getTokenIndex($token); - if(false !== $index) { + if (false !== $index) { $this->updateFrequency($token); if (!isset($counts[$index])) { $counts[$index] = 0; diff --git a/src/Phpml/Preprocessing/Imputer.php b/src/Phpml/Preprocessing/Imputer.php index 424efa4e..012bb79f 100644 --- a/src/Phpml/Preprocessing/Imputer.php +++ b/src/Phpml/Preprocessing/Imputer.php @@ -27,14 +27,14 @@ class Imputer implements Preprocessor private $axis; /** - * @var $samples + * @var */ private $samples; /** - * @param mixed $missingValue - * @param Strategy $strategy - * @param int $axis + * @param mixed $missingValue + * @param Strategy $strategy + * @param int $axis * @param array|null $samples */ public function __construct($missingValue = null, Strategy $strategy, int $axis = self::AXIS_COLUMN, array $samples = []) diff --git a/src/Phpml/Transformer.php b/src/Phpml/Transformer.php index 1accdda9..f7ddd81b 100644 --- a/src/Phpml/Transformer.php +++ b/src/Phpml/Transformer.php @@ -6,7 +6,6 @@ interface Transformer { - /** * @param array $samples */ diff --git a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php index cf22b5c8..3a5f7fee 100644 --- a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php +++ b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php @@ -78,7 +78,6 @@ public function testMinimumDocumentTokenCountFrequency() $vectorizer->transform($samples); $this->assertEquals($tokensCounts, $samples); - // word at least once in all samples $samples = [ 'Lorem ipsum dolor sit amet', diff --git a/tests/Phpml/Preprocessing/ImputerTest.php b/tests/Phpml/Preprocessing/ImputerTest.php index fce195b3..c6075109 100644 --- a/tests/Phpml/Preprocessing/ImputerTest.php +++ b/tests/Phpml/Preprocessing/ImputerTest.php @@ -172,5 +172,4 @@ public function testImputerWorksOnFitSamples() $this->assertEquals($imputeData, $data, '', $delta = 0.01); } - } diff --git a/tests/Phpml/Preprocessing/NormalizerTest.php b/tests/Phpml/Preprocessing/NormalizerTest.php index d01e62c8..f0e21c99 100644 --- a/tests/Phpml/Preprocessing/NormalizerTest.php +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -79,5 +79,4 @@ public function testFitNotChangeNormalizerBehavior() $this->assertEquals($normalized, $samples, '', $delta = 0.01); } - } From dd9727e1abc9be69d87d99e4ba0cfe82c0c7a804 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 30 Jun 2016 22:55:48 +0200 Subject: [PATCH 041/328] add osx to travis config --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 247e222c..2337e482 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,6 @@ +os: + - linux + - osx language: php php: - '7.0' From 44f04498bae269e7c65500879880deb395d1e28c Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 30 Jun 2016 23:03:47 +0200 Subject: [PATCH 042/328] add hhvm for travis --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2337e482..cc176a69 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ os: - osx language: php php: - - '7.0' + - 7 + - hhvm before_script: composer install script: bin/phpunit \ No newline at end of file From 62fc7b9efd17f5ca3a017c282db3f871d0346178 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 30 Jun 2016 23:16:19 +0200 Subject: [PATCH 043/328] fix problem with platform req for travis --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index cc176a69..9f599352 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ os: - osx language: php php: - - 7 + - 7.0 - hhvm -before_script: composer install +before_script: composer install --ignore-platform-reqs script: bin/phpunit \ No newline at end of file From be7693ff2e978dadaceded5743a7c1e9879aa761 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 30 Jun 2016 23:27:17 +0200 Subject: [PATCH 044/328] remove osx from travis - dont work with php 7.0 --- .travis.yml | 1 - src/Phpml/Classification/KNearestNeighbors.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9f599352..3d634626 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ os: - linux - - osx language: php php: - 7.0 diff --git a/src/Phpml/Classification/KNearestNeighbors.php b/src/Phpml/Classification/KNearestNeighbors.php index 95ebeafb..594438b3 100644 --- a/src/Phpml/Classification/KNearestNeighbors.php +++ b/src/Phpml/Classification/KNearestNeighbors.php @@ -67,7 +67,7 @@ protected function predictSample(array $sample) * * @throws \Phpml\Exception\InvalidArgumentException */ - private function kNeighborsDistances(array $sample): array + private function kNeighborsDistances(array $sample) { $distances = []; From 93a28aa762925f57558422a77241d9b78661d1ea Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 30 Jun 2016 23:33:21 +0200 Subject: [PATCH 045/328] update travis --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3d634626..6ddffd82 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,5 @@ -os: - - linux language: php php: - 7.0 - - hhvm before_script: composer install --ignore-platform-reqs script: bin/phpunit \ No newline at end of file From bfd97eb463ba1b870fbbbf8be4aa460e54313e06 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 30 Jun 2016 23:42:45 +0200 Subject: [PATCH 046/328] try with other os in travis --- .travis.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6ddffd82..5281d26c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,14 @@ language: php + +os: + - linux + - windows + php: - 7.0 -before_script: composer install --ignore-platform-reqs -script: bin/phpunit \ No newline at end of file + +before_script: + - composer install --ignore-platform-reqs + +script: + - bin/phpunit From 2f152b4a34d38b6efd570447da1d3b04e838bd95 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 30 Jun 2016 23:49:43 +0200 Subject: [PATCH 047/328] clean up travis config --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5281d26c..383d3de8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: php os: - linux - - windows php: - 7.0 From 43ba6e99fc0cf74a6a7e6caa2edd9709b119da18 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 1 Jul 2016 22:05:20 +0200 Subject: [PATCH 048/328] test build php for osx in travis --- .travis.yml | 25 +++++++++++++++++++------ tools/handle_brew_pkg.sh | 31 +++++++++++++++++++++++++++++++ tools/prepare_osx_env.sh | 19 +++++++++++++++++++ 3 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 tools/handle_brew_pkg.sh create mode 100644 tools/prepare_osx_env.sh diff --git a/.travis.yml b/.travis.yml index 383d3de8..6b59901c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,26 @@ language: php -os: - - linux +matrix: + fast_finish: true -php: - - 7.0 + include: + - os: linux + php: '7.0' -before_script: - - composer install --ignore-platform-reqs + - os: osx + osx_image: xcode7.3 + language: generic + env: + - _OSX=10.11 + - _PHP: php70 + +before_install: + - if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then /usr/bin/env bash build/prepare_osx_env.sh ; fi + +install: + - if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then /usr/bin/env bash tools/handle_brew_pkg.sh "${_PHP}" ; fi + - curl -s http://getcomposer.org/installer | php + - php composer.phar install --dev --no-interaction --ignore-platform-reqs script: - bin/phpunit diff --git a/tools/handle_brew_pkg.sh b/tools/handle_brew_pkg.sh new file mode 100644 index 00000000..3e501e04 --- /dev/null +++ b/tools/handle_brew_pkg.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +if [[ "$#" -eq 1 ]]; then + echo "Handling \"$1\" brew package..." +else + echo "Brew failed - invalid $0 call" + exit 1; +fi + +if [[ $(brew ls --versions "$1") ]]; then + if brew outdated "$1"; then + echo "Package upgrade is not required, skipping" + else + echo "Updating package..."; + brew upgrade "$1" + if [ $? -ne 0 ]; then + echo "Upgrade failed" + exit 1 + fi + fi +else + echo "Package not available - installing..." + brew install "$1" + if [ $? -ne 0 ]; then + echo "Install failed" + exit 1 + fi +fi + +echo "Linking installed package..." +brew link "$1" \ No newline at end of file diff --git a/tools/prepare_osx_env.sh b/tools/prepare_osx_env.sh new file mode 100644 index 00000000..93303ee1 --- /dev/null +++ b/tools/prepare_osx_env.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +echo "Here's the OSX environment:" +sw_vers +brew --version + +echo "Updating brew..." +brew update + +if [[ "${_PHP}" == "hhvm" ]]; then + echo "Adding brew HHVM dependencies..." + brew tap hhvm/hhvm + +else + echo "Adding brew PHP dependencies..." + brew tap homebrew/dupes + brew tap homebrew/versions + brew tap homebrew/homebrew-php +fi \ No newline at end of file From ddddd739f4e7524ee0af5e235de3a0e69c5b2d14 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 1 Jul 2016 22:07:57 +0200 Subject: [PATCH 049/328] fix path for sh script in travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6b59901c..ea7c1c84 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ matrix: - _PHP: php70 before_install: - - if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then /usr/bin/env bash build/prepare_osx_env.sh ; fi + - if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then /usr/bin/env bash tools/prepare_osx_env.sh ; fi install: - if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then /usr/bin/env bash tools/handle_brew_pkg.sh "${_PHP}" ; fi From 9507d58a80953cbbdc46ca112d473f711ac390c0 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 1 Jul 2016 22:25:57 +0200 Subject: [PATCH 050/328] add support for osx --- bin/libsvm/svm-predict-osx | Bin 0 -> 75716 bytes bin/libsvm/svm-scale-osx | Bin 0 -> 14432 bytes bin/libsvm/svm-train-osx | Bin 0 -> 76220 bytes .../SupportVectorMachine.php | 5 ++++- 4 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 bin/libsvm/svm-predict-osx create mode 100644 bin/libsvm/svm-scale-osx create mode 100644 bin/libsvm/svm-train-osx diff --git a/bin/libsvm/svm-predict-osx b/bin/libsvm/svm-predict-osx new file mode 100644 index 0000000000000000000000000000000000000000..480a60b7899acc51af2b93d2f840ee5b61e8c2a1 GIT binary patch literal 75716 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 z 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 G@BaW7hNhqZ literal 0 HcmV?d00001 diff --git a/bin/libsvm/svm-scale-osx b/bin/libsvm/svm-scale-osx new file mode 100644 index 0000000000000000000000000000000000000000..0ac83f662a66312ec248c096266115aea1f02aaa GIT binary patch literal 14432 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 literal 0 HcmV?d00001 diff --git a/bin/libsvm/svm-train-osx b/bin/libsvm/svm-train-osx new file mode 100644 index 0000000000000000000000000000000000000000..a716cdabd5f925c143f25dac1a876b8fe6da10f8 GIT binary patch literal 76220 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 sL)Ry& Date: Fri, 1 Jul 2016 22:36:59 +0200 Subject: [PATCH 051/328] fix permission for osx --- bin/libsvm/svm-predict-osx | Bin bin/libsvm/svm-scale-osx | Bin bin/libsvm/svm-train-osx | Bin 3 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 bin/libsvm/svm-predict-osx mode change 100644 => 100755 bin/libsvm/svm-scale-osx mode change 100644 => 100755 bin/libsvm/svm-train-osx diff --git a/bin/libsvm/svm-predict-osx b/bin/libsvm/svm-predict-osx old mode 100644 new mode 100755 diff --git a/bin/libsvm/svm-scale-osx b/bin/libsvm/svm-scale-osx old mode 100644 new mode 100755 diff --git a/bin/libsvm/svm-train-osx b/bin/libsvm/svm-train-osx old mode 100644 new mode 100755 From a2aa27adbab71852c9c0ab9002aaabc39c436c28 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Mon, 4 Jul 2016 22:22:22 +0200 Subject: [PATCH 052/328] fix problem in SVM with path on windows --- src/Phpml/SupportVectorMachine/SupportVectorMachine.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php index 0453d4c7..65b8ef65 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -211,7 +211,7 @@ private function getOSExtension() */ private function buildTrainCommand(string $trainingSetFileName, string $modelFileName): string { - return sprintf('%ssvm-train%s -s %s -t %s -c %s -n %s -d %s%s -r %s -p %s -m %s -e %s -h %d -b %d \'%s\' \'%s\'', + return sprintf('%ssvm-train%s -s %s -t %s -c %s -n %s -d %s%s -r %s -p %s -m %s -e %s -h %d -b %d %s %s', $this->binPath, $this->getOSExtension(), $this->type, @@ -226,8 +226,8 @@ private function buildTrainCommand(string $trainingSetFileName, string $modelFil $this->tolerance, $this->shrinking, $this->probabilityEstimates, - $trainingSetFileName, - $modelFileName + escapeshellarg($trainingSetFileName), + escapeshellarg($modelFileName) ); } } From cce68997a1daf0551366be083e1cd2ff0d3d1fff Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 6 Jul 2016 23:22:29 +0200 Subject: [PATCH 053/328] implement StopWords in TokenCountVectorizer --- .../TokenCountVectorizer.php | 28 ++++++++++- .../TokenCountVectorizerTest.php | 47 +++++++++++++++++-- 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php index 8d4a5ab6..3ec6af1f 100644 --- a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php +++ b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php @@ -14,6 +14,11 @@ class TokenCountVectorizer implements Transformer */ private $tokenizer; + /** + * @var StopWords + */ + private $stopWords; + /** * @var float */ @@ -31,12 +36,15 @@ class TokenCountVectorizer implements Transformer /** * @param Tokenizer $tokenizer + * @param StopWords $stopWords * @param float $minDF */ - public function __construct(Tokenizer $tokenizer, float $minDF = 0) + public function __construct(Tokenizer $tokenizer, StopWords $stopWords = null, float $minDF = 0) { $this->tokenizer = $tokenizer; + $this->stopWords = $stopWords; $this->minDF = $minDF; + $this->vocabulary = []; $this->frequencies = []; } @@ -118,6 +126,10 @@ private function transformSample(string &$sample) */ private function getTokenIndex(string $token) { + if ($this->isStopWord($token)) { + return false; + } + return isset($this->vocabulary[$token]) ? $this->vocabulary[$token] : false; } @@ -126,11 +138,25 @@ private function getTokenIndex(string $token) */ private function addTokenToVocabulary(string $token) { + if ($this->isStopWord($token)) { + return; + } + if (!isset($this->vocabulary[$token])) { $this->vocabulary[$token] = count($this->vocabulary); } } + /** + * @param string $token + * + * @return bool + */ + private function isStopWord(string $token): bool + { + return $this->stopWords && $this->stopWords->isStopWord($token); + } + /** * @param string $token */ diff --git a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php index 3a5f7fee..b18db60e 100644 --- a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php +++ b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php @@ -4,12 +4,13 @@ namespace tests\Phpml\FeatureExtraction; +use Phpml\FeatureExtraction\StopWords; use Phpml\FeatureExtraction\TokenCountVectorizer; use Phpml\Tokenization\WhitespaceTokenizer; class TokenCountVectorizerTest extends \PHPUnit_Framework_TestCase { - public function testTokenCountVectorizerWithWhitespaceTokenizer() + public function testTransformationWithWhitespaceTokenizer() { $samples = [ 'Lorem ipsum dolor sit amet dolor', @@ -45,7 +46,7 @@ public function testTokenCountVectorizerWithWhitespaceTokenizer() $this->assertEquals($tokensCounts, $samples); } - public function testMinimumDocumentTokenCountFrequency() + public function testTransformationWithMinimumDocumentTokenCountFrequency() { // word at least in half samples $samples = [ @@ -70,7 +71,7 @@ public function testMinimumDocumentTokenCountFrequency() [0 => 0, 1 => 1, 2 => 0, 3 => 1, 4 => 1], ]; - $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), 0.5); + $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), null, 0.5); $vectorizer->fit($samples); $this->assertEquals($vocabulary, $vectorizer->getVocabulary()); @@ -91,10 +92,48 @@ public function testMinimumDocumentTokenCountFrequency() [0 => 1, 1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0, 7 => 0, 8 => 0], ]; - $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), 1); + $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), null, 1); $vectorizer->fit($samples); $vectorizer->transform($samples); $this->assertEquals($tokensCounts, $samples); } + + public function testTransformationWithStopWords() + { + $samples = [ + 'Lorem ipsum dolor sit amet dolor', + 'Mauris placerat ipsum dolor', + 'Mauris diam eros fringilla diam', + ]; + + $stopWords = new StopWords(['dolor', 'diam']); + + $vocabulary = [ + 0 => 'Lorem', + 1 => 'ipsum', + //2 => 'dolor', + 2 => 'sit', + 3 => 'amet', + 4 => 'Mauris', + 5 => 'placerat', + //7 => 'diam', + 6 => 'eros', + 7 => 'fringilla', + ]; + + $tokensCounts = [ + [0 => 1, 1 => 1, 2 => 1, 3 => 1, 4 => 0, 5 => 0, 6 => 0, 7 => 0], + [0 => 0, 1 => 1, 2 => 0, 3 => 0, 4 => 1, 5 => 1, 6 => 0, 7 => 0], + [0 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 1, 5 => 0, 6 => 1, 7 => 1], + ]; + + $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), $stopWords); + + $vectorizer->fit($samples); + $this->assertEquals($vocabulary, $vectorizer->getVocabulary()); + + $vectorizer->transform($samples); + $this->assertEquals($tokensCounts, $samples); + } } From 6c7416a9c40b596afce625617dce07892ccaa2fc Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 7 Jul 2016 00:29:58 +0200 Subject: [PATCH 054/328] implement ConfusionMatrix metric --- src/Phpml/Metric/ConfusionMatrix.php | 71 ++++++++++++++++++++++ tests/Phpml/Metric/ConfusionMatrixTest.php | 61 +++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 src/Phpml/Metric/ConfusionMatrix.php create mode 100644 tests/Phpml/Metric/ConfusionMatrixTest.php diff --git a/src/Phpml/Metric/ConfusionMatrix.php b/src/Phpml/Metric/ConfusionMatrix.php new file mode 100644 index 00000000..6aeaa873 --- /dev/null +++ b/src/Phpml/Metric/ConfusionMatrix.php @@ -0,0 +1,71 @@ + $actual) { + $predicted = $predictedLabels[$index]; + + if (!isset($labels[$actual]) || !isset($labels[$predicted])) { + continue; + } + + if ($predicted === $actual) { + $row = $column = $labels[$actual]; + } else { + $row = $labels[$actual]; + $column = $labels[$predicted]; + } + + $matrix[$row][$column] += 1; + } + + return $matrix; + } + + /** + * @param array $labels + * + * @return array + */ + private static function generateMatrixWithZeros(array $labels): array + { + $count = count($labels); + $matrix = []; + + for ($i = 0; $i < $count; ++$i) { + $matrix[$i] = array_fill(0, $count, 0); + } + + return $matrix; + } + + /** + * @param array $labels + * + * @return array + */ + private static function getUniqueLabels(array $labels): array + { + $labels = array_values(array_unique($labels)); + sort($labels); + $labels = array_flip($labels); + + return $labels; + } +} diff --git a/tests/Phpml/Metric/ConfusionMatrixTest.php b/tests/Phpml/Metric/ConfusionMatrixTest.php new file mode 100644 index 00000000..d133ae2e --- /dev/null +++ b/tests/Phpml/Metric/ConfusionMatrixTest.php @@ -0,0 +1,61 @@ +assertEquals($confusionMatrix, ConfusionMatrix::compute($actualLabels, $predictedLabels)); + } + + public function testComputeConfusionMatrixOnStringLabels() + { + $actualLabels = ['cat', 'ant', 'cat', 'cat', 'ant', 'bird']; + $predictedLabels = ['ant', 'ant', 'cat', 'cat', 'ant', 'cat']; + + $confusionMatrix = [ + [2, 0, 0], + [0, 0, 1], + [1, 0, 2], + ]; + + $this->assertEquals($confusionMatrix, ConfusionMatrix::compute($actualLabels, $predictedLabels)); + } + + public function testComputeConfusionMatrixOnLabelsWithSubset() + { + $actualLabels = ['cat', 'ant', 'cat', 'cat', 'ant', 'bird']; + $predictedLabels = ['ant', 'ant', 'cat', 'cat', 'ant', 'cat']; + $labels = ['ant', 'bird']; + + $confusionMatrix = [ + [2, 0], + [0, 0], + ]; + + $this->assertEquals($confusionMatrix, ConfusionMatrix::compute($actualLabels, $predictedLabels, $labels)); + + $labels = ['bird', 'ant']; + + $confusionMatrix = [ + [0, 0], + [0, 2], + ]; + + $this->assertEquals($confusionMatrix, ConfusionMatrix::compute($actualLabels, $predictedLabels, $labels)); + } +} From 4aa9702943470f916f969ec9a20f93a0f3194fa1 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 7 Jul 2016 22:47:36 +0200 Subject: [PATCH 055/328] fix errors on hhvm with float casting --- src/Phpml/FeatureExtraction/TfIdfTransformer.php | 2 +- src/Phpml/Preprocessing/Normalizer.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Phpml/FeatureExtraction/TfIdfTransformer.php b/src/Phpml/FeatureExtraction/TfIdfTransformer.php index 1e222d2a..55629bd9 100644 --- a/src/Phpml/FeatureExtraction/TfIdfTransformer.php +++ b/src/Phpml/FeatureExtraction/TfIdfTransformer.php @@ -32,7 +32,7 @@ public function fit(array $samples) $count = count($samples); foreach ($this->idf as &$value) { - $value = log($count / $value, 10); + $value = log(floatval($count / $value), 10.0); } } diff --git a/src/Phpml/Preprocessing/Normalizer.php b/src/Phpml/Preprocessing/Normalizer.php index 76479973..fa62dde0 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -78,7 +78,7 @@ private function normalizeL2(array &$sample) foreach ($sample as $feature) { $norm2 += $feature * $feature; } - $norm2 = sqrt($norm2); + $norm2 = sqrt(floatval($norm2)); if (0 == $norm2) { $sample = array_fill(0, count($sample), 1); From 0a612a603179ac7721578c846407035a06c6a07b Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 7 Jul 2016 22:50:46 +0200 Subject: [PATCH 056/328] add hhvm to travis --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index ea7c1c84..96ac55ff 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,11 @@ matrix: - os: linux php: '7.0' + - os: linux + php: hhvm + env: + - HHVMPHP7="yes" + - os: osx osx_image: xcode7.3 language: generic From f3288c594617a40064cf69da871a17b5eb78694a Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 7 Jul 2016 23:33:06 +0200 Subject: [PATCH 057/328] fix scalar typehint for hhvm --- src/Phpml/Classification/KNearestNeighbors.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Phpml/Classification/KNearestNeighbors.php b/src/Phpml/Classification/KNearestNeighbors.php index 594438b3..42d8374f 100644 --- a/src/Phpml/Classification/KNearestNeighbors.php +++ b/src/Phpml/Classification/KNearestNeighbors.php @@ -27,13 +27,13 @@ class KNearestNeighbors implements Classifier * @param int $k * @param Distance|null $distanceMetric (if null then Euclidean distance as default) */ - public function __construct(int $k = 3, Distance $distanceMetric = null) + public function __construct(int $k = null, Distance $distanceMetric = null) { if (null === $distanceMetric) { $distanceMetric = new Euclidean(); } - $this->k = $k; + $this->k = $k ?? 3; $this->samples = []; $this->targets = []; $this->distanceMetric = $distanceMetric; From adc2d1c81b67a630508ae57a7f7a59304c11fbb4 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 7 Jul 2016 23:38:11 +0200 Subject: [PATCH 058/328] change hhvm to 3.12 --- .travis.yml | 2 +- src/Phpml/Classification/KNearestNeighbors.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 96ac55ff..0219f4ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ matrix: php: '7.0' - os: linux - php: hhvm + php: hhvm-3.12 env: - HHVMPHP7="yes" diff --git a/src/Phpml/Classification/KNearestNeighbors.php b/src/Phpml/Classification/KNearestNeighbors.php index 42d8374f..594438b3 100644 --- a/src/Phpml/Classification/KNearestNeighbors.php +++ b/src/Phpml/Classification/KNearestNeighbors.php @@ -27,13 +27,13 @@ class KNearestNeighbors implements Classifier * @param int $k * @param Distance|null $distanceMetric (if null then Euclidean distance as default) */ - public function __construct(int $k = null, Distance $distanceMetric = null) + public function __construct(int $k = 3, Distance $distanceMetric = null) { if (null === $distanceMetric) { $distanceMetric = new Euclidean(); } - $this->k = $k ?? 3; + $this->k = $k; $this->samples = []; $this->targets = []; $this->distanceMetric = $distanceMetric; From 3171758418b93e58f7f47a145ab0a30532b3c2d3 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 7 Jul 2016 23:42:39 +0200 Subject: [PATCH 059/328] add php7 to hhvm --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0219f4ba..535a3c26 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ matrix: php: '7.0' - os: linux - php: hhvm-3.12 + php: hhvm env: - HHVMPHP7="yes" @@ -27,5 +27,8 @@ install: - curl -s http://getcomposer.org/installer | php - php composer.phar install --dev --no-interaction --ignore-platform-reqs +before_script: + - if [[ $HHVMPHP7 == "yes" ]]; then echo hhvm.php7.all=1 >> /etc/hhvm/php.ini; fi + script: - bin/phpunit From 2e417cb39abc289d73a6cd22acb748df410f80b8 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 7 Jul 2016 23:50:10 +0200 Subject: [PATCH 060/328] restart hhvm in travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 535a3c26..93d162bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,6 +29,7 @@ install: before_script: - if [[ $HHVMPHP7 == "yes" ]]; then echo hhvm.php7.all=1 >> /etc/hhvm/php.ini; fi + - if [[ $HHVMPHP7 == "yes" ]]; then sudo service hhvm restart ; sleep 1 ; fi script: - bin/phpunit From baadfc365f97da166015d5c9532cbe332bc9e137 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 7 Jul 2016 23:54:03 +0200 Subject: [PATCH 061/328] comment hhvm in travis --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 93d162bb..6e665ef6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,10 +7,10 @@ matrix: - os: linux php: '7.0' - - os: linux - php: hhvm - env: - - HHVMPHP7="yes" +# - os: linux +# php: hhvm +# env: +# - HHVMPHP7="yes" - os: osx osx_image: xcode7.3 From 3f9617b1965f345d97af807ad4597b3a3b6ea714 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 7 Jul 2016 23:57:50 +0200 Subject: [PATCH 062/328] another try with hhvm and travis --- .travis.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6e665ef6..925d8e42 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,10 +7,8 @@ matrix: - os: linux php: '7.0' -# - os: linux -# php: hhvm -# env: -# - HHVMPHP7="yes" + - os: linux + php: hhvm - os: osx osx_image: xcode7.3 @@ -27,9 +25,6 @@ install: - curl -s http://getcomposer.org/installer | php - php composer.phar install --dev --no-interaction --ignore-platform-reqs -before_script: - - if [[ $HHVMPHP7 == "yes" ]]; then echo hhvm.php7.all=1 >> /etc/hhvm/php.ini; fi - - if [[ $HHVMPHP7 == "yes" ]]; then sudo service hhvm restart ; sleep 1 ; fi - script: - - bin/phpunit + - if [[ "$TRAVIS_PHP_VERSION" = "hhv*" ]]; then php -d hhvm.php7.all=1 bin/phpunit ; fi + - if [[ "$TRAVIS_PHP_VERSION" = "php*" ]]; then bin/phpunit ; fi From 6049d953e19088bd1cec94a23e65c58d92ba8f68 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 8 Jul 2016 00:00:52 +0200 Subject: [PATCH 063/328] another try with hhvm and travis --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 925d8e42..e3b92fc7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,5 +26,5 @@ install: - php composer.phar install --dev --no-interaction --ignore-platform-reqs script: - - if [[ "$TRAVIS_PHP_VERSION" = "hhv*" ]]; then php -d hhvm.php7.all=1 bin/phpunit ; fi - - if [[ "$TRAVIS_PHP_VERSION" = "php*" ]]; then bin/phpunit ; fi + - if [[ "${TRAVIS_PHP_VERSION}" = "hhv*" ]]; then php -d hhvm.php7.all=1 bin/phpunit ; fi + - if [[ "${TRAVIS_PHP_VERSION}" = "php*" ]]; then bin/phpunit ; fi From 0213208a9684ed72d05d9b81bca9fb1261db567e Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 8 Jul 2016 00:03:22 +0200 Subject: [PATCH 064/328] remove hhvm from travis --- .travis.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index e3b92fc7..ea7c1c84 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,9 +7,6 @@ matrix: - os: linux php: '7.0' - - os: linux - php: hhvm - - os: osx osx_image: xcode7.3 language: generic @@ -26,5 +23,4 @@ install: - php composer.phar install --dev --no-interaction --ignore-platform-reqs script: - - if [[ "${TRAVIS_PHP_VERSION}" = "hhv*" ]]; then php -d hhvm.php7.all=1 bin/phpunit ; fi - - if [[ "${TRAVIS_PHP_VERSION}" = "php*" ]]; then bin/phpunit ; fi + - bin/phpunit From f04cc04da5d0711f2d7854dbcc37c97aa744141b Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 10 Jul 2016 14:13:35 +0200 Subject: [PATCH 065/328] create StratifiedRandomSplit for cross validation --- src/Phpml/CrossValidation/RandomSplit.php | 83 +--------------- src/Phpml/CrossValidation/Split.php | 94 +++++++++++++++++++ .../CrossValidation/StratifiedRandomSplit.php | 62 ++++++++++++ .../StratifiedRandomSplitTest.php | 65 +++++++++++++ 4 files changed, 225 insertions(+), 79 deletions(-) create mode 100644 src/Phpml/CrossValidation/Split.php create mode 100644 src/Phpml/CrossValidation/StratifiedRandomSplit.php create mode 100644 tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php diff --git a/src/Phpml/CrossValidation/RandomSplit.php b/src/Phpml/CrossValidation/RandomSplit.php index 92de9763..c1e709ee 100644 --- a/src/Phpml/CrossValidation/RandomSplit.php +++ b/src/Phpml/CrossValidation/RandomSplit.php @@ -5,101 +5,26 @@ namespace Phpml\CrossValidation; use Phpml\Dataset\Dataset; -use Phpml\Exception\InvalidArgumentException; -class RandomSplit +class RandomSplit extends Split { - /** - * @var array - */ - private $trainSamples = []; - - /** - * @var array - */ - private $testSamples = []; - - /** - * @var array - */ - private $trainLabels = []; - - /** - * @var array - */ - private $testLabels = []; - /** * @param Dataset $dataset * @param float $testSize - * @param int $seed - * - * @throws InvalidArgumentException */ - public function __construct(Dataset $dataset, float $testSize = 0.3, int $seed = null) + protected function splitDataset(Dataset $dataset, float $testSize) { - if (0 >= $testSize || 1 <= $testSize) { - throw InvalidArgumentException::percentNotInRange('testSize'); - } - $this->seedGenerator($seed); - $samples = $dataset->getSamples(); $labels = $dataset->getTargets(); $datasetSize = count($samples); + $testCount = count($this->testSamples); for ($i = $datasetSize; $i > 0; --$i) { $key = mt_rand(0, $datasetSize - 1); - $setName = count($this->testSamples) / $datasetSize >= $testSize ? 'train' : 'test'; + $setName = (count($this->testSamples) - $testCount) / $datasetSize >= $testSize ? 'train' : 'test'; $this->{$setName.'Samples'}[] = $samples[$key]; $this->{$setName.'Labels'}[] = $labels[$key]; - - $samples = array_values($samples); - $labels = array_values($labels); - } - } - - /** - * @return array - */ - public function getTrainSamples() - { - return $this->trainSamples; - } - - /** - * @return array - */ - public function getTestSamples() - { - return $this->testSamples; - } - - /** - * @return array - */ - public function getTrainLabels() - { - return $this->trainLabels; - } - - /** - * @return array - */ - public function getTestLabels() - { - return $this->testLabels; - } - - /** - * @param int|null $seed - */ - private function seedGenerator(int $seed = null) - { - if (null === $seed) { - mt_srand(); - } else { - mt_srand($seed); } } } diff --git a/src/Phpml/CrossValidation/Split.php b/src/Phpml/CrossValidation/Split.php new file mode 100644 index 00000000..3478f544 --- /dev/null +++ b/src/Phpml/CrossValidation/Split.php @@ -0,0 +1,94 @@ += $testSize || 1 <= $testSize) { + throw InvalidArgumentException::percentNotInRange('testSize'); + } + $this->seedGenerator($seed); + + $this->splitDataset($dataset, $testSize); + } + + abstract protected function splitDataset(Dataset $dataset, float $testSize); + + /** + * @return array + */ + public function getTrainSamples() + { + return $this->trainSamples; + } + + /** + * @return array + */ + public function getTestSamples() + { + return $this->testSamples; + } + + /** + * @return array + */ + public function getTrainLabels() + { + return $this->trainLabels; + } + + /** + * @return array + */ + public function getTestLabels() + { + return $this->testLabels; + } + + /** + * @param int|null $seed + */ + protected function seedGenerator(int $seed = null) + { + if (null === $seed) { + mt_srand(); + } else { + mt_srand($seed); + } + } +} diff --git a/src/Phpml/CrossValidation/StratifiedRandomSplit.php b/src/Phpml/CrossValidation/StratifiedRandomSplit.php new file mode 100644 index 00000000..10af3037 --- /dev/null +++ b/src/Phpml/CrossValidation/StratifiedRandomSplit.php @@ -0,0 +1,62 @@ +splitByTarget($dataset); + + foreach ($datasets as $targetSet) { + parent::splitDataset($targetSet, $testSize); + } + } + + /** + * @param Dataset $dataset + * + * @return Dataset[]|array + */ + private function splitByTarget(Dataset $dataset): array + { + $targets = $dataset->getTargets(); + $samples = $dataset->getSamples(); + + $uniqueTargets = array_unique($targets); + $split = array_combine($uniqueTargets, array_fill(0, count($uniqueTargets), [])); + + foreach ($samples as $key => $sample) { + $split[$targets[$key]][] = $sample; + } + + $datasets = $this->createDatasets($uniqueTargets, $split); + + return $datasets; + } + + /** + * @param array $uniqueTargets + * @param array $split + * + * @return array + */ + private function createDatasets(array $uniqueTargets, array $split): array + { + $datasets = []; + foreach ($uniqueTargets as $target) { + $datasets[$target] = new ArrayDataset($split[$target], array_fill(0, count($split[$target]), $target)); + } + + return $datasets; + } +} diff --git a/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php b/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php new file mode 100644 index 00000000..14802de8 --- /dev/null +++ b/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php @@ -0,0 +1,65 @@ +assertEquals(2, $this->countSamplesByTarget($split->getTestLabels(), 'a')); + $this->assertEquals(2, $this->countSamplesByTarget($split->getTestLabels(), 'b')); + + $split = new StratifiedRandomSplit($dataset, 0.25); + + $this->assertEquals(1, $this->countSamplesByTarget($split->getTestLabels(), 'a')); + $this->assertEquals(1, $this->countSamplesByTarget($split->getTestLabels(), 'b')); + } + + public function testDatasetStratifiedRandomSplitWithEvenDistributionAndNumericTargets() + { + $dataset = new ArrayDataset( + $samples = [[1], [2], [3], [4], [5], [6], [7], [8]], + $labels = [1, 2, 1, 2, 1, 2, 1, 2] + ); + + $split = new StratifiedRandomSplit($dataset, 0.5); + + $this->assertEquals(2, $this->countSamplesByTarget($split->getTestLabels(), 1)); + $this->assertEquals(2, $this->countSamplesByTarget($split->getTestLabels(), 2)); + + $split = new StratifiedRandomSplit($dataset, 0.25); + + $this->assertEquals(1, $this->countSamplesByTarget($split->getTestLabels(), 1)); + $this->assertEquals(1, $this->countSamplesByTarget($split->getTestLabels(), 2)); + } + + /** + * @param $splitTargets + * @param $countTarget + * + * @return int + */ + private function countSamplesByTarget($splitTargets, $countTarget): int + { + $count = 0; + foreach ($splitTargets as $target) { + if ($target === $countTarget) { + ++$count; + } + } + + return $count; + } +} From ee6ea3b85075ad81eaa7a1f0e3e85d6a5efb1556 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Mon, 11 Jul 2016 00:07:07 +0200 Subject: [PATCH 066/328] create docs for StratifiedRandomSplit --- README.md | 1 + docs/index.md | 1 + .../cross-validation/random-split.md | 4 +- .../stratified-random-split.md | 44 +++++++++++++++++++ 4 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 docs/machine-learning/cross-validation/stratified-random-split.md diff --git a/README.md b/README.md index c10cb7b5..1f7d97cf 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ composer require php-ai/php-ml * [Accuracy](http://php-ml.readthedocs.io/en/latest/machine-learning/metric/accuracy/) * Cross Validation * [Random Split](http://php-ml.readthedocs.io/en/latest/machine-learning/cross-validation/random-split/) + * [Stratified Random Split](http://php-ml.readthedocs.io/en/latest/machine-learning/cross-validation/stratified-random-split/) * Preprocessing * [Normalization](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/normalization/) * [Imputation missing values](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/imputation-missing-values/) diff --git a/docs/index.md b/docs/index.md index db3c32bc..7943c386 100644 --- a/docs/index.md +++ b/docs/index.md @@ -50,6 +50,7 @@ composer require php-ai/php-ml * [Accuracy](http://php-ml.readthedocs.io/en/latest/machine-learning/metric/accuracy/) * Cross Validation * [Random Split](http://php-ml.readthedocs.io/en/latest/machine-learning/cross-validation/random-split/) + * [Stratified Random Split](http://php-ml.readthedocs.io/en/latest/machine-learning/cross-validation/stratified-random-split/) * Preprocessing * [Normalization](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/normalization/) * [Imputation missing values](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/imputation-missing-values/) diff --git a/docs/machine-learning/cross-validation/random-split.md b/docs/machine-learning/cross-validation/random-split.md index 464f0dbc..edfdded5 100644 --- a/docs/machine-learning/cross-validation/random-split.md +++ b/docs/machine-learning/cross-validation/random-split.md @@ -1,4 +1,4 @@ -# RandomSplit +# Random Split One of the simplest methods from Cross-validation is implemented as `RandomSpilt` class. Samples are split to two groups: train group and test group. You can adjust number of samples in each group. @@ -6,7 +6,7 @@ One of the simplest methods from Cross-validation is implemented as `RandomSpilt * $dataset - object that implements `Dataset` interface * $testSize - a fraction of test split (float, from 0 to 1, default: 0.3) -* $seed - seed for random generator (for tests) +* $seed - seed for random generator (e.g. for tests) ``` $randomSplit = new RandomSplit($dataset, 0.2); diff --git a/docs/machine-learning/cross-validation/stratified-random-split.md b/docs/machine-learning/cross-validation/stratified-random-split.md new file mode 100644 index 00000000..d3f53be0 --- /dev/null +++ b/docs/machine-learning/cross-validation/stratified-random-split.md @@ -0,0 +1,44 @@ +# Stratified Random Split + +Analogously to `RandomSpilt` class samples are split to two groups: train group and test group. +Distribution of samples takes into account their targets and trying to divide them equally. +You can adjust number of samples in each group. + +### Constructor Parameters + +* $dataset - object that implements `Dataset` interface +* $testSize - a fraction of test split (float, from 0 to 1, default: 0.3) +* $seed - seed for random generator (e.g. for tests) + +``` +$split = new StratifiedRandomSplit($dataset, 0.2); +``` + +### Samples and labels groups + +To get samples or labels from test and train group you can use getters: + +``` +$dataset = new StratifiedRandomSplit($dataset, 0.3, 1234); + +// train group +$dataset->getTrainSamples(); +$dataset->getTrainLabels(); + +// test group +$dataset->getTestSamples(); +$dataset->getTestLabels(); +``` + +### Example + +``` +$dataset = new ArrayDataset( + $samples = [[1], [2], [3], [4], [5], [6], [7], [8]], + $targets = ['a', 'a', 'a', 'a', 'b', 'b', 'b', 'b'] +); + +$split = new StratifiedRandomSplit($dataset, 0.5); +``` + +Split will have equals amount of each target. Two of the target `a` and two of `b`. From 76974e10a51891922eebe3d9fff17cec7872832e Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Mon, 11 Jul 2016 10:05:02 +0200 Subject: [PATCH 067/328] add link to Stratified Random Split in mkdocs --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index 68e8b974..dcf5071b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -16,6 +16,7 @@ pages: - Accuracy: machine-learning/metric/accuracy.md - Cross Validation: - RandomSplit: machine-learning/cross-validation/random-split.md + - Stratified Random Split: machine-learning/cross-validation/stratified-random-split.md - Preprocessing: - Normalization: machine-learning/preprocessing/normalization.md - Imputation missing values: machine-learning/preprocessing/imputation-missing-values.md From 212be20fe7e3c60c541ebd9d40934750e1af80e8 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Mon, 11 Jul 2016 21:12:49 +0200 Subject: [PATCH 068/328] create changelog --- CHANGELOG.md | 13 +++++++++++++ docs/index.md | 3 ++- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..5c800501 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +CHANGELOG +========= + +This changelog references the relevant changes done in PHP-ML library. + +* 0.2.0 (in progress) + * feature [Cross Validation] Stratified Random Split - equal distribution for targets in split + * feature [General] Documentation - add missing pages and fix links + +* 0.1.0 (2016-07-08) + * first develop release + * base tools for Machine Learning: Algorithms, Cross Validation, Preprocessing, Feature Extraction + * bug [General] #7 - PHP-ML doesn't work on Mac diff --git a/docs/index.md b/docs/index.md index 7943c386..dd444a3e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -37,7 +37,7 @@ composer require php-ai/php-ml ## Features * Classification - * [SVC](http://php-ml.readthedocs.io/en/latest/machine-learning/classification/svc/) + * [SVC](machine-learning/classification/svc/) * [k-Nearest Neighbors](http://php-ml.readthedocs.io/en/latest/machine-learning/classification/k-nearest-neighbors/) * [Naive Bayes](http://php-ml.readthedocs.io/en/latest/machine-learning/classification/naive-bayes/) * Regression @@ -46,6 +46,7 @@ composer require php-ai/php-ml * Clustering * [k-Means](http://php-ml.readthedocs.io/en/latest/machine-learning/clustering/k-means/) * [DBSCAN](http://php-ml.readthedocs.io/en/latest/machine-learning/clustering/dbscan/) +* * Metric * [Accuracy](http://php-ml.readthedocs.io/en/latest/machine-learning/metric/accuracy/) * Cross Validation From bb35d045ba9d400a0ccf8f1f65dfac6176dc46e7 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 12 Jul 2016 00:00:17 +0200 Subject: [PATCH 069/328] add docs for Pipeline --- README.md | 2 + docs/index.md | 41 +++++++------- docs/machine-learning/workflow/pipeline.md | 65 ++++++++++++++++++++++ mkdocs.yml | 2 + 4 files changed, 90 insertions(+), 20 deletions(-) create mode 100644 docs/machine-learning/workflow/pipeline.md diff --git a/README.md b/README.md index 1f7d97cf..24c1d53e 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,8 @@ composer require php-ai/php-ml * [DBSCAN](http://php-ml.readthedocs.io/en/latest/machine-learning/clustering/dbscan/) * Metric * [Accuracy](http://php-ml.readthedocs.io/en/latest/machine-learning/metric/accuracy/) +* Workflow + * [Pipeline](http://php-ml.readthedocs.io/en/latest/machine-learning/workflow/pipeline) * Cross Validation * [Random Split](http://php-ml.readthedocs.io/en/latest/machine-learning/cross-validation/random-split/) * [Stratified Random Split](http://php-ml.readthedocs.io/en/latest/machine-learning/cross-validation/stratified-random-split/) diff --git a/docs/index.md b/docs/index.md index dd444a3e..e1cde6e3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -38,35 +38,36 @@ composer require php-ai/php-ml * Classification * [SVC](machine-learning/classification/svc/) - * [k-Nearest Neighbors](http://php-ml.readthedocs.io/en/latest/machine-learning/classification/k-nearest-neighbors/) - * [Naive Bayes](http://php-ml.readthedocs.io/en/latest/machine-learning/classification/naive-bayes/) + * [k-Nearest Neighbors](machine-learning/classification/k-nearest-neighbors/) + * [Naive Bayes](machine-learning/classification/naive-bayes/) * Regression - * [Least Squares](http://php-ml.readthedocs.io/en/latest/machine-learning/regression/least-squares/) - * [SVR](http://php-ml.readthedocs.io/en/latest/machine-learning/regression/svr/) + * [Least Squares](machine-learning/regression/least-squares/) + * [SVR](machine-learning/regression/svr/) * Clustering - * [k-Means](http://php-ml.readthedocs.io/en/latest/machine-learning/clustering/k-means/) - * [DBSCAN](http://php-ml.readthedocs.io/en/latest/machine-learning/clustering/dbscan/) -* + * [k-Means](machine-learning/clustering/k-means/) + * [DBSCAN](machine-learning/clustering/dbscan/) * Metric - * [Accuracy](http://php-ml.readthedocs.io/en/latest/machine-learning/metric/accuracy/) + * [Accuracy](machine-learning/metric/accuracy/) +* Workflow + * [Pipeline](machine-learning/workflow/pipeline) * Cross Validation - * [Random Split](http://php-ml.readthedocs.io/en/latest/machine-learning/cross-validation/random-split/) - * [Stratified Random Split](http://php-ml.readthedocs.io/en/latest/machine-learning/cross-validation/stratified-random-split/) + * [Random Split](machine-learning/cross-validation/random-split/) + * [Stratified Random Split](machine-learning/cross-validation/stratified-random-split/) * Preprocessing - * [Normalization](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/normalization/) - * [Imputation missing values](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/imputation-missing-values/) + * [Normalization](machine-learning/preprocessing/normalization/) + * [Imputation missing values](machine-learning/preprocessing/imputation-missing-values/) * Feature Extraction - * [Token Count Vectorizer](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-extraction/token-count-vectorizer/) + * [Token Count Vectorizer](machine-learning/feature-extraction/token-count-vectorizer/) * Datasets - * [CSV](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/csv-dataset/) + * [CSV](machine-learning/datasets/csv-dataset/) * Ready to use: - * [Iris](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/demo/iris/) - * [Wine](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/demo/wine/) - * [Glass](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/demo/glass/) + * [Iris](machine-learning/datasets/demo/iris/) + * [Wine](machine-learning/datasets/demo/wine/) + * [Glass](machine-learning/datasets/demo/glass/) * Math - * [Distance](http://php-ml.readthedocs.io/en/latest/math/distance/) - * [Matrix](http://php-ml.readthedocs.io/en/latest/math/matrix/) - * [Statistic](http://php-ml.readthedocs.io/en/latest/math/statistic/) + * [Distance](math/distance/) + * [Matrix](math/matrix/) + * [Statistic](math/statistic/) ## Contribute diff --git a/docs/machine-learning/workflow/pipeline.md b/docs/machine-learning/workflow/pipeline.md new file mode 100644 index 00000000..34465eb5 --- /dev/null +++ b/docs/machine-learning/workflow/pipeline.md @@ -0,0 +1,65 @@ +# Pipeline + +In machine learning, it is common to run a sequence of algorithms to process and learn from dataset. For example: + + * Split each document’s text into tokens. + * Convert each document’s words into a numerical feature vector ([Token Count Vectorizer](machine-learning/feature-extraction/token-count-vectorizer/)). + * Learn a prediction model using the feature vectors and labels. + +PHP-ML represents such a workflow as a Pipeline, which consists sequence of transformers and a estimator. + + +### Constructor Parameters + +* $transformers (array|Transformer[]) - sequence of objects that implements Transformer interface +* $estimator (Estimator) - estimator that can train and predict + +``` +use Phpml\Classification\SVC; +use Phpml\FeatureExtraction\TfIdfTransformer; +use Phpml\Pipeline; + +$transformers = [ + new TfIdfTransformer(), +]; +$estimator = new SVC(); + +$pipeline = new Pipeline($transformers, $estimator); +``` + +### Example + +First our pipeline replace missing value, then normalize samples and finally train SVC estimator. Thus prepared pipeline repeats each transformation step for predicted sample. + +``` +use Phpml\Classification\SVC; +use Phpml\Pipeline; +use Phpml\Preprocessing\Imputer; +use Phpml\Preprocessing\Normalizer; +use Phpml\Preprocessing\Imputer\Strategy\MostFrequentStrategy; + +$transformers = [ + new Imputer(null, new MostFrequentStrategy()), + new Normalizer(), +]; +$estimator = new SVC(); + +$samples = [ + [1, -1, 2], + [2, 0, null], + [null, 1, -1], +]; + +$targets = [ + 4, + 1, + 4, +]; + +$pipeline = new Pipeline($transformers, $estimator); +$pipeline->train($samples, $targets); + +$predicted = $pipeline->predict([[0, 0, 0]]); + +// $predicted == 4 +``` diff --git a/mkdocs.yml b/mkdocs.yml index dcf5071b..4c9fbf6e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -14,6 +14,8 @@ pages: - DBSCAN: machine-learning/clustering/dbscan.md - Metric: - Accuracy: machine-learning/metric/accuracy.md + - Workflow: + - Pipeline: machine-learning/workflow/pipeline.md - Cross Validation: - RandomSplit: machine-learning/cross-validation/random-split.md - Stratified Random Split: machine-learning/cross-validation/stratified-random-split.md From ba8927459c99d801f291c56f5e27560c07538b8c Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 12 Jul 2016 00:11:18 +0200 Subject: [PATCH 070/328] add docs for ConfusionMatrix --- CHANGELOG.md | 6 ++- README.md | 1 + docs/index.md | 1 + .../metric/confusion-matrix.md | 44 +++++++++++++++++++ mkdocs.yml | 1 + 5 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 docs/machine-learning/metric/confusion-matrix.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c800501..81ca8973 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,11 @@ CHANGELOG This changelog references the relevant changes done in PHP-ML library. -* 0.2.0 (in progress) +* 0.2.0 (in plan) + * feature [Dataset] - FileDataset - load dataset from files (folders as targets) + * feature [Metric] - ClassificationReport - report about trained classifier + +* 0.1.1 (2016-07-12) * feature [Cross Validation] Stratified Random Split - equal distribution for targets in split * feature [General] Documentation - add missing pages and fix links diff --git a/README.md b/README.md index 24c1d53e..9602e4d7 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ composer require php-ai/php-ml * [DBSCAN](http://php-ml.readthedocs.io/en/latest/machine-learning/clustering/dbscan/) * Metric * [Accuracy](http://php-ml.readthedocs.io/en/latest/machine-learning/metric/accuracy/) + * [Confusion Matrix](http://php-ml.readthedocs.io/en/latest/machine-learning/metric/confusion-matrix/) * Workflow * [Pipeline](http://php-ml.readthedocs.io/en/latest/machine-learning/workflow/pipeline) * Cross Validation diff --git a/docs/index.md b/docs/index.md index e1cde6e3..2d5589fe 100644 --- a/docs/index.md +++ b/docs/index.md @@ -48,6 +48,7 @@ composer require php-ai/php-ml * [DBSCAN](machine-learning/clustering/dbscan/) * Metric * [Accuracy](machine-learning/metric/accuracy/) + * [Confusion Matrix](machine-learning/metric/confusion-matrix/) * Workflow * [Pipeline](machine-learning/workflow/pipeline) * Cross Validation diff --git a/docs/machine-learning/metric/confusion-matrix.md b/docs/machine-learning/metric/confusion-matrix.md new file mode 100644 index 00000000..b07443ac --- /dev/null +++ b/docs/machine-learning/metric/confusion-matrix.md @@ -0,0 +1,44 @@ +# Confusion Matrix + +Class for compute confusion matrix to evaluate the accuracy of a classification. + +### Example (all targets) + +Compute ConfusionMatrix for all targets. + +``` +use Phpml\Metric\ConfusionMatrix; + +$actualTargets = [2, 0, 2, 2, 0, 1]; +$predictedTargets = [0, 0, 2, 2, 0, 2]; + +$confusionMatrix = ConfusionMatrix::compute($actualTargets, $predictedTargets) + +/* +$confusionMatrix = [ + [2, 0, 0], + [0, 0, 1], + [1, 0, 2], +]; +*/ +``` + +### Example (chosen targets) + +Compute ConfusionMatrix for chosen targets. + +``` +use Phpml\Metric\ConfusionMatrix; + +$actualTargets = ['cat', 'ant', 'cat', 'cat', 'ant', 'bird']; +$predictedTargets = ['ant', 'ant', 'cat', 'cat', 'ant', 'cat']; + +$confusionMatrix = ConfusionMatrix::compute($actualTargets, $predictedTargets, ['ant', 'bird']) + +/* +$confusionMatrix = [ + [2, 0], + [0, 0], +]; +*/ +``` diff --git a/mkdocs.yml b/mkdocs.yml index 4c9fbf6e..71ad898f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -14,6 +14,7 @@ pages: - DBSCAN: machine-learning/clustering/dbscan.md - Metric: - Accuracy: machine-learning/metric/accuracy.md + - Confusion Matrix: machine-learning/metric/confusion-matrix.md - Workflow: - Pipeline: machine-learning/workflow/pipeline.md - Cross Validation: From 7c0767c15a030309ec9d847d2d56e4db206a3acf Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 12 Jul 2016 00:21:34 +0200 Subject: [PATCH 071/328] create docs for tf-idf transformer --- CHANGELOG.md | 2 +- README.md | 1 + docs/index.md | 1 + .../feature-extraction/tf-idf-transformer.md | 42 +++++++++++++++++++ mkdocs.yml | 1 + .../TfIdfTransformerTest.php | 2 +- 6 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 docs/machine-learning/feature-extraction/tf-idf-transformer.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 81ca8973..bc660655 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ This changelog references the relevant changes done in PHP-ML library. * 0.1.1 (2016-07-12) * feature [Cross Validation] Stratified Random Split - equal distribution for targets in split - * feature [General] Documentation - add missing pages and fix links + * feature [General] Documentation - add missing pages (Pipeline, ConfusionMatrix and TfIdfTransformer) and fix links * 0.1.0 (2016-07-08) * first develop release diff --git a/README.md b/README.md index 9602e4d7..61d215ad 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ composer require php-ai/php-ml * [Imputation missing values](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/imputation-missing-values/) * Feature Extraction * [Token Count Vectorizer](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-extraction/token-count-vectorizer/) + * [Tf-idf Transformer](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-extraction/tf-idf-transformer/) * Datasets * [CSV](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/csv-dataset/) * Ready to use: diff --git a/docs/index.md b/docs/index.md index 2d5589fe..c3088e3b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -59,6 +59,7 @@ composer require php-ai/php-ml * [Imputation missing values](machine-learning/preprocessing/imputation-missing-values/) * Feature Extraction * [Token Count Vectorizer](machine-learning/feature-extraction/token-count-vectorizer/) + * [Tf-idf Transformer](machine-learning/feature-extraction/tf-idf-transformer/) * Datasets * [CSV](machine-learning/datasets/csv-dataset/) * Ready to use: diff --git a/docs/machine-learning/feature-extraction/tf-idf-transformer.md b/docs/machine-learning/feature-extraction/tf-idf-transformer.md new file mode 100644 index 00000000..c592b8d6 --- /dev/null +++ b/docs/machine-learning/feature-extraction/tf-idf-transformer.md @@ -0,0 +1,42 @@ +# Tf-idf Transformer + +Tf–idf, short for term frequency–inverse document frequency, is a numerical statistic that is intended to reflect how important a word is to a document in a collection or corpus. + +### Constructor Parameters + +* $samples (array) - samples for fit tf-idf model + +``` +use Phpml\FeatureExtraction\TfIdfTransformer; + +$samples = [ + [1, 2, 4], + [0, 2, 1] +]; + +$transformer = new TfIdfTransformer($samples); +``` + +### Transformation + +To transform a collection of text samples use `transform` method. Example: + +``` +use Phpml\FeatureExtraction\TfIdfTransformer; + +$samples = [ + [0 => 1, 1 => 1, 2 => 2, 3 => 1, 4 => 0, 5 => 0], + [0 => 1, 1 => 1, 2 => 0, 3 => 0, 4 => 2, 5 => 3], +]; + +$transformer = new TfIdfTransformer($samples); +$transformer->transform($samples); + +/* +$samples = [ + [0 => 0, 1 => 0, 2 => 0.602, 3 => 0.301, 4 => 0, 5 => 0], + [0 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 0.602, 5 => 0.903], +]; +*/ + +``` diff --git a/mkdocs.yml b/mkdocs.yml index 71ad898f..2634101d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -25,6 +25,7 @@ pages: - Imputation missing values: machine-learning/preprocessing/imputation-missing-values.md - Feature Extraction: - Token Count Vectorizer: machine-learning/feature-extraction/token-count-vectorizer.md + - Tf-idf Transformer: machine-learning/feature-extraction/tf-idf-transformer.md - Datasets: - Array Dataset: machine-learning/datasets/array-dataset.md - CSV Dataset: machine-learning/datasets/csv-dataset.md diff --git a/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php b/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php index eceaeb3e..116b6087 100644 --- a/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php +++ b/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php @@ -10,7 +10,7 @@ class TfIdfTransformerTest extends \PHPUnit_Framework_TestCase { public function testTfIdfTransformation() { - //https://en.wikipedia.org/wiki/Tf%E2%80%93idf + // https://en.wikipedia.org/wiki/Tf-idf $samples = [ [0 => 1, 1 => 1, 2 => 2, 3 => 1, 4 => 0, 5 => 0], From 9f140d5b6ff611b36a33af620d156b9f5c317cda Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 14 Jul 2016 13:25:11 +0200 Subject: [PATCH 072/328] fix problem with token count vectorizer array order --- .../FeatureExtraction/TokenCountVectorizer.php | 2 ++ .../FeatureExtraction/TokenCountVectorizerTest.php | 14 +++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php index 3ec6af1f..56d63e2a 100644 --- a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php +++ b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php @@ -116,6 +116,8 @@ private function transformSample(string &$sample) } } + ksort($counts); + $sample = $counts; } diff --git a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php index b18db60e..22ff1a93 100644 --- a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php +++ b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php @@ -40,10 +40,10 @@ public function testTransformationWithWhitespaceTokenizer() $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer()); $vectorizer->fit($samples); - $this->assertEquals($vocabulary, $vectorizer->getVocabulary()); + $this->assertSame($vocabulary, $vectorizer->getVocabulary()); $vectorizer->transform($samples); - $this->assertEquals($tokensCounts, $samples); + $this->assertSame($tokensCounts, $samples); } public function testTransformationWithMinimumDocumentTokenCountFrequency() @@ -74,10 +74,10 @@ public function testTransformationWithMinimumDocumentTokenCountFrequency() $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), null, 0.5); $vectorizer->fit($samples); - $this->assertEquals($vocabulary, $vectorizer->getVocabulary()); + $this->assertSame($vocabulary, $vectorizer->getVocabulary()); $vectorizer->transform($samples); - $this->assertEquals($tokensCounts, $samples); + $this->assertSame($tokensCounts, $samples); // word at least once in all samples $samples = [ @@ -96,7 +96,7 @@ public function testTransformationWithMinimumDocumentTokenCountFrequency() $vectorizer->fit($samples); $vectorizer->transform($samples); - $this->assertEquals($tokensCounts, $samples); + $this->assertSame($tokensCounts, $samples); } public function testTransformationWithStopWords() @@ -131,9 +131,9 @@ public function testTransformationWithStopWords() $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), $stopWords); $vectorizer->fit($samples); - $this->assertEquals($vocabulary, $vectorizer->getVocabulary()); + $this->assertSame($vocabulary, $vectorizer->getVocabulary()); $vectorizer->transform($samples); - $this->assertEquals($tokensCounts, $samples); + $this->assertSame($tokensCounts, $samples); } } From e0b560f31da2f685c387e11757a87666be8a0da9 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sat, 16 Jul 2016 23:29:40 +0200 Subject: [PATCH 073/328] create FilesDataset class --- CHANGELOG.md | 3 +- src/Phpml/Dataset/CsvDataset.php | 5 -- src/Phpml/Dataset/FilesDataset.php | 47 +++++++++++++ src/Phpml/Exception/DatasetException.php | 12 +++- tests/Phpml/Dataset/FilesDatasetTest.php | 38 ++++++++++ .../Dataset/Resources/bbc/business/001.txt | 11 +++ .../Dataset/Resources/bbc/business/002.txt | 7 ++ .../Dataset/Resources/bbc/business/003.txt | 7 ++ .../Dataset/Resources/bbc/business/004.txt | 11 +++ .../Dataset/Resources/bbc/business/005.txt | 7 ++ .../Dataset/Resources/bbc/business/006.txt | 7 ++ .../Dataset/Resources/bbc/business/007.txt | 9 +++ .../Dataset/Resources/bbc/business/008.txt | 7 ++ .../Dataset/Resources/bbc/business/009.txt | 9 +++ .../Dataset/Resources/bbc/business/010.txt | 7 ++ .../Resources/bbc/entertainment/001.txt | 7 ++ .../Resources/bbc/entertainment/002.txt | 7 ++ .../Resources/bbc/entertainment/003.txt | 7 ++ .../Resources/bbc/entertainment/004.txt | 7 ++ .../Resources/bbc/entertainment/005.txt | 7 ++ .../Resources/bbc/entertainment/006.txt | 9 +++ .../Resources/bbc/entertainment/007.txt | 9 +++ .../Resources/bbc/entertainment/008.txt | 9 +++ .../Resources/bbc/entertainment/009.txt | 9 +++ .../Resources/bbc/entertainment/010.txt | 9 +++ .../Dataset/Resources/bbc/politics/001.txt | 11 +++ .../Dataset/Resources/bbc/politics/002.txt | 11 +++ .../Dataset/Resources/bbc/politics/003.txt | 15 ++++ .../Dataset/Resources/bbc/politics/004.txt | 9 +++ .../Dataset/Resources/bbc/politics/005.txt | 15 ++++ .../Dataset/Resources/bbc/politics/006.txt | 7 ++ .../Dataset/Resources/bbc/politics/007.txt | 13 ++++ .../Dataset/Resources/bbc/politics/008.txt | 11 +++ .../Dataset/Resources/bbc/politics/009.txt | 13 ++++ .../Dataset/Resources/bbc/politics/010.txt | 7 ++ .../Phpml/Dataset/Resources/bbc/sport/001.txt | 7 ++ .../Phpml/Dataset/Resources/bbc/sport/002.txt | 5 ++ .../Phpml/Dataset/Resources/bbc/sport/003.txt | 7 ++ .../Phpml/Dataset/Resources/bbc/sport/004.txt | 5 ++ .../Phpml/Dataset/Resources/bbc/sport/005.txt | 5 ++ .../Phpml/Dataset/Resources/bbc/sport/006.txt | 5 ++ .../Phpml/Dataset/Resources/bbc/sport/007.txt | 5 ++ .../Phpml/Dataset/Resources/bbc/sport/008.txt | 5 ++ .../Phpml/Dataset/Resources/bbc/sport/009.txt | 15 ++++ .../Phpml/Dataset/Resources/bbc/sport/010.txt | 5 ++ .../Phpml/Dataset/Resources/bbc/tech/001.txt | 19 +++++ .../Phpml/Dataset/Resources/bbc/tech/002.txt | 9 +++ .../Phpml/Dataset/Resources/bbc/tech/003.txt | 9 +++ .../Phpml/Dataset/Resources/bbc/tech/004.txt | 9 +++ .../Phpml/Dataset/Resources/bbc/tech/005.txt | 13 ++++ .../Phpml/Dataset/Resources/bbc/tech/006.txt | 15 ++++ .../Phpml/Dataset/Resources/bbc/tech/007.txt | 7 ++ .../Phpml/Dataset/Resources/bbc/tech/008.txt | 9 +++ .../Phpml/Dataset/Resources/bbc/tech/009.txt | 69 +++++++++++++++++++ .../Phpml/Dataset/Resources/bbc/tech/010.txt | 11 +++ 55 files changed, 605 insertions(+), 8 deletions(-) create mode 100644 src/Phpml/Dataset/FilesDataset.php create mode 100644 tests/Phpml/Dataset/FilesDatasetTest.php create mode 100644 tests/Phpml/Dataset/Resources/bbc/business/001.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/business/002.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/business/003.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/business/004.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/business/005.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/business/006.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/business/007.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/business/008.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/business/009.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/business/010.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/entertainment/001.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/entertainment/002.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/entertainment/003.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/entertainment/004.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/entertainment/005.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/entertainment/006.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/entertainment/007.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/entertainment/008.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/entertainment/009.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/entertainment/010.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/politics/001.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/politics/002.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/politics/003.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/politics/004.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/politics/005.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/politics/006.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/politics/007.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/politics/008.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/politics/009.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/politics/010.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/sport/001.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/sport/002.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/sport/003.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/sport/004.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/sport/005.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/sport/006.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/sport/007.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/sport/008.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/sport/009.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/sport/010.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/tech/001.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/tech/002.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/tech/003.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/tech/004.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/tech/005.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/tech/006.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/tech/007.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/tech/008.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/tech/009.txt create mode 100644 tests/Phpml/Dataset/Resources/bbc/tech/010.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index bc660655..6e70a3e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,9 @@ CHANGELOG This changelog references the relevant changes done in PHP-ML library. * 0.2.0 (in plan) - * feature [Dataset] - FileDataset - load dataset from files (folders as targets) + * feature [Dataset] - FilesDataset - load dataset from files (folder names as targets) * feature [Metric] - ClassificationReport - report about trained classifier + * bug [Feature Extraction] - fix problem with token count vectorizer array order * 0.1.1 (2016-07-12) * feature [Cross Validation] Stratified Random Split - equal distribution for targets in split diff --git a/src/Phpml/Dataset/CsvDataset.php b/src/Phpml/Dataset/CsvDataset.php index 23ac35c1..b552d471 100644 --- a/src/Phpml/Dataset/CsvDataset.php +++ b/src/Phpml/Dataset/CsvDataset.php @@ -8,11 +8,6 @@ class CsvDataset extends ArrayDataset { - /** - * @var string - */ - protected $filepath; - /** * @param string $filepath * @param int $features diff --git a/src/Phpml/Dataset/FilesDataset.php b/src/Phpml/Dataset/FilesDataset.php new file mode 100644 index 00000000..f28e09ba --- /dev/null +++ b/src/Phpml/Dataset/FilesDataset.php @@ -0,0 +1,47 @@ +scanRootPath($rootPath); + } + + /** + * @param string $rootPath + */ + private function scanRootPath(string $rootPath) + { + foreach(glob($rootPath . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR) as $dir) { + $this->scanDir($dir); + } + } + + /** + * @param string $dir + */ + private function scanDir(string $dir) + { + $target = basename($dir); + + foreach(array_filter(glob($dir. DIRECTORY_SEPARATOR . '*'), 'is_file') as $file) { + $this->samples[] = [file_get_contents($file)]; + $this->targets[] = $target; + } + } + +} diff --git a/src/Phpml/Exception/DatasetException.php b/src/Phpml/Exception/DatasetException.php index 7b99e64b..813166b0 100644 --- a/src/Phpml/Exception/DatasetException.php +++ b/src/Phpml/Exception/DatasetException.php @@ -11,11 +11,19 @@ class DatasetException extends \Exception */ public static function missingFile($filepath) { - return new self(sprintf('Dataset file %s missing.', $filepath)); + return new self(sprintf('Dataset file "%s" missing.', $filepath)); + } + + /** + * @return DatasetException + */ + public static function missingFolder($path) + { + return new self(sprintf('Dataset root folder "%s" missing.', $path)); } public static function cantOpenFile($filepath) { - return new self(sprintf('Dataset file %s can\'t be open.', $filepath)); + return new self(sprintf('Dataset file "%s" can\'t be open.', $filepath)); } } diff --git a/tests/Phpml/Dataset/FilesDatasetTest.php b/tests/Phpml/Dataset/FilesDatasetTest.php new file mode 100644 index 00000000..5a5e7e99 --- /dev/null +++ b/tests/Phpml/Dataset/FilesDatasetTest.php @@ -0,0 +1,38 @@ +assertEquals(50, count($dataset->getSamples())); + $this->assertEquals(50, count($dataset->getTargets())); + + $targets = ['business', 'entertainment', 'politics', 'sport', 'tech']; + $this->assertEquals($targets, array_values(array_unique($dataset->getTargets()))); + + $firstSample = file_get_contents($rootPath.'/business/001.txt'); + $this->assertEquals($firstSample, $dataset->getSamples()[0][0]); + + $lastSample = file_get_contents($rootPath.'/tech/010.txt'); + $this->assertEquals($lastSample, $dataset->getSamples()[49][0]); + } + +} diff --git a/tests/Phpml/Dataset/Resources/bbc/business/001.txt b/tests/Phpml/Dataset/Resources/bbc/business/001.txt new file mode 100644 index 00000000..f4e22427 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/business/001.txt @@ -0,0 +1,11 @@ +Ad sales boost Time Warner profit + +Quarterly profits at US media giant TimeWarner jumped 76% to $1.13bn (£600m) for the three months to December, from $639m year-earlier. + +The firm, which is now one of the biggest investors in Google, benefited from sales of high-speed internet connections and higher advert sales. TimeWarner said fourth quarter sales rose 2% to $11.1bn from $10.9bn. Its profits were buoyed by one-off gains which offset a profit dip at Warner Bros, and less users for AOL. + +Time Warner said on Friday that it now owns 8% of search-engine Google. But its own internet business, AOL, had has mixed fortunes. It lost 464,000 subscribers in the fourth quarter profits were lower than in the preceding three quarters. However, the company said AOL's underlying profit before exceptional items rose 8% on the back of stronger internet advertising revenues. It hopes to increase subscribers by offering the online service free to TimeWarner internet customers and will try to sign up AOL's existing customers for high-speed broadband. TimeWarner also has to restate 2000 and 2003 results following a probe by the US Securities Exchange Commission (SEC), which is close to concluding. + +Time Warner's fourth quarter profits were slightly better than analysts' expectations. But its film division saw profits slump 27% to $284m, helped by box-office flops Alexander and Catwoman, a sharp contrast to year-earlier, when the third and final film in the Lord of the Rings trilogy boosted results. For the full-year, TimeWarner posted a profit of $3.36bn, up 27% from its 2003 performance, while revenues grew 6.4% to $42.09bn. "Our financial performance was strong, meeting or exceeding all of our full-year objectives and greatly enhancing our flexibility," chairman and chief executive Richard Parsons said. For 2005, TimeWarner is projecting operating earnings growth of around 5%, and also expects higher revenue and wider profit margins. + +TimeWarner is to restate its accounts as part of efforts to resolve an inquiry into AOL by US market regulators. It has already offered to pay $300m to settle charges, in a deal that is under review by the SEC. The company said it was unable to estimate the amount it needed to set aside for legal reserves, which it previously set at $500m. It intends to adjust the way it accounts for a deal with German music publisher Bertelsmann's purchase of a stake in AOL Europe, which it had reported as advertising revenue. It will now book the sale of its stake in AOL Europe as a loss on the value of that stake. diff --git a/tests/Phpml/Dataset/Resources/bbc/business/002.txt b/tests/Phpml/Dataset/Resources/bbc/business/002.txt new file mode 100644 index 00000000..0aa9c6f7 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/business/002.txt @@ -0,0 +1,7 @@ +Dollar gains on Greenspan speech + +The dollar has hit its highest level against the euro in almost three months after the Federal Reserve head said the US trade deficit is set to stabilise. + +And Alan Greenspan highlighted the US government's willingness to curb spending and rising household savings as factors which may help to reduce it. In late trading in New York, the dollar reached $1.2871 against the euro, from $1.2974 on Thursday. Market concerns about the deficit has hit the greenback in recent months. On Friday, Federal Reserve chairman Mr Greenspan's speech in London ahead of the meeting of G7 finance ministers sent the dollar higher after it had earlier tumbled on the back of worse-than-expected US jobs data. "I think the chairman's taking a much more sanguine view on the current account deficit than he's taken for some time," said Robert Sinche, head of currency strategy at Bank of America in New York. "He's taking a longer-term view, laying out a set of conditions under which the current account deficit can improve this year and next." + +Worries about the deficit concerns about China do, however, remain. China's currency remains pegged to the dollar and the US currency's sharp falls in recent months have therefore made Chinese export prices highly competitive. But calls for a shift in Beijing's policy have fallen on deaf ears, despite recent comments in a major Chinese newspaper that the "time is ripe" for a loosening of the peg. The G7 meeting is thought unlikely to produce any meaningful movement in Chinese policy. In the meantime, the US Federal Reserve's decision on 2 February to boost interest rates by a quarter of a point - the sixth such move in as many months - has opened up a differential with European rates. The half-point window, some believe, could be enough to keep US assets looking more attractive, and could help prop up the dollar. The recent falls have partly been the result of big budget deficits, as well as the US's yawning current account gap, both of which need to be funded by the buying of US bonds and assets by foreign firms and governments. The White House will announce its budget on Monday, and many commentators believe the deficit will remain at close to half a trillion dollars. diff --git a/tests/Phpml/Dataset/Resources/bbc/business/003.txt b/tests/Phpml/Dataset/Resources/bbc/business/003.txt new file mode 100644 index 00000000..dd696552 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/business/003.txt @@ -0,0 +1,7 @@ +Yukos unit buyer faces loan claim + +The owners of embattled Russian oil giant Yukos are to ask the buyer of its former production unit to pay back a $900m (£479m) loan. + +State-owned Rosneft bought the Yugansk unit for $9.3bn in a sale forced by Russia to part settle a $27.5bn tax claim against Yukos. Yukos' owner Menatep Group says it will ask Rosneft to repay a loan that Yugansk had secured on its assets. Rosneft already faces a similar $540m repayment demand from foreign banks. Legal experts said Rosneft's purchase of Yugansk would include such obligations. "The pledged assets are with Rosneft, so it will have to pay real money to the creditors to avoid seizure of Yugansk assets," said Moscow-based US lawyer Jamie Firestone, who is not connected to the case. Menatep Group's managing director Tim Osborne told the Reuters news agency: "If they default, we will fight them where the rule of law exists under the international arbitration clauses of the credit." + +Rosneft officials were unavailable for comment. But the company has said it intends to take action against Menatep to recover some of the tax claims and debts owed by Yugansk. Yukos had filed for bankruptcy protection in a US court in an attempt to prevent the forced sale of its main production arm. The sale went ahead in December and Yugansk was sold to a little-known shell company which in turn was bought by Rosneft. Yukos claims its downfall was punishment for the political ambitions of its founder Mikhail Khodorkovsky and has vowed to sue any participant in the sale. diff --git a/tests/Phpml/Dataset/Resources/bbc/business/004.txt b/tests/Phpml/Dataset/Resources/bbc/business/004.txt new file mode 100644 index 00000000..f03a2c14 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/business/004.txt @@ -0,0 +1,11 @@ +High fuel prices hit BA's profits + +British Airways has blamed high fuel prices for a 40% drop in profits. + +Reporting its results for the three months to 31 December 2004, the airline made a pre-tax profit of £75m ($141m) compared with £125m a year earlier. Rod Eddington, BA's chief executive, said the results were "respectable" in a third quarter when fuel costs rose by £106m or 47.3%. BA's profits were still better than market expectation of £59m, and it expects a rise in full-year revenues. + +To help offset the increased price of aviation fuel, BA last year introduced a fuel surcharge for passengers. + +In October, it increased this from £6 to £10 one-way for all long-haul flights, while the short-haul surcharge was raised from £2.50 to £4 a leg. Yet aviation analyst Mike Powell of Dresdner Kleinwort Wasserstein says BA's estimated annual surcharge revenues - £160m - will still be way short of its additional fuel costs - a predicted extra £250m. Turnover for the quarter was up 4.3% to £1.97bn, further benefiting from a rise in cargo revenue. Looking ahead to its full year results to March 2005, BA warned that yields - average revenues per passenger - were expected to decline as it continues to lower prices in the face of competition from low-cost carriers. However, it said sales would be better than previously forecast. "For the year to March 2005, the total revenue outlook is slightly better than previous guidance with a 3% to 3.5% improvement anticipated," BA chairman Martin Broughton said. BA had previously forecast a 2% to 3% rise in full-year revenue. + +It also reported on Friday that passenger numbers rose 8.1% in January. Aviation analyst Nick Van den Brul of BNP Paribas described BA's latest quarterly results as "pretty modest". "It is quite good on the revenue side and it shows the impact of fuel surcharges and a positive cargo development, however, operating margins down and cost impact of fuel are very strong," he said. Since the 11 September 2001 attacks in the United States, BA has cut 13,000 jobs as part of a major cost-cutting drive. "Our focus remains on reducing controllable costs and debt whilst continuing to invest in our products," Mr Eddington said. "For example, we have taken delivery of six Airbus A321 aircraft and next month we will start further improvements to our Club World flat beds." BA's shares closed up four pence at 274.5 pence. diff --git a/tests/Phpml/Dataset/Resources/bbc/business/005.txt b/tests/Phpml/Dataset/Resources/bbc/business/005.txt new file mode 100644 index 00000000..ac7bf0be --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/business/005.txt @@ -0,0 +1,7 @@ +Pernod takeover talk lifts Domecq + +Shares in UK drinks and food firm Allied Domecq have risen on speculation that it could be the target of a takeover by France's Pernod Ricard. + +Reports in the Wall Street Journal and the Financial Times suggested that the French spirits firm is considering a bid, but has yet to contact its target. Allied Domecq shares in London rose 4% by 1200 GMT, while Pernod shares in Paris slipped 1.2%. Pernod said it was seeking acquisitions but refused to comment on specifics. + +Pernod's last major purchase was a third of US giant Seagram in 2000, the move which propelled it into the global top three of drinks firms. The other two-thirds of Seagram was bought by market leader Diageo. In terms of market value, Pernod - at 7.5bn euros ($9.7bn) - is about 9% smaller than Allied Domecq, which has a capitalisation of £5.7bn ($10.7bn; 8.2bn euros). Last year Pernod tried to buy Glenmorangie, one of Scotland's premier whisky firms, but lost out to luxury goods firm LVMH. Pernod is home to brands including Chivas Regal Scotch whisky, Havana Club rum and Jacob's Creek wine. Allied Domecq's big names include Malibu rum, Courvoisier brandy, Stolichnaya vodka and Ballantine's whisky - as well as snack food chains such as Dunkin' Donuts and Baskin-Robbins ice cream. The WSJ said that the two were ripe for consolidation, having each dealt with problematic parts of their portfolio. Pernod has reduced the debt it took on to fund the Seagram purchase to just 1.8bn euros, while Allied has improved the performance of its fast-food chains. diff --git a/tests/Phpml/Dataset/Resources/bbc/business/006.txt b/tests/Phpml/Dataset/Resources/bbc/business/006.txt new file mode 100644 index 00000000..fa78492a --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/business/006.txt @@ -0,0 +1,7 @@ +Japan narrowly escapes recession + +Japan's economy teetered on the brink of a technical recession in the three months to September, figures show. + +Revised figures indicated growth of just 0.1% - and a similar-sized contraction in the previous quarter. On an annual basis, the data suggests annual growth of just 0.2%, suggesting a much more hesitant recovery than had previously been thought. A common technical definition of a recession is two successive quarters of negative growth. + +The government was keen to play down the worrying implications of the data. "I maintain the view that Japan's economy remains in a minor adjustment phase in an upward climb, and we will monitor developments carefully," said economy minister Heizo Takenaka. But in the face of the strengthening yen making exports less competitive and indications of weakening economic conditions ahead, observers were less sanguine. "It's painting a picture of a recovery... much patchier than previously thought," said Paul Sheard, economist at Lehman Brothers in Tokyo. Improvements in the job market apparently have yet to feed through to domestic demand, with private consumption up just 0.2% in the third quarter. diff --git a/tests/Phpml/Dataset/Resources/bbc/business/007.txt b/tests/Phpml/Dataset/Resources/bbc/business/007.txt new file mode 100644 index 00000000..4147eb0b --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/business/007.txt @@ -0,0 +1,9 @@ +Jobs growth still slow in the US + +The US created fewer jobs than expected in January, but a fall in jobseekers pushed the unemployment rate to its lowest level in three years. + +According to Labor Department figures, US firms added only 146,000 jobs in January. The gain in non-farm payrolls was below market expectations of 190,000 new jobs. Nevertheless it was enough to push down the unemployment rate to 5.2%, its lowest level since September 2001. The job gains mean that President Bush can celebrate - albeit by a very fine margin - a net growth in jobs in the US economy in his first term in office. He presided over a net fall in jobs up to last November's Presidential election - the first President to do so since Herbert Hoover. As a result, job creation became a key issue in last year's election. However, when adding December and January's figures, the administration's first term jobs record ended in positive territory. + +The Labor Department also said it had revised down the jobs gains in December 2004, from 157,000 to 133,000. + +Analysts said the growth in new jobs was not as strong as could be expected given the favourable economic conditions. "It suggests that employment is continuing to expand at a moderate pace," said Rick Egelton, deputy chief economist at BMO Financial Group. "We are not getting the boost to employment that we would have got given the low value of the dollar and the still relatively low interest rate environment." "The economy is producing a moderate but not a satisfying amount of job growth," said Ken Mayland, president of ClearView Economics. "That means there are a limited number of new opportunities for workers." diff --git a/tests/Phpml/Dataset/Resources/bbc/business/008.txt b/tests/Phpml/Dataset/Resources/bbc/business/008.txt new file mode 100644 index 00000000..6657036a --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/business/008.txt @@ -0,0 +1,7 @@ +India calls for fair trade rules + +India, which attends the G7 meeting of seven leading industrialised nations on Friday, is unlikely to be cowed by its newcomer status. + +In London on Thursday ahead of the meeting, India's finance minister, lashed out at the restrictive trade policies of the G7 nations. He objected to subsidies on agriculture that make it hard for developing nations like India to compete. He also called for reform of the United Nations, the World Bank and the IMF. + +Palaniappan Chidambaram, India's finance minister, argued that these organisations need to take into account the changing world order, given India and China's integration into the global economy. He said the issue is not globalisation but "the terms of engagement in globalisation." Mr Chidambaram is attending the G7 meeting as part of the G20 group of nations, which account for two thirds of the world's population. At a conference on developing enterprise hosted by UK finance minister Gordon Brown on Friday, he said that he was in favour of floating exchange rates because they help countries cope with economic shocks. "A flexible exchange rate is one more channel for absorbing both positive and negative shocks," he told the conference. India, along with China, Brazil, South Africa and Russia, has been invited to take part in the G7 meeting taking place in London on Friday and Saturday. China is expected to face renewed pressure to abandon its fixed exchange rate, which G7 nations, in particular the US, have blamed for a surge in cheap Chinese exports. "Some countries have tried to use fixed exchange rates. I do not wish to make any judgements," Mr Chidambaram said. Separately, the IMF warned on Thursday that India's budget deficit was too large and would hamper the country's economic growth, which it forecast to be around 6.5% in the year to March 2005. In the year to March 2004, the Indian economy grew by 8.5%. diff --git a/tests/Phpml/Dataset/Resources/bbc/business/009.txt b/tests/Phpml/Dataset/Resources/bbc/business/009.txt new file mode 100644 index 00000000..7345c8da --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/business/009.txt @@ -0,0 +1,9 @@ +Ethiopia's crop production up 24% + +Ethiopia produced 14.27 million tonnes of crops in 2004, 24% higher than in 2003 and 21% more than the average of the past five years, a report says. + +In 2003, crop production totalled 11.49 million tonnes, the joint report from the Food and Agriculture Organisation and the World Food Programme said. Good rains, increased use of fertilizers and improved seeds contributed to the rise in production. Nevertheless, 2.2 million Ethiopians will still need emergency assistance. + +The report calculated emergency food requirements for 2005 to be 387,500 tonnes. On top of that, 89,000 tonnes of fortified blended food and vegetable oil for "targeted supplementary food distributions for a survival programme for children under five and pregnant and lactating women" will be needed. + +In eastern and southern Ethiopia, a prolonged drought has killed crops and drained wells. Last year, a total of 965,000 tonnes of food assistance was needed to help seven million Ethiopians. The Food and Agriculture Organisation (FAO) recommend that the food assistance is bought locally. "Local purchase of cereals for food assistance programmes is recommended as far as possible, so as to assist domestic markets and farmers," said Henri Josserand, chief of FAO's Global Information and Early Warning System. Agriculture is the main economic activity in Ethiopia, representing 45% of gross domestic product. About 80% of Ethiopians depend directly or indirectly on agriculture. diff --git a/tests/Phpml/Dataset/Resources/bbc/business/010.txt b/tests/Phpml/Dataset/Resources/bbc/business/010.txt new file mode 100644 index 00000000..078b6e94 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/business/010.txt @@ -0,0 +1,7 @@ +Court rejects $280bn tobacco case + +A US government claim accusing the country's biggest tobacco companies of covering up the effects of smoking has been thrown out by an appeal court. + +The demand for $280bn (£155bn) - filed by the Clinton administration in 1999 - was rejected in a 2-1 decision. The court in Washington found that the case could not be brought under federal anti-racketeering laws. Among the accused were Altria Group, RJ Reynolds Tobacco, Lorillard Tobacco, Liggett Group and Brown and Williamson. In its case, the government claimed tobacco firms manipulated nicotine levels to increase addiction, targeted teenagers with multi-billion dollar advertising campaigns, lied about the dangers of smoking and ignored research to the contrary. + +Prosecutors wanted the cigarette firms to surrender $280bn in profits accumulated over the past 50 years and impose tougher rules on marketing their products. But the Court of Appeals for the District of Columbia ruled that the US government could not sue the firms under legislation drawn up to counteract Mafia infiltration of business. The tobacco companies deny that they illegally conspired to promote smoking and defraud the public. They also say they have already met many of the government's demands in a landmark $206bn settlement reached with 46 states in 1998. Shares of tobacco companies closed higher after the ruling, with Altria rising 5% and Reynolds showing gains of 4.5%. diff --git a/tests/Phpml/Dataset/Resources/bbc/entertainment/001.txt b/tests/Phpml/Dataset/Resources/bbc/entertainment/001.txt new file mode 100644 index 00000000..aa8cee02 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/entertainment/001.txt @@ -0,0 +1,7 @@ +Gallery unveils interactive tree + +A Christmas tree that can receive text messages has been unveiled at London's Tate Britain art gallery. + +The spruce has an antenna which can receive Bluetooth texts sent by visitors to the Tate. The messages will be "unwrapped" by sculptor Richard Wentworth, who is responsible for decorating the tree with broken plates and light bulbs. It is the 17th year that the gallery has invited an artist to dress their Christmas tree. Artists who have decorated the Tate tree in previous years include Tracey Emin in 2002. + +The plain green Norway spruce is displayed in the gallery's foyer. Its light bulb adornments are dimmed, ordinary domestic ones joined together with string. The plates decorating the branches will be auctioned off for the children's charity ArtWorks. Wentworth worked as an assistant to sculptor Henry Moore in the late 1960s. His reputation as a sculptor grew in the 1980s, while he has been one of the most influential teachers during the last two decades. Wentworth is also known for his photography of mundane, everyday subjects such as a cigarette packet jammed under the wonky leg of a table. diff --git a/tests/Phpml/Dataset/Resources/bbc/entertainment/002.txt b/tests/Phpml/Dataset/Resources/bbc/entertainment/002.txt new file mode 100644 index 00000000..b79825f4 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/entertainment/002.txt @@ -0,0 +1,7 @@ +Jarre joins fairytale celebration + +French musician Jean-Michel Jarre is to perform at a concert in Copenhagen to mark the bicentennial of the birth of writer Hans Christian Andersen. + +Denmark is holding a three-day celebration of the life of the fairy-tale author, with a concert at Parken stadium on 2 April. Other stars are expected to join the line-up in the coming months, and the Danish royal family will attend. "Christian Andersen's fairy tales are timeless and universal," said Jarre. "For all of us, at any age there is always - beyond the pure enjoyment of the tale - a message to learn." There are year-long celebrations planned across the world to celebrate Andersen and his work, which includes The Emperor's New Clothes and The Little Mermaid. Denmark's Crown Prince Frederik and Crown Princess Mary visited New York on Monday to help promote the festivities. The pair were at a Manhattan library to honour US literary critic Harold Bloom "the international icon we thought we knew so well". + +"Bloom recognizes the darker aspects of Andersen's authorship," Prince Frederik said. Bloom is to be formally presented with the Hans Christian Andersen Award this spring in Anderson's hometown of Odense. The royal couple also visited the Hans Christian Anderson School complex, where Queen Mary read The Ugly Duckling to the young audience. Later at a gala dinner, Danish supermodel Helena Christensen was named a Hans Christian Andersen ambassador. Other ambassadors include actors Harvey Keitel and Sir Roger Moore, athlete Cathy Freeman and Brazilian soccer legend Pele. diff --git a/tests/Phpml/Dataset/Resources/bbc/entertainment/003.txt b/tests/Phpml/Dataset/Resources/bbc/entertainment/003.txt new file mode 100644 index 00000000..92bb95af --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/entertainment/003.txt @@ -0,0 +1,7 @@ +Musical treatment for Capra film + +The classic film It's A Wonderful Life is to be turned into a musical by the producer of the controversial hit show Jerry Springer - The Opera. + +Frank Capra's 1946 movie starring James Stewart, is being turned into a £7m musical by producer Jon Thoday. He is working with Steve Brown, who wrote the award-winning musical Spend Spend Spend. A spokeswoman said the plans were in the "very early stages", with no cast, opening date or theatre announced. + +A series of workshops have been held in London, and on Wednesday a cast of singers unveiled the musical to a select group of potential investors. Mr Thoday said the idea of turning the film into a musical had been an ambition of his for almost 20 years. It's a Wonderful Life was based on a short story, The Greatest Gift, by Philip van Doren Stern. Mr Thoday managed to buy the rights to the story from Van Doren Stern's family in 1999, following Mr Brown's success with Spend Spend Spend. He later secured the film rights from Paramount, enabling them to use the title It's A Wonderful Life. diff --git a/tests/Phpml/Dataset/Resources/bbc/entertainment/004.txt b/tests/Phpml/Dataset/Resources/bbc/entertainment/004.txt new file mode 100644 index 00000000..8a4b6572 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/entertainment/004.txt @@ -0,0 +1,7 @@ +Richard and Judy choose top books + +The 10 authors shortlisted for a Richard and Judy book award in 2005 are hoping for a boost in sales following the success of this year's winner. + +The TV couple's interest in the book world coined the term "the Richard & Judy effect" and created the top two best-selling paperbacks of 2004 so far. The finalists for 2005 include Andrew Taylor's The American Boy and Robbie Williams' autobiography Feel. This year's winner, Alice Sebold's The Lovely Bones, sold over one million. Joseph O'Connor's Star of the Sea came second and saw sales increase by 350%. The best read award, on Richard Madeley and Judy Finnigan's Channel 4 show, is part of the British Book Awards. David Mitchell's Booker-shortlisted novel, Cloud Atlas, makes it into this year's top 10 along with several lesser known works. + +"There's no doubt that this year's selection of book club entries is the best yet. If anything, the choice is even wider than last time," said Madeley. "It was very hard to follow last year's extremely successful list, but we think this year's books will do even better," said Richard and Judy executive producer Amanda Ross. "We were spoiled for choice and it was tough getting down to only 10 from the 301 submitted." diff --git a/tests/Phpml/Dataset/Resources/bbc/entertainment/005.txt b/tests/Phpml/Dataset/Resources/bbc/entertainment/005.txt new file mode 100644 index 00000000..e7bc04eb --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/entertainment/005.txt @@ -0,0 +1,7 @@ +Poppins musical gets flying start + +The stage adaptation of children's film Mary Poppins has had its opening night in London's West End. + +Sir Cameron Mackintosh's lavish production, which has cost £9m to bring to the stage, was given a 10-minute standing ovation. Lead actress Laura Michelle Kelly soared over the heads of the audience holding the nanny's trademark umbrella. Technical hitches had prevented Mary Poppins' flight into the auditorium during preview performances. A number of celebrities turned out for the musical's premiere, including actress Barbara Windsor, comic Graham Norton and Sir Richard Attenborough. + +The show's director Richard Eyre issued a warning earlier in the week that the show was unsuitable for children under seven, while under-threes are barred. Mary Poppins was originally created by author Pamela Travers, who is said to have cried when she saw Disney's 1964 film starring Julie Andrews. Travers had intended the story to be a lot darker than the perennial family favourite. Theatre impresario Sir Cameron Mackintosh has said he hopes the musical is a blend of the sweet-natured film and the original book. diff --git a/tests/Phpml/Dataset/Resources/bbc/entertainment/006.txt b/tests/Phpml/Dataset/Resources/bbc/entertainment/006.txt new file mode 100644 index 00000000..03dd2645 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/entertainment/006.txt @@ -0,0 +1,9 @@ +Bennett play takes theatre prizes + +The History Boys by Alan Bennett has been named best new play in the Critics' Circle Theatre Awards. + +Set in a grammar school, the play also earned a best actor prize for star Richard Griffiths as teacher Hector. The Producers was named best musical, Victoria Hamilton was best actress for Suddenly Last Summer and Festen's Rufus Norris was named best director. The History Boys also won the best new comedy title at the Theatregoers' Choice Awards. + +Partly based upon Alan Bennett's experience as a teacher, The History Boys has been at London's National Theatre since last May. The Critics' Circle named Rebecca Lenkiewicz its most promising playwright for The Night Season, and Eddie Redmayne most promising newcomer for The Goat or, Who is Sylvia? + +Paul Rhys was its best Shakespearean performer for Measure for Measure at the National Theatre and Christopher Oram won the design award for Suddenly Last Summer. Both the Critics' Circle and Whatsonstage.com Theatregoers' Choice award winners were announced on Tuesday. Chosen by more than 11,000 theatre fans, the Theatregoers' Choice Awards named US actor Christian Slater best actor for One Flew Over the Cuckoo's Nest. Diana Rigg was best actress for Suddenly Last Summer, Dame Judi Dench was best supporting actress for the RSC's All's Well That Ends Well and The History Boys' Samuel Barnett was best supporting actor. diff --git a/tests/Phpml/Dataset/Resources/bbc/entertainment/007.txt b/tests/Phpml/Dataset/Resources/bbc/entertainment/007.txt new file mode 100644 index 00000000..64efbe11 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/entertainment/007.txt @@ -0,0 +1,9 @@ +Levy tipped for Whitbread prize + +Novelist Andrea Levy is favourite to win the main Whitbread Prize book of the year award, after winning novel of the year with her book Small Island. + +The book has already won the Orange Prize for fiction, and is now 5/4 favourite for the £25,000 Whitbread. Second favourite is a biography of Mary Queen of Scots, by John Guy. A panel of judges including Sir Trevor McDonald, actor Hugh Grant and writer Joanne Harris will decide the overall winner on Tuesday. + +The five writers in line for the award won their respective categories - first novel, novel, biography, poetry and children's book - on 6 January. Small Island, Levy's fourth novel, is set in post-war London and centres on a landlady and her lodgers. One is a Jamaican who joined British troops to fight Hitler but finds life difficult out of uniform when he settles in the UK. "What could have been a didactic or preachy prospect turns out to hilarious, moving humane and eye-popping. It's hard to think of anybody not enjoying it," wrote the judges. The judges called Guy's My Heart is My Own: The Life of Mary Queen of Scots "an impressive and readable piece of scholarship, which cannot fail but leave the reader moved and intrigued by this most tragic and likeable of queens". Guy has published many histories, including one of Tudor England. He is a fellow at Clare College, Cambridge and became a honorary research professor of the University of St Andrews in 2003. + +The other contenders include Susan Fletcher for Eve Green, which won the first novel prize. Fletcher has recently graduated from the University of East Anglia's creative writing course. The fourth book in the running is Corpus, Michael Symmons Roberts' fourth collection of poems. As well as writing poetry, Symmons Roberts also makes documentary films. Geraldine McCaughrean is the final contender, having won the children's fiction category for the third time for Not the End of the World. McCaughrean, who went into magazine publishing after studying teaching, previously won the category in 1987 with A Little Lower than Angels and in 1994 with Gold Dust. diff --git a/tests/Phpml/Dataset/Resources/bbc/entertainment/008.txt b/tests/Phpml/Dataset/Resources/bbc/entertainment/008.txt new file mode 100644 index 00000000..c1dcf1be --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/entertainment/008.txt @@ -0,0 +1,9 @@ +West End to honour finest shows + +The West End is honouring its finest stars and shows at the Evening Standard Theatre Awards in London on Monday. + +The Producers, starring Nathan Lane and Lee Evans, is up for best musical at the ceremony at the National Theatre. It is competing against Sweeney Todd and A Funny Thing Happened on the Way to the Forum for the award. The Goat or Who is Sylvia? by Edward Albee, The Pillowman by Martin McDonagh and Alan Bennett's The History Boys are shortlisted in the best play category. + +Pam Ferris, Victoria Hamilton and Kelly Reilly are nominated for best actress. Ferris - best known for her television roles in programmes such as The Darling Buds of May - has made the shortlist for her role in Notes on Falling Leaves, at the Royal Court Theatre. Meanwhile, Richard Griffiths, who plays Hector in The History Boys at the National Theatre, will battle it out for the best actor award with Douglas Hodge (Dumb Show) and Stanley Townsend (Shining City). The best director shortlist includes Luc Bondy for Cruel and Tender, Simon McBurney for Measure for Measure, and Rufus Norris for Festen. + +Festen is also shortlisted in the best designer category where Ian MacNeil, Jean Kalman and Paul Arditti will be up against Hildegard Bechtler, for Iphigenia at Aulis, and Paul Brown, for False Servant. The Milton Shulman Award for outstanding newcomer will be presented to Dominic Cooper (His Dark Materials and The History Boys), Romola Garai (Calico), Eddie Redmayne (The Goat, or Who is Sylvia?) or Ben Wishaw (Hamlet). And playwrights David Eldridge, Rebecca Lenkiewicz and Owen McCafferty will fight it out for The Charles Wintour Award and a £30,000 bursary. Three 50th Anniversary Special Awards will also be presented to an institution, a playwright and an individual. diff --git a/tests/Phpml/Dataset/Resources/bbc/entertainment/009.txt b/tests/Phpml/Dataset/Resources/bbc/entertainment/009.txt new file mode 100644 index 00000000..7862fc2e --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/entertainment/009.txt @@ -0,0 +1,9 @@ +Da Vinci Code is 'lousy history' + +The plot of an international bestseller that thousands of readers are likely to receive as a Christmas present is 'laughable', a clergyman has said. + +The Da Vinci Code claims Jesus was not crucified, but married Mary Magdalene and died a normal death. It claims this was later covered up by the Church. The Bishop of Durham, the Rt Rev Dr Tom Wright, described the novel as a "great thriller" but "lousy history". The book has sold more than seven million copies worldwide. Despite enjoying Dan Brown's conspiracy theory, the Bishop said there was a lack of evidence to back up its claims. + +Writing his Christmas message in the Northern Echo, the Bishop said: "Conspiracy theories are always fun - fun to invent, fun to read, fun to fantasise about. "Dan Brown is the best writer I've come across in the genre, but anyone who knows anything about 1st century history will see that this underlying material is laughable." A great deal of credible evidence proves the Biblical version of Jesus' life was true, according to the Bishop. "The evidence for Jesus and the origins of Christianity is astonishingly good," he said. "We have literally a hundred times more early manuscripts for the gospels and letters in the New Testament than we have for the main classical authors like Cicero, Virgil and Tacitus. + +"Historical research shows that they present a coherent and thoroughly credible picture of Jesus, with all sorts of incidental details that fit the time when he lived, and don't fit the world of later legend." Brown's book has become a publishing phenomenon, consistently topping book charts in the UK and US. The Da Vinci Code has been translated into 42 languages and has spawned its own cottage industry of publications, including guides on to how to read the book, rebuttals and counter claims. The book, which has become an international best-seller in little over two years, is set to be made into a film starring Tom Hanks. diff --git a/tests/Phpml/Dataset/Resources/bbc/entertainment/010.txt b/tests/Phpml/Dataset/Resources/bbc/entertainment/010.txt new file mode 100644 index 00000000..037c155e --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/entertainment/010.txt @@ -0,0 +1,9 @@ +Uganda bans Vagina Monologues + +Uganda's authorities have banned the play The Vagina Monologues, due to open in the capital, Kampala this weekend. + +The Ugandan Media Council said the performance would not be put on as it promoted and glorified acts such as lesbianism and homosexuality. It said the production could go ahead if the organisers "expunge all the offending parts". But the organisers of the play say it raises awareness of sexual abuse against women. "The play promotes illegal, unnatural sexual acts, homosexuality and prostitution, it should be and is hereby banned," the council's ruling said. + +The show, which has been a controversial sell-out around the world, explores female sexuality and strength through individual women telling their stories through monologues. Some parliamentarians and church leaders are also siding with the Media Council, Uganda's New Vision newspaper reports. "The play is obscene and pornographic although it was under the guise of women's liberation," MP Kefa Ssempgani told parliament. + +But the work's author, US playwright Eve Ensler, says it is all about women's empowerment. "There is obviously some fear of the vagina and saying the word vagina," Ms Ensler told the BBC. "It's not a slang word or dirty word it's a biological, anatomical word." She said the play is being produced and performed by Ugandan women and it is not being forced on them. The four Ugandan NGOs organising the play intended to raise money to campaign to stop violence against women and to raise funds for the war-torn north of the country. "I'm extremely outraged at the hypocrisy," the play's organiser in Uganda, Sarah Mukasa, told the BBC's Focus on Africa programme. "I'm amazed that this country Uganda gives the impression that it is progressive and supports women's rights and the notions of free speech; yet when women want to share their stories the government uses the apparatus of state to shut us up." diff --git a/tests/Phpml/Dataset/Resources/bbc/politics/001.txt b/tests/Phpml/Dataset/Resources/bbc/politics/001.txt new file mode 100644 index 00000000..285893ac --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/politics/001.txt @@ -0,0 +1,11 @@ +Labour plans maternity pay rise + +Maternity pay for new mothers is to rise by £1,400 as part of new proposals announced by the Trade and Industry Secretary Patricia Hewitt. + +It would mean paid leave would be increased to nine months by 2007, Ms Hewitt told GMTV's Sunday programme. Other plans include letting maternity pay be given to fathers and extending rights to parents of older children. The Tories dismissed the maternity pay plan as "desperate", while the Liberal Democrats said it was misdirected. + +Ms Hewitt said: "We have already doubled the length of maternity pay, it was 13 weeks when we were elected, we have already taken it up to 26 weeks. "We are going to extend the pay to nine months by 2007 and the aim is to get it right up to the full 12 months by the end of the next Parliament." She said new mothers were already entitled to 12 months leave, but that many women could not take it as only six of those months were paid. "We have made a firm commitment. We will definitely extend the maternity pay, from the six months where it now is to nine months, that's the extra £1,400." She said ministers would consult on other proposals that could see fathers being allowed to take some of their partner's maternity pay or leave period, or extending the rights of flexible working to carers or parents of older children. The Shadow Secretary of State for the Family, Theresa May, said: "These plans were announced by Gordon Brown in his pre-budget review in December and Tony Blair is now recycling it in his desperate bid to win back women voters." + +She said the Conservatives would announce their proposals closer to the General Election. Liberal Democrat spokeswoman for women Sandra Gidley said: "While mothers would welcome any extra maternity pay the Liberal Democrats feel this money is being misdirected." She said her party would boost maternity pay in the first six months to allow more women to stay at home in that time. + +Ms Hewitt also stressed the plans would be paid for by taxpayers, not employers. But David Frost, director general of the British Chambers of Commerce, warned that many small firms could be "crippled" by the move. "While the majority of any salary costs may be covered by the government's statutory pay, recruitment costs, advertising costs, retraining costs and the strain on the company will not be," he said. Further details of the government's plans will be outlined on Monday. New mothers are currently entitled to 90% of average earnings for the first six weeks after giving birth, followed by £102.80 a week until the baby is six months old. diff --git a/tests/Phpml/Dataset/Resources/bbc/politics/002.txt b/tests/Phpml/Dataset/Resources/bbc/politics/002.txt new file mode 100644 index 00000000..5468695e --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/politics/002.txt @@ -0,0 +1,11 @@ +Watchdog probes e-mail deletions + +The information commissioner says he is urgently asking for details of Cabinet Office orders telling staff to delete e-mails more than three months old. + +Richard Thomas "totally condemned" the deletion of e-mails to prevent their disclosure under freedom of information laws coming into force on 1 January. Government guidance said e-mails should only be deleted if they served "no current purpose", Mr Thomas said. The Tories and the Lib Dems have questioned the timing of the new rules. + +Tory leader Michael Howard has written to Tony Blair demanding an explanation of the new rules on e-mail retention. On Monday Lib Dem constitutional affairs committee chairman Alan Beith warned that the deletion of millions of government e-mails could harm the ability of key probes like the Hutton Inquiry. The timing of the new rules just before the Freedom of Information Act comes into forces was "too unlikely to have been a coincidence", Mr Beith said. But a Cabinet Office spokeswoman said the move was not about the new laws or "the destruction of important records". Mr Beith urged the information commissioner to look at how the "e-mail regime" could "support the freedom of information regime". + +Mr Thomas said: "The new Act of Parliament makes it very clear that to destroy records in order to prevent their disclosure becomes a criminal offence." He said there was already clear guidance on the retention of e-mails contained in a code of practice from the lord chancellor. All e-mails are subject to the freedom of information laws, but the important thing was the content of the e-mail, said Mr Thomas. + +"If in doubt retain, that has been the long-standing principle of the civil service and public authorities. It's only when you've got no further use for the particular record that it may be legitimate to destroy it. "But any deliberate destruction to avoid the possibility of later disclosure is to be totally condemned." The Freedom of Information Act will cover England, Wales and Northern Ireland from next year. Similar measures are being brought in at the same time in Scotland. It provides the public with a right of access to information held by about 100,000 public bodies, subject to various exemptions. Its implementation will be monitored by the information commissioner. diff --git a/tests/Phpml/Dataset/Resources/bbc/politics/003.txt b/tests/Phpml/Dataset/Resources/bbc/politics/003.txt new file mode 100644 index 00000000..d8e1bce1 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/politics/003.txt @@ -0,0 +1,15 @@ +Hewitt decries 'career sexism' + +Plans to extend paid maternity leave beyond six months should be prominent in Labour's election manifesto, the Trade and Industry Secretary has said. + +Patricia Hewitt said the cost of the proposals was being evaluated, but it was an "increasingly high priority" and a "shared goal across government". Ms Hewitt was speaking at a gender and productivity seminar organised by the Equal Opportunities Commission (EOC). Mothers can currently take up to six months' paid leave - and six unpaid. Ms Hewitt told the seminar: "Clearly, one of the things we need to do in the future is to extend the period of payment for maternity leave beyond the first six months into the second six months. "We are looking at how quickly we can do that, because obviously there are cost implications because the taxpayer reimburses the employers for the cost of that." + +Ms Hewitt also announced a new drive to help women who want to work in male dominated sectors, saying sexism at work was still preventing women reaching their full potential. Plans include funding for universities to help female science and engineering graduates find jobs and "taster courses" for men and women in non-traditional jobs. Women in full-time work earn 19% less than men, according to the Equal Opportunities Commission (EOC). + +The minister told delegates that getting rid of "career sexism" was vital to closing the gender pay gap. + +"Career sexism limits opportunities for women of all ages and prevents them from achieving their full potential. "It is simply wrong to assume someone cannot do a job on the grounds of their sex," she said. Earlier, she told BBC Radio 4's Today programme: "What we are talking about here is the fact that about six out of 20 women work in jobs that are low-paid and typically dominated by women, so we have got very segregated employment. "Unfortunately, in some cases, this reflects very old-fashioned and stereotypical ideas about the appropriate jobs for women, or indeed for men. "Career sexism is about saying that engineering, for instance, where only 10% of employees are women, is really a male-dominated industry. Construction is even worse. "But it is also about saying childcare jobs are really there for women and not suitable for men. Career sexism goes both ways." + +She added that while progress had been made, there was still a gap in pay figures. "The average woman working full-time is being paid about 80p for every pound a man is earning. For women working part-time it is 60p." The Department for Trade and Industry will also provide funding to help a new pay experts panel run by the TUC. + +It has been set up to advise hundreds of companies on equal wage policies. Research conducted by the EOC last year revealed that many Britons believe the pay gap between men and women is the result of "natural differences" between the sexes. Women hold less than 10% of the top positions in FTSE 100 companies, the police, the judiciary and trade unions, according to their figures. And retired women have just over half the income of their male counterparts on average. diff --git a/tests/Phpml/Dataset/Resources/bbc/politics/004.txt b/tests/Phpml/Dataset/Resources/bbc/politics/004.txt new file mode 100644 index 00000000..e192dc5f --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/politics/004.txt @@ -0,0 +1,9 @@ +Labour chooses Manchester + +The Labour Party will hold its 2006 autumn conference in Manchester and not Blackpool, it has been confirmed. + +The much trailed decision was ratified by Labour's ruling National Executive Committee in a break with the traditional choice of a seaside venue. It will be the first time since 1917 that the party has chosen Manchester to host the annual event. Blackpool will get the much smaller February spring conference instead in what will be seen as a placatory move. + +For years the main political parties have rotated between Blackpool, Bournemouth and Brighton. And the news the much larger annual conference is not to gather in Blackpool will be seen as a blow in the coastal resort. In 1998 the party said it would not return to Blackpool but did so in 2002. The following year Bournemouth hosted the event before the party signed a two year deal for Brighton to host the autumn conference. + +Colin Asplin, Blackpool Hotel Association said: "We have tried very hard to make sure they come back to Blackpool. "Obviously we have failed in that. I just hope Manchester can handle the crowds. "It amazes me that the Labour Party, which is a working class party, doesn't want to come to the main working class resort in the country." The exact cost to Blackpool in terms of lost revenue for hotel accommodation is not yet known but it is thought that block bookings will be taken at the major Manchester hotels after the official announcement. diff --git a/tests/Phpml/Dataset/Resources/bbc/politics/005.txt b/tests/Phpml/Dataset/Resources/bbc/politics/005.txt new file mode 100644 index 00000000..17748d8e --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/politics/005.txt @@ -0,0 +1,15 @@ +Brown ally rejects Budget spree + +Chancellor Gordon Brown's closest ally has denied suggestions there will be a Budget giveaway on 16 March. + +Ed Balls, ex-chief economic adviser to the Treasury, said there would be no spending spree before polling day. But Mr Balls, a prospective Labour MP, said he was confident the chancellor would meet his fiscal rules. He was speaking as Sir Digby Jones, CBI director general, warned Mr Brown not to be tempted to use any extra cash on pre-election bribes. + +Mr Balls, who stepped down from his Treasury post to stand as a Labour candidate in the election, had suggested that Mr Brown would meet his golden economic rule - "with a margin to spare". He said he hoped more would be done to build on current tax credit rules. + +He also stressed rise in interest rates ahead of an expected May election would not affect the Labour Party's chances of winning. Expectations of a rate rise have gathered pace after figures showed house prices are still rising. Consumer borrowing rose at a near-record pace in January. "If the MPC (the Bank of England's Monetary Policy Committee) were to judge that a rate rise was justified before the election because of the strength of the economy - and I'm not predicting that they will - I do not believe that this will be a big election issue in Britain for Labour," he told a Parliamentary lunch. "This is a big change in our political culture." + +During an interview with BBC Radio 4's Today programme, Mr Balls said he was sure Mr Brown's Budget would not put at risk the stability of the economy. "I don't think we'll see a pre-election spending spree - we certainly did not see that before 2001," he said. + +His assurances came after Sir Digby Jones said stability was all important and any extra cash should be spent on improving workers' skills. His message to the chancellor was: "Please don't give it away in any form of electioneering." Sir Digby added: "I don't think he will. I have to say he has been a prudent chancellor right the way through. Stability is the key word - British business needs boring stability more than anything. "We would say to him 'don't increase your public spending, don't give it away. But if you are going to anywhere, just add something to the competitiveness of Britain, put it into skilling our people'. "That would be a good way to spend any excess." + +Mr Balls refused to say whether Mr Brown would remain as chancellor after the election, amid speculation he will be offered the job of Foreign Secretary. "I think that Gordon Brown wants to be part of the successful Labour government which delivers in the third term for the priorities of the people and sees off a Conservative Party that will take Britain backwards," Mr Balls told Today. Prime Minister Tony Blair has yet to name the date of the election, but most pundits are betting on 5 May. diff --git a/tests/Phpml/Dataset/Resources/bbc/politics/006.txt b/tests/Phpml/Dataset/Resources/bbc/politics/006.txt new file mode 100644 index 00000000..9dc86400 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/politics/006.txt @@ -0,0 +1,7 @@ +'Errors' doomed first Dome sale + +The initial attempt to sell the Millennium Dome failed due to a catalogue of errors, a report by the government's finance watchdog says. + +The report said too many parties were involved in decision-making when the attraction first went on sale after the Millennium exhibition ended. The National Audit Office said the Dome cost taxpayers £28.7m to maintain and sell in the four years after it closed. Finally, a deal to turn it into a sport and entertainment venue was struck. More than £550m could now be returned to the public sector in the wake of the deal to regenerate the site in Greenwich, London. + +The NAO report said that this sale went through because it avoided many of the problems of the previous attempt to sell the Dome. Deputy Prime Minister John Prescott said a good deal had been secured. "Delivery of the many benefits secured through this deal will continue the substantial progress already made at the Millennium Village and elsewhere on the peninsula," he said. But Edward Leigh, who is chairman of the Commons public accounts committee, warned the government would have to work hard to ensure taxpayers would get full benefit from the Dome deal. He said: "This report also shows that the first attempt to sell the Dome proved a complete fiasco. Every arm of government seems to have had a finger in the pie. The process was confused and muddled." He added: "Four years after the Millennium Exhibition closed, the Government finally has a deal to find a use for what has been a white elephant since it closed in a deal that, incredible as it may seem, should bring in some money and provide a benefit for the local area and the country as whole. However, it was more a question of luck that a strong bid turned up after thefirst abortive attempt." NAO head Sir John Bourn said: "In difficult circumstances following the failure of the first competition, English Partnerships and the office of the deputy prime minister have worked hard to get a deal." diff --git a/tests/Phpml/Dataset/Resources/bbc/politics/007.txt b/tests/Phpml/Dataset/Resources/bbc/politics/007.txt new file mode 100644 index 00000000..e17e1923 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/politics/007.txt @@ -0,0 +1,13 @@ +Fox attacks Blair's Tory 'lies' + +Tony Blair lied when he took the UK to war so has no qualms about lying in the election campaign, say the Tories. + +Tory co-chairman Liam Fox was speaking after Mr Blair told Labour members the Tories offered a "hard right agenda". Dr Fox told BBC Radio: "If you are willing to lie about the reasons for going to war, I guess you are going to lie about anything at all." He would not discuss reports the party repaid £500,000 to Lord Ashcroft after he predicted an election defeat. + +The prime minister ratcheted up Labour's pre-election campaigning at the weekend with a helicopter tour of the country and his speech at the party's spring conference. He insisted he did not know the poll date, but it is widely expected to be 5 May. + +In what was seen as a highly personal speech in Gateshead on Sunday, Mr Blair said: "I have the same passion and hunger as when I first walked through the door of 10 Downing Street." He described his relationship with the public as starting euphoric, then struggling to live up to the expectations, and reaching the point of raised voices and "throwing crockery". He warned his supporters against complacency, saying: "It's a fight for the future of our country, it's a fight that for Britain and the people of Britain we have to win." + +Mr Blair said that whether the public chose Michael Howard or Mr Kennedy, it would result in "a Tory government not a Labour government and a country that goes back and does not move forward". Dr Fox accused Mr Blair and other Cabinet ministers of telling lies about their opponents' policies and then attacking the lies. "What we learned at the weekend is what Labour tactics are going to be and it's going to be fear and smear," he told BBC News. The Tory co-chairman attacked Labour's six new pledges as "vacuous" and said Mr Blair was very worried voters would take revenge for his failure to deliver. Dr Fox refused to discuss weekend newspaper reports that the party had repaid £500,000 to former Tory Treasurer Lord Ashcroft after he said the party could not win the election. "We repay loans when they are due but do not comment to individual financial matters," he said, insisting he enjoyed a "warm and constructive" relationship to Lord Ashcroft. + +Meanwhile Lib Dem leader Charles Kennedy is expected to attack Mr Blair's words as he begins a nationwide tour on Monday. Mr Kennedy is accelerating Lib Dem election preparations this week as he visits Manchester, Liverpool, Leicester, Somerset, Basingstoke, Shrewsbury, Dorset and Torbay. He said: "This is three-party politics. In the northern cities, the contest is between Labour and the Liberal Democrats. "In southern and rural seats - especially in the South West - the principal contenders are the Liberal Democrats and the Conservatives, who are out of the running in Scotland and Wales." The Lib Dems accuse Mr Blair of making a "touchy-feely" speech to Labour delegates which will not help him regain public trust. diff --git a/tests/Phpml/Dataset/Resources/bbc/politics/008.txt b/tests/Phpml/Dataset/Resources/bbc/politics/008.txt new file mode 100644 index 00000000..8da64268 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/politics/008.txt @@ -0,0 +1,11 @@ +Women MPs reveal sexist taunts + +Women MPs endure "shocking" levels of sexist abuse at the hands of their male counterparts, a new study shows. + +Male MPs pretended to juggle imaginary breasts and jeered "melons" as women made Commons speeches, researchers from Birkbeck College were told. Labour's Yvette Cooper said she found it hard to persuade Commons officials she was a minister and not a secretary. Some 83 MPs gave their answers in 100 hours of taped interviews for the study "Whose Secretary are You, minister". + +The research team, under Professor Joni Lovenduski, had set out to look at the achievements and experiences of women at Westminster. But what emerged was complaints from MPs of all parties of sexist barracking in the Chamber, sexist insults and patronising assumptions about their abilities. Barbara Follet, one of the so-called "Blair Babes" elected in 1997, told researchers: "I remember some Conservatives - whenever a Labour woman got up to speak they would take their breasts - imaginary breasts - in their hands and wiggle them and say 'melons' as we spoke." Former Liberal Democrat MP Jackie Ballard recalled a stream of remarks from a leading MP on topics such as women's legs or their sexual persuasion. And ex-Tory education secretary Gillian Shepherd remembered how one of her male colleagues called all women "Betty". + +"When I said, 'Look you know my name isn't Betty', he said, 'ah but you're all the same, so I call you all Betty'." Harriet Harman told researchers of the sheer hostility prompted by her advancement to the Cabinet: "Well, you've only succeeded because you're a woman." Another current member of the Cabinet says she was told: "Oh, you've had a very fast rise, who have you been sleeping with?" Even after the great influx of women MPs at the 1997 general election, and greater numbers of women in the Cabinet, female MPs often say they feel stuck on the edge of a male world. + +Liberal Democrat Sarah Teather, the most recent female MP to be elected, told researchers: "Lots of people say it's like an old boys club. "I've always said to me it feels more like a teenage public school - you know a public school full of teenagers." Prof Joni Lovenduski, who conducted the study with the help of Margaret Moran MP and a team of journalists, said she was shocked at the findings. "We expected a bit of this but nothing like this extent. We expected to find a couple of shocking episodes." But she said there was a difference between the experiences of women before the 1997 intake and afterwards. This was mainly because there were more women present in Parliament who were not prepared to "put up with" the sexist attitudes they came across, Prof Lovenduski said. But she added: "Some women, including the women who came in 1997, received extraordinary treatment and I am not convinced that if the number of women changed back to what it was before 1997 that things would not change back. "What I think is shocking to the general public is that these things go on in the House of Commons." The interviews are to be placed in the British Library as a historical record. diff --git a/tests/Phpml/Dataset/Resources/bbc/politics/009.txt b/tests/Phpml/Dataset/Resources/bbc/politics/009.txt new file mode 100644 index 00000000..ef07bbab --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/politics/009.txt @@ -0,0 +1,13 @@ +Campbell: E-mail row 'silly fuss' + +Ex-No 10 media chief Alastair Campbell is at the centre of a new political row over an e-mail containing a four-letter outburst aimed at BBC journalists. + +Mr Campbell sent the missive by mistake to BBC2's Newsnight after it sought to question his role in Labour's controversial poster campaign. He later contacted the show saying the original e-mail had been sent in error and that it was all a "silly fuss". Mr Campbell has recently re-joined Labour's election campaign. + +The e-mail was revealed the day after Peter Mandelson, former Labour minister and now a European Commissioner, warned the BBC to steer away from "demonising" Mr Campbell. Mr Campbell messaged Newsnight after the programme investigated claims that Labour's advertising agency TBWA was blaming him for controversy over its campaign posters. The images, including one of flying pigs and another of what critics claim depicted Tory leader Michael Howard as Fagin, prompted accusations of anti-Semitism, claims denied by Labour. + +Mr Campbell's e-mail, which was apparently intended for a party official, suggested they should get Trevor Beattie, TBWA's boss, to issue a statement. In it, he said: "Just spoke to trev. think tbwa shd give statement to newsnight saying party and agency work together well and nobody here has spoken to standard. Posters done by by tbwa according to political brief. Now fuck off and cover something important you twats!" The e-mail was sent by mistake to Newsnight journalist Andrew McFadyen. Realising his error, Mr Campbell then e-mailed Mr McFadyen pointing out the mistake, but suggesting presenter Jeremy Paxman would have seen the funny side. + +He said: "Not very good at this e-mail Blackberry malarkey. Just looked at log of sent messages, have realised e-mail meant for colleagues at TBWA has gone to you. For the record, first three sentences of email spot on. No row between me and trevor. "Posters done by them according to our brief. I dreamt up flying pigs. Pigs not great but okay in the circs of Tories promising tax cuts and spending rises with the same money. TBWA made production. "Campbell swears shock. Final sentence of earlier e-mail probably a bit colourful and personal considering we have never actually met but I'm sure you share the same sense of humour as your star presenter Mr P. "Never known such a silly fuss since the last silly fuss but there we go. Must look forward not back." + +Later the prime minister's spokesman was asked by journalists about his view on Mr Campbell's use of abusive language. The spokesman said: "The person you are referring to is capable of speaking for himself and he no longer works in government." Foreign Secretary Jack Straw said he had always had "very good and polite relations" with Mr Campbell, who he described as "very talented". But on the former spin doctor's use of language, Mr Straw said: "I do know the odd journalist who has occasionally used the odd word that would probably be inappropriate in some circumstances. Maybe I mix with the wrong kind of journalists." Liam Fox, Tory co-chairman, said the return of Mr Campbell was a sign of new "sinister and underhand tactics" by Labour. diff --git a/tests/Phpml/Dataset/Resources/bbc/politics/010.txt b/tests/Phpml/Dataset/Resources/bbc/politics/010.txt new file mode 100644 index 00000000..8bcedaaa --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/politics/010.txt @@ -0,0 +1,7 @@ +Crucial decision on super-casinos + +A decision on whether to allow Westminster to legislate on super-casinos is set to be made by the Scottish Parliament. + +The government has plans for up to eight Las Vegas style resorts in the UK, one of which is likely to be in Glasgow. Scottish ministers insist they will still have the final say on whether a super-casino will be built in Scotland. But opposition parties say that will not happen in practice. The vote is due to be taken on Wednesday and is expected to be close. + +The Scottish Executive believes that the legislation should be handled by Westminster. The new law will control internet gambling for the first time and is aimed at preventing children from becoming involved. A super-casino in Glasgow could be located at Ibrox or the Scottish Exhibition and Conference Centre. The new gambling bill going through Westminster will allow casino complexes to open to the public, have live entertainment and large numbers of fruit machines with unlimited prizes. But the Scottish National Party and the Tories say the issue of super-casinos should be decided in Scotland and believe the executive is shirking its responsibility. diff --git a/tests/Phpml/Dataset/Resources/bbc/sport/001.txt b/tests/Phpml/Dataset/Resources/bbc/sport/001.txt new file mode 100644 index 00000000..0233bf64 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/sport/001.txt @@ -0,0 +1,7 @@ +Claxton hunting first major medal + +British hurdler Sarah Claxton is confident she can win her first major medal at next month's European Indoor Championships in Madrid. + +The 25-year-old has already smashed the British record over 60m hurdles twice this season, setting a new mark of 7.96 seconds to win the AAAs title. "I am quite confident," said Claxton. "But I take each race as it comes. "As long as I keep up my training but not do too much I think there is a chance of a medal." Claxton has won the national 60m hurdles title for the past three years but has struggled to translate her domestic success to the international stage. Now, the Scotland-born athlete owns the equal fifth-fastest time in the world this year. And at last week's Birmingham Grand Prix, Claxton left European medal favourite Russian Irina Shevchenko trailing in sixth spot. + +For the first time, Claxton has only been preparing for a campaign over the hurdles - which could explain her leap in form. In previous seasons, the 25-year-old also contested the long jump but since moving from Colchester to London she has re-focused her attentions. Claxton will see if her new training regime pays dividends at the European Indoors which take place on 5-6 March. diff --git a/tests/Phpml/Dataset/Resources/bbc/sport/002.txt b/tests/Phpml/Dataset/Resources/bbc/sport/002.txt new file mode 100644 index 00000000..0102893b --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/sport/002.txt @@ -0,0 +1,5 @@ +O'Sullivan could run in Worlds + +Sonia O'Sullivan has indicated that she would like to participate in next month's World Cross Country Championships in St Etienne. + +Athletics Ireland have hinted that the 35-year-old Cobh runner may be included in the official line-up for the event in France on 19-20 March. Provincial teams were selected after last Saturday's Nationals in Santry and will be officially announced this week. O'Sullivan is at present preparing for the London marathon on 17 April. The participation of O'Sullivan, currentily training at her base in Australia, would boost the Ireland team who won the bronze three years agio. The first three at Santry last Saturday, Jolene Byrne, Maria McCambridge and Fionnualla Britton, are automatic selections and will most likely form part of the long-course team. O'Sullivan will also take part in the Bupa Great Ireland Run on 9 April in Dublin. diff --git a/tests/Phpml/Dataset/Resources/bbc/sport/003.txt b/tests/Phpml/Dataset/Resources/bbc/sport/003.txt new file mode 100644 index 00000000..9dcc752b --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/sport/003.txt @@ -0,0 +1,7 @@ +Greene sets sights on world title + +Maurice Greene aims to wipe out the pain of losing his Olympic 100m title in Athens by winning a fourth World Championship crown this summer. + +He had to settle for bronze in Greece behind fellow American Justin Gatlin and Francis Obikwelu of Portugal. "It really hurts to look at that medal. It was my mistake. I lost because of the things I did," said Greene, who races in Birmingham on Friday. "It's never going to happen again. My goal - I'm going to win the worlds." Greene crossed the line just 0.02 seconds behind Gatlin, who won in 9.87 seconds in one of the closest and fastest sprints of all time. But Greene believes he lost the race and his title in the semi-finals. "In my semi-final race, I should have won the race but I was conserving energy. "That's when Francis Obikwelu came up and I took third because I didn't know he was there. "I believe that's what put me in lane seven in the final and, while I was in lane seven, I couldn't feel anything in the race. + +"I just felt like I was running all alone. "I believe if I was in the middle of the race I would have been able to react to people that came ahead of me." Greene was also denied Olympic gold in the 4x100m men's relay when he could not catch Britain's Mark Lewis-Francis on the final leg. The Kansas star is set to go head-to-head with Lewis-Francis again at Friday's Norwich Union Grand Prix. The pair contest the 60m, the distance over which Greene currently holds the world record of 6.39 seconds. He then has another indoor meeting in France before resuming training for the outdoor season and the task of recapturing his world title in Helsinki in August. Greene believes Gatlin will again prove the biggest threat to his ambitions in Finland. But he also admits he faces more than one rival for the world crown. "There's always someone else coming. I think when I was coming up I would say there was me and Ato (Boldon) in the young crowd," Greene said. "Now you've got about five or six young guys coming up at the same time." diff --git a/tests/Phpml/Dataset/Resources/bbc/sport/004.txt b/tests/Phpml/Dataset/Resources/bbc/sport/004.txt new file mode 100644 index 00000000..59195b6a --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/sport/004.txt @@ -0,0 +1,5 @@ +IAAF launches fight against drugs + +The IAAF - athletics' world governing body - has met anti-doping officials, coaches and athletes to co-ordinate the fight against drugs in sport. + +Two task forces have been set up to examine doping and nutrition issues. It was also agreed that a programme to "de-mystify" the issue to athletes, the public and the media was a priority. "Nothing was decided to change things - it was more to have a forum of the stakeholders allowing them to express themselves," said an IAAF spokesman. "Getting everyone together gave us a lot of food for thought." About 60 people attended Sunday's meeting in Monaco, including IAAF chief Lamine Diack and Namibian athlete Frankie Fredericks, now a member of the Athletes' Commission. "I am very happy to see you all, members of the athletics family, respond positively to the IAAF call to sit together and discuss what more we can do in the fight against doping," said Diack. "We are the leading Federation in this field and it is our duty to keep our sport clean." The two task forces will report back to the IAAF Council, at its April meeting in Qatar. diff --git a/tests/Phpml/Dataset/Resources/bbc/sport/005.txt b/tests/Phpml/Dataset/Resources/bbc/sport/005.txt new file mode 100644 index 00000000..f3d9dd45 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/sport/005.txt @@ -0,0 +1,5 @@ +Dibaba breaks 5,000m world record + +Ethiopia's Tirunesh Dibaba set a new world record in winning the women's 5,000m at the Boston Indoor Games. + +Dibaba won in 14 minutes 32.93 seconds to erase the previous world indoor mark of 14:39.29 set by another Ethiopian, Berhane Adera, in Stuttgart last year. But compatriot Kenenisa Bekele's record hopes were dashed when he miscounted his laps in the men's 3,000m and staged his sprint finish a lap too soon. Ireland's Alistair Cragg won in 7:39.89 as Bekele battled to second in 7:41.42. "I didn't want to sit back and get out-kicked," said Cragg. "So I kept on the pace. The plan was to go with 500m to go no matter what, but when Bekele made the mistake that was it. The race was mine." Sweden's Carolina Kluft, the Olympic heptathlon champion, and Slovenia's Jolanda Ceplak had winning performances, too. Kluft took the long jump at 6.63m, while Ceplak easily won the women's 800m in 2:01.52. diff --git a/tests/Phpml/Dataset/Resources/bbc/sport/006.txt b/tests/Phpml/Dataset/Resources/bbc/sport/006.txt new file mode 100644 index 00000000..98d645fe --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/sport/006.txt @@ -0,0 +1,5 @@ +Isinbayeva claims new world best + +Pole vaulter Yelena Isinbayeva broke her own indoor world record by clearing 4.89 metres in Lievin on Saturday. + +It was the Russian's 12th world record of her career and came just a few days after she cleared 4.88m at the Norwich Union Grand Prix in Birmingham. The Olympic champion went on to attempt 5.05m at the meeting on France but failed to clear that height. In the men's 60m, former Olympic 100m champion Maurice Greene could only finish second to Leonard Scott. It was Greene's second consecutive defeat at the hands of his fellow American, who also won in Birmingham last week. "I ran my race perfectly," said Scott, who won in 6.46secs, his best time indoors. "I am happy even if I know that Maurice is a long way from being at his peak at the start of the season." diff --git a/tests/Phpml/Dataset/Resources/bbc/sport/007.txt b/tests/Phpml/Dataset/Resources/bbc/sport/007.txt new file mode 100644 index 00000000..1047180b --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/sport/007.txt @@ -0,0 +1,5 @@ +O'Sullivan commits to Dublin race + +Sonia O'Sullivan will seek to regain her title at the Bupa Great Ireland Run on 9 April in Dublin. + +The 35-year-old was beaten into fourth at last year's event, having won it a year earlier. "I understand she's had a solid winter's training down in Australia after recovering from a minor injury," said race director Matthew Turnbull. Mark Carroll, Irish record holder at 3km, 5km and 10km, will make his debut in the mass participation 10km race. Carroll has stepped up his form in recent weeks and in late January scored an impressive 3,000m victory over leading American Alan Webb in Boston. Carroll will be facing stiff competition from Australian Craig Mottram, winner in Dublin for the last two years. diff --git a/tests/Phpml/Dataset/Resources/bbc/sport/008.txt b/tests/Phpml/Dataset/Resources/bbc/sport/008.txt new file mode 100644 index 00000000..b2b47ae4 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/sport/008.txt @@ -0,0 +1,5 @@ +Hansen 'delays return until 2006' + +British triple jumper Ashia Hansen has ruled out a comeback this year after a setback in her recovery from a bad knee injury, according to reports. + +Hansen, the Commonwealth and European champion, has been sidelined since the European Cup in Poland in June 2004. It was hoped she would be able to return this summer, but the wound from the injury has been very slow to heal. Her coach Aston Moore told the Times: "We're not looking at any sooner than 2006, not as a triple jumper." Moore said Hansen may be able to return to sprinting and long jumping sooner, but there is no short-term prospect of her being involved again in her specialist event. "There was a problem with the wound healing and it set back her rehabilitation by about two months, but that has been solved and we can push ahead now," he said. "The aim is for her to get fit as an athlete - then we will start looking at sprinting and the long jump as an introduction back to the competitive arena." Moore said he is confident Hansen can make it back to top-level competition, though it is unclear if that will be in time for the Commonwealth Games in Melbourne next March, when she will be 34. "It's been a frustrating time for her, but it has not fazed her determination," he added. diff --git a/tests/Phpml/Dataset/Resources/bbc/sport/009.txt b/tests/Phpml/Dataset/Resources/bbc/sport/009.txt new file mode 100644 index 00000000..51232351 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/sport/009.txt @@ -0,0 +1,15 @@ +Off-colour Gardener storms to win + +Britain's Jason Gardener shook off an upset stomach to win the 60m at Sunday's Leipzig International meeting. + +Gardener clocked 6.56 seconds to equal the meeting record and finished well ahead of Germany's Marc Blume, who crossed the line in 6.67 secs. The world indoor champion said: "I got to the airport and my stomach was upset and I was vomiting. I almost went home. "I felt a little better Sunday morning but decided I'd only run in the main race. Then everything went perfectly." Gardener, part of the Great Britain 4x100m quartet that won gold at the Athens Olympics, will now turn his attention to next weekend's Norwich Union European Indoor trials in Sheffield. + +"Given I am still off-colour I know there is plenty more in the tank and I expect to get faster in the next few weeks," he said. "It's just a case of chipping away as I have done in previous years and the results will come." Scotland's Ian Mackie was also in action in Leipzig. He stepped down from his favoured 400m to 200m to finish third in 21.72 secs. Germany's Alexander Kosenkow won the race in 21.07 secs with Dutchman Patrick van Balkom second in 21.58 secs. There were plenty of other senior British athletes showing their indoor form over the weekend. Promising 60m hurdler + +clocked a new UK record of 7.98 seconds at a meeting in Norway. The 24-year-old reached the mark in her heat but had to settle for joint first place with former AAA champion Diane Allahgreen in the final. + +, who broke onto the international scene at the Olympic Games last season, set an indoor personal best of 16.50m in the triple jump at a meeting in Ghent. That leap - 37cm short of Brazilian winner Jadel Gregorio's effort - was good enough to qualify for the European Indoor Championships. At the same meeting, + +finished third in 7.27 seconds in a high-class women's 60m. The event was won by European medal favourite Christine Arron of France while Belgium rival Kim Gevaert was second. Britain's Joice Maduaka finished fifth in 7.35. Olympic bronze heptathlon medallist + +made a low-key return to action at an indoor meeting in Birmingham. The 28-year-old cleared 1.76m to win the high jump and threw 13.86m in the women's shot put. diff --git a/tests/Phpml/Dataset/Resources/bbc/sport/010.txt b/tests/Phpml/Dataset/Resources/bbc/sport/010.txt new file mode 100644 index 00000000..b9fd6544 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/sport/010.txt @@ -0,0 +1,5 @@ +Collins to compete in Birmingham + +World and Commonwealth 100m champion Kim Collins will compete in the 60m at the Norwich Union Grand Prix in Birmingham on 18 February. + +The St Kitts and Nevis star joins British Olympic relay gold medallists Jason Gardener and Mark Lewis-Francis. Sydney Olympic 100m champion and world indoor record holder Maurice Greene and Athens Olympic 100m silver medallist Francis Obikwelu will also take part. Collins ran in Birmingham at the 2003 World Indoor Championships. "I'm looking forward to competing against such a strong field," he said. "I got a great reception form the crowd at the NIA when I won my 60m world indoor silver medal in 2003 and it will be really exciting to return to this venue." The world champion says he's in good shape but he isn't underestimating the home competition. "Jason Gardener and Mark Lewis-Francis are Olympic gold medallists now and I'm sure they'll be aiming to win in front of their home supporters. "I'm looking forward to competing against Britain's best sprinters and I'm sure the 60 metres will be one of the most exciting races of the evening." Collins was sixth in the Olympic final in Athens but is hoping for a better result at the World Championships in Finland this summer. "This will be a big year for me and I plan to defend my 100m world title in Helsinki in August. Before then I want to perform well over 60m indoors and start my year in winning form." diff --git a/tests/Phpml/Dataset/Resources/bbc/tech/001.txt b/tests/Phpml/Dataset/Resources/bbc/tech/001.txt new file mode 100644 index 00000000..acb7e7f7 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/tech/001.txt @@ -0,0 +1,19 @@ +Ink helps drive democracy in Asia + +The Kyrgyz Republic, a small, mountainous state of the former Soviet republic, is using invisible ink and ultraviolet readers in the country's elections as part of a drive to prevent multiple voting. + +This new technology is causing both worries and guarded optimism among different sectors of the population. In an effort to live up to its reputation in the 1990s as "an island of democracy", the Kyrgyz President, Askar Akaev, pushed through the law requiring the use of ink during the upcoming Parliamentary and Presidential elections. The US government agreed to fund all expenses associated with this decision. + +The Kyrgyz Republic is seen by many experts as backsliding from the high point it reached in the mid-1990s with a hastily pushed through referendum in 2003, reducing the legislative branch to one chamber with 75 deputies. The use of ink is only one part of a general effort to show commitment towards more open elections - the German Embassy, the Soros Foundation and the Kyrgyz government have all contributed to purchase transparent ballot boxes. + +The actual technology behind the ink is not that complicated. The ink is sprayed on a person's left thumb. It dries and is not visible under normal light. + +However, the presence of ultraviolet light (of the kind used to verify money) causes the ink to glow with a neon yellow light. At the entrance to each polling station, one election official will scan voter's fingers with UV lamp before allowing them to enter, and every voter will have his/her left thumb sprayed with ink before receiving the ballot. If the ink shows under the UV light the voter will not be allowed to enter the polling station. Likewise, any voter who refuses to be inked will not receive the ballot. These elections are assuming even greater significance because of two large factors - the upcoming parliamentary elections are a prelude to a potentially regime changing presidential election in the Autumn as well as the echo of recent elections in other former Soviet Republics, notably Ukraine and Georgia. The use of ink has been controversial - especially among groups perceived to be pro-government. + +Widely circulated articles compared the use of ink to the rural practice of marking sheep - a still common metaphor in this primarily agricultural society. + +The author of one such article began a petition drive against the use of the ink. The greatest part of the opposition to ink has often been sheer ignorance. Local newspapers have carried stories that the ink is harmful, radioactive or even that the ultraviolet readers may cause health problems. Others, such as the aggressively middle of the road, Coalition of Non-governmental Organizations, have lauded the move as an important step forward. This type of ink has been used in many elections in the world, in countries as varied as Serbia, South Africa, Indonesia and Turkey. The other common type of ink in elections is indelible visible ink - but as the elections in Afghanistan showed, improper use of this type of ink can cause additional problems. The use of "invisible" ink is not without its own problems. In most elections, numerous rumors have spread about it. + +In Serbia, for example, both Christian and Islamic leaders assured their populations that its use was not contrary to religion. Other rumours are associated with how to remove the ink - various soft drinks, solvents and cleaning products are put forward. However, in reality, the ink is very effective at getting under the cuticle of the thumb and difficult to wash off. The ink stays on the finger for at least 72 hours and for up to a week. The use of ink and readers by itself is not a panacea for election ills. The passage of the inking law is, nevertheless, a clear step forward towards free and fair elections." The country's widely watched parliamentary elections are scheduled for 27 February. + +David Mikosz works for the IFES, an international, non-profit organisation that supports the building of democratic societies. diff --git a/tests/Phpml/Dataset/Resources/bbc/tech/002.txt b/tests/Phpml/Dataset/Resources/bbc/tech/002.txt new file mode 100644 index 00000000..4c5decd4 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/tech/002.txt @@ -0,0 +1,9 @@ +China net cafe culture crackdown + +Chinese authorities closed 12,575 net cafes in the closing months of 2004, the country's government said. + +According to the official news agency most of the net cafes were closed down because they were operating illegally. Chinese net cafes operate under a set of strict guidelines and many of those most recently closed broke rules that limit how close they can be to schools. The move is the latest in a series of steps the Chinese government has taken to crack down on what it considers to be immoral net use. + +The official Xinhua News Agency said the crackdown was carried out to create a "safer environment for young people in China". Rules introduced in 2002 demand that net cafes be at least 200 metres away from middle and elementary schools. The hours that children can use net cafes are also tightly regulated. China has long been worried that net cafes are an unhealthy influence on young people. The 12,575 cafes were shut in the three months from October to December. China also tries to dictate the types of computer games people can play to limit the amount of violence people are exposed to. + +Net cafes are hugely popular in China because the relatively high cost of computer hardware means that few people have PCs in their homes. This is not the first time that the Chinese government has moved against net cafes that are not operating within its strict guidelines. All the 100,000 or so net cafes in the country are required to use software that controls what websites users can see. Logs of sites people visit are also kept. Laws on net cafe opening hours and who can use them were introduced in 2002 following a fire at one cafe that killed 25 people. During the crackdown following the blaze authorities moved to clean up net cafes and demanded that all of them get permits to operate. In August 2004 Chinese authorities shut down 700 websites and arrested 224 people in a crackdown on net porn. At the same time it introduced new controls to block overseas sex sites. The Reporters Without Borders group said in a report that Chinese government technologies for e-mail interception and net censorship are among the most highly developed in the world. diff --git a/tests/Phpml/Dataset/Resources/bbc/tech/003.txt b/tests/Phpml/Dataset/Resources/bbc/tech/003.txt new file mode 100644 index 00000000..dbab557e --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/tech/003.txt @@ -0,0 +1,9 @@ +Microsoft seeking spyware trojan + +Microsoft is investigating a trojan program that attempts to switch off the firm's anti-spyware software. + +The spyware tool was only released by Microsoft in the last few weeks and has been downloaded by six million people. Stephen Toulouse, a security manager at Microsoft, said the malicious program was called Bankash-A Trojan and was being sent as an e-mail attachment. Microsoft said it did not believe the program was widespread and recommended users to use an anti-virus program. The program attempts to disable or delete Microsoft's anti-spyware tool and suppress warning messages given to users. + +It may also try to steal online banking passwords or other personal information by tracking users' keystrokes. + +Microsoft said in a statement it is investigating what it called a criminal attack on its software. Earlier this week, Microsoft said it would buy anti-virus software maker Sybari Software to improve its security in its Windows and e-mail software. Microsoft has said it plans to offer its own paid-for anti-virus software but it has not yet set a date for its release. The anti-spyware program being targeted is currently only in beta form and aims to help users find and remove spyware - programs which monitor internet use, causes advert pop-ups and slow a PC's performance. diff --git a/tests/Phpml/Dataset/Resources/bbc/tech/004.txt b/tests/Phpml/Dataset/Resources/bbc/tech/004.txt new file mode 100644 index 00000000..950dd0ef --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/tech/004.txt @@ -0,0 +1,9 @@ +Digital guru floats sub-$100 PC + +Nicholas Negroponte, chairman and founder of MIT's Media Labs, says he is developing a laptop PC that will go on sale for less than $100 (£53). + +He told the BBC World Service programme Go Digital he hoped it would become an education tool in developing countries. He said one laptop per child could be " very important to the development of not just that child but now the whole family, village and neighbourhood". He said the child could use the laptop like a text book. He described the device as a stripped down laptop, which would run a Linux-based operating system, "We have to get the display down to below $20, to do this we need to rear project the image rather than using an ordinary flat panel. + +"The second trick is to get rid of the fat , if you can skinny it down you can gain speed and the ability to use smaller processors and slower memory." The device will probably be exported as a kit of parts to be assembled locally to keep costs down. Mr Negroponte said this was a not for profit venture, though he recognised that the manufacturers of the components would be making money. In 1995 Mr Negroponte published the bestselling Being Digital, now widely seen as predicting the digital age. The concept is based on experiments in the US state of Maine, where children were given laptop computers to take home and do their work on. + +While the idea was popular amongst the children, it initially received some resistance from the teachers and there were problems with laptops getting broken. However, Mr Negroponte has adapted the idea to his own work in Cambodia where he set up two schools together with his wife and gave the children laptops. "We put in 25 laptops three years ago , only one has been broken, the kids cherish these things, it's also a TV a telephone and a games machine, not just a textbook." Mr Negroponte wants the laptops to become more common than mobile phones but conceded this was ambitious. "Nokia make 200 million cell phones a year, so for us to claim we're going to make 200 million laptops is a big number, but we're not talking about doing it in three or five years, we're talking about months." He plans to be distributing them by the end of 2006 and is already in discussion with the Chinese education ministry who are expected to make a large order. "In China they spend $17 per child per year on textbooks. That's for five or six years, so if we can distribute and sell laptops in quantities of one million or more to ministries of education that's cheaper and the marketing overheads go away." diff --git a/tests/Phpml/Dataset/Resources/bbc/tech/005.txt b/tests/Phpml/Dataset/Resources/bbc/tech/005.txt new file mode 100644 index 00000000..bd1caf79 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/tech/005.txt @@ -0,0 +1,13 @@ +Technology gets the creative bug + +The hi-tech and the arts worlds have for some time danced around each other and offered creative and technical help when required. + +Often this help has come in the form of corporate art sponsorship or infrastructure provision. But that dance is growing more intimate as hi-tech firms look to the creative industries for inspiration. And vice versa. UK telco BT is serious about the idea and has launched its Connected World initiative. The idea, says BT, is to shape a "21st Century model" which will help cement the art, technology, and business worlds together. "We are hoping to understand the creative industry that has a natural thirst for broadband technology," said Frank Stone, head of the BT's business sector programmes. He looks after several "centres of excellence" which the telco has set up with other institutions and organisations, one of which is focused on creative industries. + +To mark the initiative's launch, a major international art installation is to open on 15 April in Brussels, with a further exhibit in Madrid later in the summer. They have both been created using the telco's technology that it has been incubating at its research and development arm, including a sophisticated graphics rendering program. Using a 3D graphics engine, the type commonly used in gaming, Bafta-winning artists Langlands & Bell have created a virtual, story-based, 3D model of Brussels' Coudenberg Cellars. + +They have recently been excavated and are thought to be the remnants of Coudenberg Palace, an historical seat of European power. The 3D world can be navigated using a joystick and offers an immersive experience of a landscape that historically had a river running through it until it was bricked up in the 19th Century. "The river was integral to the city's survival for hundreds of years and it was equally essential to the city that it disappeared," said the artists. "We hope that by uncovering the river, we can greater understand the connections between the past and the present, and appreciate the flow of modernity, once concealing, but now revealing the River Senne." In their previous works they used the Quake game graphics engine. The game engine is the core component of a video game because it handles graphics rendering, game AI, and how objects behave and relate to each other in a game. They are so time-consuming and expensive to create, the engines can be licensed out to handle other graphics-intensive games. BT's own engine, Tara (Total Abstract Rendering Architecture) has been in development since 2001 and has been used to recreate virtual interactive models of buildings for planners. It was also used in 2003 in Encounter, an urban-based, pervasive game that combined both virtual play in conjunction with physical, on-the-street action. Because the artists wanted video and interactive elements in their worlds, new features were added to Tara in order to handle the complex data sets. But collaboration between art and digital technology is by no means new, and many keen coders, designers, games makers and animators argue that what they create is art itself. + +As more tools for self-expression are given to the person on the street, enabling people to take photos with a phone and upload them to the web for instance, creativity will become an integral part of technology. The Orange Expressionist exhibition last year, for example, displayed thousands of picture messages from people all over the UK to create an interactive installation. + +Technology as a way of unleashing creativity has massive potential, not least because it gives people something to do with their technology. Big businesses know it is good for them to get in on the creative vein too. The art world is "fantastically rich", said Mr Stone, with creative people and ideas which means traditional companies like BT want to get in with them. Between 1997 and 2002, the creative industry brought £21 billion to London alone. It is an industry that is growing by 6% a year too. The partnership between artists and technologists is part of trying to understand the creative potential of technologies like broadband net, according to Mr Stone. "This is not just about putting art galleries and museums online," he said. "It is about how can everyone have the best seat in house and asking if technology has a role in solving that problem." With broadband penetration reaching 100% in the UK, businesses with a stake in the technology want to give people reasons to want and use it. The creative drive is not purely altruistic obviously. It is about both industries borrowing strategies and creative ideas together which can result in better business practices for creative industries, or more patent ideas for tech companies. "What we are trying to do is have outside-in thinking. "We are creating a future cultural drive for the economy," said Mr Stone. diff --git a/tests/Phpml/Dataset/Resources/bbc/tech/006.txt b/tests/Phpml/Dataset/Resources/bbc/tech/006.txt new file mode 100644 index 00000000..4a3d70e5 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/tech/006.txt @@ -0,0 +1,15 @@ +Wi-fi web reaches farmers in Peru + +A network of community computer centres, linked by wireless technology, is providing a helping hand for poor farmers in Peru. + +The pilot scheme in the Huaral Valley, 80 kilometres north of the capital Lima, aims to offer the 6,000-strong community up-to-date information on agricultural market prices and trends. The Agricultural Information Project for Farmers of the Chancay-Huaral Valley also provides vital links between local organisations in charge of water irrigation, enabling them to coordinate their actions. More than 13,000 rural inhabitants, as well as 18,000 students in the region, will also benefit from the telecoms infrastructure. + +The 14 telecentres uses only free open source software and affordable computer equipment. The network has been three years in the making and was officially inaugurated in September. + +The non-government organisation, Cepes (Peruvian Centre for Social Studies) led the $200,000 project, also backed by local institutions, the Education and Agriculture ministries, and European development organisations. "The plan includes training on computers and internet skills for both operators and users of the system," said Carlos Saldarriaga, technical coordinator at Cepes. Farmers are also taking extra lessons on how to apply the new information to make the most of their plots of land. The Board of Irrigation Users which runs the computer centres, aims to make the network self-sustainable within three years, through the cash generated by using the telecentres as internet cafes. + +One of the key elements of the project is the Agricultural Information System, with its flagship huaral.org website. There, farmers can find the prices for local produce, as well as information on topics ranging from plague prevention to the latest farming techniques. The system also helps the inhabitants of the Chancay-Huaral Valley to organise their vital irrigation systems. "Water is the main element that unites them all. It is a precious element in Peru's coastal areas, because it is so scarce, and therefore it is necessary to have proper irrigation systems to make the most of it," Mr Saldarriaga told the BBC News website. The information network also allows farmers to look beyond their own region, and share experiences with other colleagues from the rest of Peru and even around the world. + +Cepes says the involvement of the farmers has been key in the project's success. "Throughout the last three years, the people have provided a vital thrust to the project; they feel it belongs to them," said Mr Saldarriaga. The community training sessions, attended by an equal number of men and women, have been the perfect showcase for their enthusiasm. "We have had an excellent response, mainly from young people. But we have also had a great feedback when we trained 40 or 50-year old women, who were seeing a computer for the first time in their lives." So far, the Huaral programme promoters say the experience has been very positive, and are already planning on spreading the model among other farmers' organisations in Peru. "This is a pilot project, and we have been very keen on its cloning potential in other places," underlined Mr Saldarriaga. + +The Cepes researcher recalls what happened in Cuyo, a 50-family community with no electricity, during the construction of the local telecentre site. There it was necessary to build a mini-hydraulic dam in order to generate 2kW worth of power for the computers, the communications equipment and the cabin lights. "It was already dark when the technicians realised they didn't have any light bulbs to test the generator, so they turned up to the local store to buy light bulbs," recalls Carlos Saldarriaga. "The logical answer was 'we don't sell any', so they had to wait until the next morning to do the testing." Now, with the wireless network, Cuyo as well as the other communities is no longer isolated. diff --git a/tests/Phpml/Dataset/Resources/bbc/tech/007.txt b/tests/Phpml/Dataset/Resources/bbc/tech/007.txt new file mode 100644 index 00000000..1c9b89bf --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/tech/007.txt @@ -0,0 +1,7 @@ +Microsoft releases bumper patches + +Microsoft has warned PC users to update their systems with the latest security fixes for flaws in Windows programs. + +In its monthly security bulletin, it flagged up eight "critical" security holes which could leave PCs open to attack if left unpatched. The number of holes considered "critical" is more than usual. They affect Windows programs, including Internet Explorer (IE), media player and instant messaging. Four other important fixes were also released. These were considered to be less critical, however. If not updated, either automatically or manually, PC users running the programs could be vulnerable to viruses or other malicious attacks designed to exploit the holes. Many of the flaws could be used by virus writers to take over computers remotely, install programs, change, and delete or see data. + +One of the critical patches Microsoft has made available is an important one that fixes some IE flaws. Stephen Toulouse, a Microsoft security manager, said the flaws were known about, and although the firm had not seen any attacks exploiting the flaw, he did not rule them out. Often, when a critical flaw is announced, spates of viruses follow because home users and businesses leave the flaw unpatched. A further patch fixes a hole in Media Player, Windows Messenger and MSN Messenger which an attacker could use to take control of unprotected machines through .png files. Microsoft announces any vulnerabilities in its software every month. The most important ones are those which are classed as "critical". Its latest releases came the week that the company announced it was to buy security software maker Sybari Software as part of Microsoft's plans to make its own security programs. diff --git a/tests/Phpml/Dataset/Resources/bbc/tech/008.txt b/tests/Phpml/Dataset/Resources/bbc/tech/008.txt new file mode 100644 index 00000000..31359e32 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/tech/008.txt @@ -0,0 +1,9 @@ +Virus poses as Christmas e-mail + +Security firms are warning about a Windows virus disguising itself as an electronic Christmas card. + +The Zafi.D virus translates the Christmas greeting on its subject line into the language of the person receiving infected e-mail. Anti-virus firms speculate that this multilingual ability is helping the malicious program spread widely online. Anti-virus firm Sophos said that 10% of the e-mail currently on the net was infected with the Zafi virus. + +Like many other Windows viruses, Zafi-D plunders Microsoft Outlook for e-mail addresses and then uses mail-sending software to despatch itself across the web to new victims. To be infected users must open up the attachment travelling with the message which bears the code for the malicious bug. The attachment on the e-mail poses as an electronic Christmas card but anyone opening it will simply get a crude image of two smiley faces. + +The virus' subject line says "Merry Christmas" and translates this into one of 15 languages depending of the final suffix of the e-mail address the infected message has been sent to. The message in the body of the e-mail reads: "Happy Holidays" and this too is translated. On infected machines the virus tries to disable anti-virus and firewall software and opens up a backdoor on the PC to hand over control to the writer of the virus. The virus is thought to have spread most widely in South America, Italy, Spain, Bulgaria and Hungary. The original Zafi virus appeared in April this year. "We have seen these hoaxes for several Christmases already, and personally I prefer traditional pen and paper cards, and we recommend this to all our clients too," said Mikko Hypponen, who heads F-Secure's anti-virus team. diff --git a/tests/Phpml/Dataset/Resources/bbc/tech/009.txt b/tests/Phpml/Dataset/Resources/bbc/tech/009.txt new file mode 100644 index 00000000..3af3f256 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/tech/009.txt @@ -0,0 +1,69 @@ +Apple laptop is 'greatest gadget' + +The Apple Powerbook 100 has been chosen as the greatest gadget of all time, by US magazine Mobile PC. + +The 1991 laptop was chosen because it was one of the first "lightweight" portable computers and helped define the layout of all future notebook PCs. The magazine has compiled an all-time top 100 list of gadgets, which includes the Sony Walkman at number three and the 1956 Zenith remote control at two. Gadgets needed moving parts and/or electronics to warrant inclusion. The magazine specified that gadgets also needed to be a "self-contained apparatus that can be used on its own, not a subset of another device". + +"In general we included only items that were potentially mobile," said the magazine. + +"In the end, we tried to get to the heart of what really makes a gadget a gadget," it concluded. The oldest "gadget" in the top 100 is the abacus, which the magazine dates at 190 A.D., and put in 60th place. Other pre-electronic gadgets in the top 100 include the sextant from 1731 (59th position), the marine chronometer from 1761 (42nd position) and the Kodak Brownie camera from 1900 (28th position). The Tivo personal video recorder is the newest device to make the top 10, which also includes the first flash mp3 player (Diamound Multimedia), as well as the first "successful" digital camera (Casio QV-10) and mobile phone (Motorola Startac). The most popular gadget of the moment, the Apple iPod, is at number 12 in the list while the first Sony transistor radio is at number 13. + +Sony's third entry in the top 20 is the CDP-101 CD player from 1983. "Who can forget the crystalline, hiss-free blast of Madonna's Like A Virgin emenating from their first CD player?" asked the magazine. Karl Elsener's knife, the Swiss Army Knife from 1891, is at number 20 in the list. Gadgets which could be said to feature surprisngly low down in the list include the original telephone (23rd), the Nintendo GameBoy (25th), and the Pulsar quartz digital watch (36th). The list also contains plenty of oddities: the Pez sweet dispenser (98th), 1980s toy Tamagotchi (86th) and the bizarre Ronco inside the shell egg scrambler (84th). + +Why worry about mobile phones. Soon they will be subsumed into the PDA's / laptops etc. + +What about the Marine Chronometer? Completely revolutionised navigation for boats and was in use for centuries. For it's time, a technological marvel! + +Sony Net Minidisc! It paved the way for more mp3 player to explode onto the market. I always used my NetMD, and could not go anywhere without it. + +A laptop computer is not a gadget! It's a working tool! + +The Sinclair Executive was the world's first pocket calculator. I think this should be there as well. + +How about the clockwork radio? Or GPS? Or a pocket calculator? All these things are useful to real people, not just PC magazine editors. + +Are the people who created this list insane ? Surely the most important gadget of the modern age is the mobile phone? It has revolutionalised communication, which is more than can be said for a niche market laptop. From outside the modern age, the marine chronometer is the single most important gadget, without which modern transportation systems would not have evolved so quickly. + +Has everyone forgot about the Breville pie maker?? + +An interesting list. Of the electronic gadgets, thousands of journalists in the early 1980s blessed the original noteboook pc - the Tandy 100. The size of A4 paper and light, three weeks on a set of batteries, an excellent keyboard, a modem. A pity Tandy did not make it DOS compatible. + +What's an Apple Powerbook 100 ? It's out of date - not much of a "gadget". Surely it has to be something simple / timeless - the tin opener, Swiss Army Knife, safety razor blade, wristwatch or the thing for taking stones out of horses hooves ? + +It has to be the mobile phone. No other single device has had such an effect on our way of living in such a short space of time. + +The ball point pen has got to be one of the most used and common gadgets ever. Also many might be grateful for the pocket calculator which was a great improvement over the slide rule. + +The Casio pocket calculator that played a simple game and made tinny noises was also a hot gadget in 1980. A true gadget, it could be carried around and shown off. + +All top 10 are electronic toys, so the list is probably a better reflection of the current high-tech obsession than anyhting else. I say this as the Swiss Army Knife only made No 20. + +Sinclair QL a machine far ahead of its time. The first home machine with a true multi-takings OS. Shame the marketing was so bad!!! + +Apple.. a triumph of fashion over... well everything else. + +Utter rubbish. Yes, the Apple laptop and Sony Walkman are classic gadgets. But to call the sextant and the marine chronometer 'gadgets' and rank them as less important than a TV remote control reveals a quite shocking lack of historical perspective. The former literally helped change the world by vastly improving navigation at see. The latter is the seed around which the couch potato culture has developed. No competition. + +I'd also put Apple's Newton and the first Palm Pilot there as the front runners for portable computing, and possibly the Toshiba Libretto for the same reason. I only wish that Vulcan Inc's Flipstart wasn't just vapourware otherwise it would be at the top. + +How did a laptop ever manage to beat off the challenge of the wristwatch or the telephone (mobile or otherwise)? What about radios and TVs? + +The swiss army knife. By far the most useful gadget. I got mine 12 years ago. Still wearing and using it a lot! It stood the test of time. + +Psion Organiser series 3, should be up there. Had a usable qwerty keyboard, removable storage, good set of apps and programmable. Case design was good (batteries in the hinge - a first, I think). Great product innovation. + +The first mobile PC was voted best gadget by readers of...err... mobile PC?! Why do you keep putting these obviously biased lists on your site? It's obviously the mobile phone or remote control, and readers of a less partisan publication would tell you that. + +The Motorola Startac should be Number One. Why? There will be mobile phones long after notebook computers and other gadgets are either gone or integrated in communications devices. + +The Psion series 3c! The first most practical way to carry all your info around... + +I too would back the Sinclair Spectrum - without this little beauty I would never have moved into the world of IT and earn the living that I do now. + +I'd have put the mobile phone high up the list. Probably a Nokia model. + +Sinclair Spectrum - 16k. It plugged into the tv. Games were rubbish but it gave me a taste for programming and that's what I do for a living now. + +I wish more modern notebooks -- even Apple's newest offerings -- were more like the PB100. Particularly disheartening is the demise of the trackball, which has given way to the largely useless "trackpad" which every notebook on the market today uses. They're invariably inaccurate, uncomfortable, and cumbersome to use. + +Congratulations to Apple, a deserved win! diff --git a/tests/Phpml/Dataset/Resources/bbc/tech/010.txt b/tests/Phpml/Dataset/Resources/bbc/tech/010.txt new file mode 100644 index 00000000..3e4bd431 --- /dev/null +++ b/tests/Phpml/Dataset/Resources/bbc/tech/010.txt @@ -0,0 +1,11 @@ +Google's toolbar sparks concern + +Search engine firm Google has released a trial tool which is concerning some net users because it directs people to pre-selected commercial websites. + +The AutoLink feature comes with Google's latest toolbar and provides links in a webpage to Amazon.com if it finds a book's ISBN number on the site. It also links to Google's map service, if there is an address, or to car firm Carfax, if there is a licence plate. Google said the feature, available only in the US, "adds useful links". But some users are concerned that Google's dominant position in the search engine market place could mean it would be giving a competitive edge to firms like Amazon. + +AutoLink works by creating a link to a website based on information contained in a webpage - even if there is no link specified and whether or not the publisher of the page has given permission. + +If a user clicks the AutoLink feature in the Google toolbar then a webpage with a book's unique ISBN number would link directly to Amazon's website. It could mean online libraries that list ISBN book numbers find they are directing users to Amazon.com whether they like it or not. Websites which have paid for advertising on their pages may also be directing people to rival services. Dan Gillmor, founder of Grassroots Media, which supports citizen-based media, said the tool was a "bad idea, and an unfortunate move by a company that is looking to continue its hypergrowth". In a statement Google said the feature was still only in beta, ie trial, stage and that the company welcomed feedback from users. It said: "The user can choose never to click on the AutoLink button, and web pages she views will never be modified. "In addition, the user can choose to disable the AutoLink feature entirely at any time." + +The new tool has been compared to the Smart Tags feature from Microsoft by some users. It was widely criticised by net users and later dropped by Microsoft after concerns over trademark use were raised. Smart Tags allowed Microsoft to link any word on a web page to another site chosen by the company. Google said none of the companies which received AutoLinks had paid for the service. Some users said AutoLink would only be fair if websites had to sign up to allow the feature to work on their pages or if they received revenue for any "click through" to a commercial site. Cory Doctorow, European outreach coordinator for digital civil liberties group Electronic Fronter Foundation, said that Google should not be penalised for its market dominance. "Of course Google should be allowed to direct people to whatever proxies it chooses. "But as an end user I would want to know - 'Can I choose to use this service?, 'How much is Google being paid?', 'Can I substitute my own companies for the ones chosen by Google?'." Mr Doctorow said the only objection would be if users were forced into using AutoLink or "tricked into using the service". From 7abee3061abe229768774cb5d9cc89cad40264ad Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sat, 16 Jul 2016 23:56:52 +0200 Subject: [PATCH 074/328] docs for files dataset and php-cs-fixer --- README.md | 2 + docs/index.md | 2 + .../datasets/files-dataset.md | 57 +++++++++++++++++++ mkdocs.yml | 1 + src/Phpml/Dataset/FilesDataset.php | 8 +-- tests/Phpml/Dataset/FilesDatasetTest.php | 7 ++- 6 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 docs/machine-learning/datasets/files-dataset.md diff --git a/README.md b/README.md index 61d215ad..5b14f0b4 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,9 @@ composer require php-ai/php-ml * [Token Count Vectorizer](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-extraction/token-count-vectorizer/) * [Tf-idf Transformer](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-extraction/tf-idf-transformer/) * Datasets + * [Array](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/array-dataset/) * [CSV](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/csv-dataset/) + * [Files](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/files-dataset/) * Ready to use: * [Iris](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/demo/iris/) * [Wine](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/demo/wine/) diff --git a/docs/index.md b/docs/index.md index c3088e3b..938d73fc 100644 --- a/docs/index.md +++ b/docs/index.md @@ -61,7 +61,9 @@ composer require php-ai/php-ml * [Token Count Vectorizer](machine-learning/feature-extraction/token-count-vectorizer/) * [Tf-idf Transformer](machine-learning/feature-extraction/tf-idf-transformer/) * Datasets + * [Array](machine-learning/datasets/array-dataset/) * [CSV](machine-learning/datasets/csv-dataset/) + * [Files](machine-learning/datasets/files-dataset/) * Ready to use: * [Iris](machine-learning/datasets/demo/iris/) * [Wine](machine-learning/datasets/demo/wine/) diff --git a/docs/machine-learning/datasets/files-dataset.md b/docs/machine-learning/datasets/files-dataset.md new file mode 100644 index 00000000..969610cc --- /dev/null +++ b/docs/machine-learning/datasets/files-dataset.md @@ -0,0 +1,57 @@ +# FilesDataset + +Helper class that loads dataset from files. Use folder names as targets. It extends the `ArrayDataset`. + +### Constructors Parameters + +* $rootPath - (string) path to root folder that contains files dataset + +``` +use Phpml\Dataset\FilesDataset; + +$dataset = new FilesDataset('path/to/data'); +``` + +See [ArrayDataset](machine-learning/datasets/array-dataset/) for more information. + +### Example + +Files structure: + +``` +data + business + 001.txt + 002.txt + ... + entertainment + 001.txt + 002.txt + ... + politics + 001.txt + 002.txt + ... + sport + 001.txt + 002.txt + ... + tech + 001.txt + 002.txt + ... +``` + +Load files data with `FilesDataset`: + +``` +use Phpml\Dataset\FilesDataset; + +$dataset = new FilesDataset('path/to/data'); + +$dataset->getSamples()[0][0] // content from file path/to/data/business/001.txt +$dataset->getTargets()[0] // business + +$dataset->getSamples()[40][0] // content from file path/to/data/tech/001.txt +$dataset->getTargets()[0] // tech +``` diff --git a/mkdocs.yml b/mkdocs.yml index 2634101d..f06a08b2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -29,6 +29,7 @@ pages: - Datasets: - Array Dataset: machine-learning/datasets/array-dataset.md - CSV Dataset: machine-learning/datasets/csv-dataset.md + - Files Dataset: machine-learning/datasets/files-dataset.md - Ready to use datasets: - Iris: machine-learning/datasets/demo/iris.md - Wine: machine-learning/datasets/demo/wine.md diff --git a/src/Phpml/Dataset/FilesDataset.php b/src/Phpml/Dataset/FilesDataset.php index f28e09ba..6897ba14 100644 --- a/src/Phpml/Dataset/FilesDataset.php +++ b/src/Phpml/Dataset/FilesDataset.php @@ -1,5 +1,6 @@ scanDir($dir); } } @@ -38,10 +39,9 @@ private function scanDir(string $dir) { $target = basename($dir); - foreach(array_filter(glob($dir. DIRECTORY_SEPARATOR . '*'), 'is_file') as $file) { + foreach (array_filter(glob($dir.DIRECTORY_SEPARATOR.'*'), 'is_file') as $file) { $this->samples[] = [file_get_contents($file)]; $this->targets[] = $target; } } - } diff --git a/tests/Phpml/Dataset/FilesDatasetTest.php b/tests/Phpml/Dataset/FilesDatasetTest.php index 5a5e7e99..5461c2f8 100644 --- a/tests/Phpml/Dataset/FilesDatasetTest.php +++ b/tests/Phpml/Dataset/FilesDatasetTest.php @@ -31,8 +31,13 @@ public function testLoadFilesDatasetWithBBCData() $firstSample = file_get_contents($rootPath.'/business/001.txt'); $this->assertEquals($firstSample, $dataset->getSamples()[0][0]); + $firstTarget = 'business'; + $this->assertEquals($firstTarget, $dataset->getTargets()[0]); + $lastSample = file_get_contents($rootPath.'/tech/010.txt'); $this->assertEquals($lastSample, $dataset->getSamples()[49][0]); + + $lastTarget = 'tech'; + $this->assertEquals($lastTarget, $dataset->getTargets()[49]); } - } From 76d15e96916558ce1907a9a4f0f1552b92a2f85a Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 17 Jul 2016 00:31:47 +0200 Subject: [PATCH 075/328] add php-ml logo --- CHANGELOG.md | 2 +- README.md | 2 ++ docs/assets/php-ml-logo.png | Bin 0 -> 6313 bytes docs/index.md | 2 ++ 4 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 docs/assets/php-ml-logo.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e70a3e2..6a9a928a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ CHANGELOG This changelog references the relevant changes done in PHP-ML library. -* 0.2.0 (in plan) +* 0.2.0 (in plan/progress) * feature [Dataset] - FilesDataset - load dataset from files (folder names as targets) * feature [Metric] - ClassificationReport - report about trained classifier * bug [Feature Extraction] - fix problem with token count vectorizer array order diff --git a/README.md b/README.md index 5b14f0b4..a3f01ce4 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ [![License](https://poser.pugx.org/php-ai/php-ml/license.svg)](https://packagist.org/packages/php-ai/php-ml) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/quality-score.png?b=develop)](https://scrutinizer-ci.com/g/php-ai/php-ml/?branch=develop) +![PHP-ML - Machine Learning library for PHP](docs/assets/php-ml-logo.png) + Fresh approach to Machine Learning in PHP. Algorithms, Cross Validation, Preprocessing, Feature Extraction and much more in one library. Simple example of classification: diff --git a/docs/assets/php-ml-logo.png b/docs/assets/php-ml-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..5a3766a7ef20ab8dd2c8e8c3ca25a92fb4be47f1 GIT binary patch literal 6313 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 literal 0 HcmV?d00001 diff --git a/docs/index.md b/docs/index.md index 938d73fc..d58f03e3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,6 +6,8 @@ [![License](https://poser.pugx.org/php-ai/php-ml/license.svg)](https://packagist.org/packages/php-ai/php-ml) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/quality-score.png?b=develop)](https://scrutinizer-ci.com/g/php-ai/php-ml/?branch=develop) +![PHP-ML - Machine Learning library for PHP](assets/php-ml-logo.png) + Fresh approach to Machine Learning in PHP. Note that at the moment PHP is not the best choice for machine learning but maybe this will change ... Simple example of classification: From 96654571592eb1e1ae3fe4cc230c47312c777c97 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 19 Jul 2016 21:58:59 +0200 Subject: [PATCH 076/328] implement ClassificationReport class --- src/Phpml/Metric/ClassificationReport.php | 148 ++++++++++++++++++ .../Phpml/Metric/ClassificationReportTest.php | 32 ++++ 2 files changed, 180 insertions(+) create mode 100644 src/Phpml/Metric/ClassificationReport.php create mode 100644 tests/Phpml/Metric/ClassificationReportTest.php diff --git a/src/Phpml/Metric/ClassificationReport.php b/src/Phpml/Metric/ClassificationReport.php new file mode 100644 index 00000000..69209fd5 --- /dev/null +++ b/src/Phpml/Metric/ClassificationReport.php @@ -0,0 +1,148 @@ +support = self::getLabelIndexedArray($actualLabels); + + foreach ($actualLabels as $index => $actual) { + $predicted = $predictedLabels[$index]; + $this->support[$actual]++; + + if($actual === $predicted) { + $truePositive[$actual]++; + } else { + $falsePositive[$predicted]++; + $falseNegative[$actual]++; + } + } + + $this->computeMetrics($truePositive, $falsePositive, $falseNegative); + $this->computeAverage(); + } + + /** + * @return array + */ + public function getPrecision() + { + return $this->precision; + } + + /** + * @return array + */ + public function getRecall() + { + return $this->recall; + } + + /** + * @return array + */ + public function getF1score() + { + return $this->f1score; + } + + /** + * @return array + */ + public function getSupport() + { + return $this->support; + } + + /** + * @return array + */ + public function getAverage() + { + return $this->average; + } + + /** + * @param array $truePositive + * @param array $falsePositive + * @param array $falseNegative + */ + private function computeMetrics(array $truePositive, array $falsePositive, array $falseNegative) + { + foreach ($truePositive as $label => $tp) { + $this->precision[$label] = $tp / ($tp + $falsePositive[$label]); + $this->recall[$label] = $tp / ($tp + $falseNegative[$label]); + $this->f1score[$label] = $this->computeF1Score((float)$this->precision[$label], (float)$this->recall[$label]); + } + } + + private function computeAverage() + { + foreach (['precision', 'recall', 'f1score'] as $metric) { + $values = array_filter($this->$metric); + $this->average[$metric] = array_sum($values) / count($values); + } + } + + /** + * @param float $precision + * @param float $recall + * + * @return float + */ + private function computeF1Score(float $precision, float $recall): float + { + if(0 == ($divider = $precision+$recall)) { + return 0.0; + } + + return 2.0 * (($precision * $recall) / ($divider)); + } + + /** + * @param array $labels + * + * @return array + */ + private static function getLabelIndexedArray(array $labels): array + { + $labels = array_values(array_unique($labels)); + sort($labels); + $labels = array_combine($labels, array_fill(0, count($labels), 0)); + + return $labels; + } + +} diff --git a/tests/Phpml/Metric/ClassificationReportTest.php b/tests/Phpml/Metric/ClassificationReportTest.php new file mode 100644 index 00000000..58520bee --- /dev/null +++ b/tests/Phpml/Metric/ClassificationReportTest.php @@ -0,0 +1,32 @@ + 0.5, 'ant' => 0.0, 'bird' => 1.0]; + $recall = ['cat' => 1.0, 'ant' => 0.0, 'bird' => 0.67]; + $f1score = ['cat' => 0.67, 'ant' => 0.0, 'bird' => 0.80]; + $support = ['cat' => 1, 'ant' => 1, 'bird' => 3]; + $average = ['precision' => 0.75, 'recall' => 0.83, 'f1score' => 0.73]; + + + $this->assertEquals($precision, $report->getPrecision(), '', 0.01); + $this->assertEquals($recall, $report->getRecall(), '', 0.01); + $this->assertEquals($f1score, $report->getF1score(), '', 0.01); + $this->assertEquals($support, $report->getSupport(), '', 0.01); + $this->assertEquals($average, $report->getAverage(), '', 0.01); + } + +} From 074dcf7470d77d2993eff39a5f4b6247e7fc42e0 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 19 Jul 2016 21:59:23 +0200 Subject: [PATCH 077/328] php-cs-fixer --- src/Phpml/Metric/ClassificationReport.php | 18 +++++++++--------- .../Phpml/Metric/ClassificationReportTest.php | 8 +++----- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/Phpml/Metric/ClassificationReport.php b/src/Phpml/Metric/ClassificationReport.php index 69209fd5..31ee0b76 100644 --- a/src/Phpml/Metric/ClassificationReport.php +++ b/src/Phpml/Metric/ClassificationReport.php @@ -1,5 +1,6 @@ $actual) { $predicted = $predictedLabels[$index]; - $this->support[$actual]++; + ++$this->support[$actual]; - if($actual === $predicted) { - $truePositive[$actual]++; + if ($actual === $predicted) { + ++$truePositive[$actual]; } else { - $falsePositive[$predicted]++; - $falseNegative[$actual]++; + ++$falsePositive[$predicted]; + ++$falseNegative[$actual]; } } @@ -104,7 +105,7 @@ private function computeMetrics(array $truePositive, array $falsePositive, array foreach ($truePositive as $label => $tp) { $this->precision[$label] = $tp / ($tp + $falsePositive[$label]); $this->recall[$label] = $tp / ($tp + $falseNegative[$label]); - $this->f1score[$label] = $this->computeF1Score((float)$this->precision[$label], (float)$this->recall[$label]); + $this->f1score[$label] = $this->computeF1Score((float) $this->precision[$label], (float) $this->recall[$label]); } } @@ -124,7 +125,7 @@ private function computeAverage() */ private function computeF1Score(float $precision, float $recall): float { - if(0 == ($divider = $precision+$recall)) { + if (0 == ($divider = $precision + $recall)) { return 0.0; } @@ -144,5 +145,4 @@ private static function getLabelIndexedArray(array $labels): array return $labels; } - } diff --git a/tests/Phpml/Metric/ClassificationReportTest.php b/tests/Phpml/Metric/ClassificationReportTest.php index 58520bee..2f7bc1a9 100644 --- a/tests/Phpml/Metric/ClassificationReportTest.php +++ b/tests/Phpml/Metric/ClassificationReportTest.php @@ -1,5 +1,6 @@ 1, 'ant' => 1, 'bird' => 3]; $average = ['precision' => 0.75, 'recall' => 0.83, 'f1score' => 0.73]; - $this->assertEquals($precision, $report->getPrecision(), '', 0.01); $this->assertEquals($recall, $report->getRecall(), '', 0.01); $this->assertEquals($f1score, $report->getF1score(), '', 0.01); $this->assertEquals($support, $report->getSupport(), '', 0.01); $this->assertEquals($average, $report->getAverage(), '', 0.01); } - } From 093e8fc89cd5530dcb663b75663d1ce97aa30348 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 19 Jul 2016 22:01:39 +0200 Subject: [PATCH 078/328] add more tests for CReport --- .../Phpml/Metric/ClassificationReportTest.php | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/Phpml/Metric/ClassificationReportTest.php b/tests/Phpml/Metric/ClassificationReportTest.php index 2f7bc1a9..f0f1cd36 100644 --- a/tests/Phpml/Metric/ClassificationReportTest.php +++ b/tests/Phpml/Metric/ClassificationReportTest.php @@ -27,4 +27,24 @@ public function testClassificationReportGenerateWithStringLabels() $this->assertEquals($support, $report->getSupport(), '', 0.01); $this->assertEquals($average, $report->getAverage(), '', 0.01); } + + public function testClassificationReportGenerateWithNumericLabels() + { + $labels = [0, 1, 2, 2, 2]; + $predicted = [0, 0, 2, 2, 1]; + + $report = new ClassificationReport($labels, $predicted); + + $precision = [0 => 0.5, 1 => 0.0, 2 => 1.0]; + $recall = [0 => 1.0, 1 => 0.0, 2 => 0.67]; + $f1score = [0 => 0.67, 1 => 0.0, 2 => 0.80]; + $support = [0 => 1, 1 => 1, 2 => 3]; + $average = ['precision' => 0.75, 'recall' => 0.83, 'f1score' => 0.73]; + + $this->assertEquals($precision, $report->getPrecision(), '', 0.01); + $this->assertEquals($recall, $report->getRecall(), '', 0.01); + $this->assertEquals($f1score, $report->getF1score(), '', 0.01); + $this->assertEquals($support, $report->getSupport(), '', 0.01); + $this->assertEquals($average, $report->getAverage(), '', 0.01); + } } From 963cfea551744c8ca8dccccfe3139d623fc20e3e Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 19 Jul 2016 22:17:03 +0200 Subject: [PATCH 079/328] add ClassificationReport docs --- CHANGELOG.md | 2 +- README.md | 1 + docs/index.md | 1 + .../metric/classification-report.md | 61 +++++++++++++++++++ mkdocs.yml | 1 + 5 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 docs/machine-learning/metric/classification-report.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a9a928a..74021004 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ CHANGELOG This changelog references the relevant changes done in PHP-ML library. -* 0.2.0 (in plan/progress) +* 0.1.2 (in plan/progress) * feature [Dataset] - FilesDataset - load dataset from files (folder names as targets) * feature [Metric] - ClassificationReport - report about trained classifier * bug [Feature Extraction] - fix problem with token count vectorizer array order diff --git a/README.md b/README.md index a3f01ce4..29394a64 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ composer require php-ai/php-ml * Metric * [Accuracy](http://php-ml.readthedocs.io/en/latest/machine-learning/metric/accuracy/) * [Confusion Matrix](http://php-ml.readthedocs.io/en/latest/machine-learning/metric/confusion-matrix/) + * [Classification Report](http://php-ml.readthedocs.io/en/latest/machine-learning/metric/classification-report/) * Workflow * [Pipeline](http://php-ml.readthedocs.io/en/latest/machine-learning/workflow/pipeline) * Cross Validation diff --git a/docs/index.md b/docs/index.md index d58f03e3..757f2785 100644 --- a/docs/index.md +++ b/docs/index.md @@ -51,6 +51,7 @@ composer require php-ai/php-ml * Metric * [Accuracy](machine-learning/metric/accuracy/) * [Confusion Matrix](machine-learning/metric/confusion-matrix/) + * [Classification Report](machine-learning/metric/classification-report/) * Workflow * [Pipeline](machine-learning/workflow/pipeline) * Cross Validation diff --git a/docs/machine-learning/metric/classification-report.md b/docs/machine-learning/metric/classification-report.md new file mode 100644 index 00000000..53490b2c --- /dev/null +++ b/docs/machine-learning/metric/classification-report.md @@ -0,0 +1,61 @@ +# Classification Report + +Class for calculate main classifier metrics: precision, recall, F1 score and support. + +### Report + +To generate report you must provide the following parameters: + +* $actualLabels - (array) true sample labels +* $predictedLabels - (array) predicted labels (e.x. from test group) + +``` +use Phpml\Metric\ClassificationReport; + +$actualLabels = ['cat', 'ant', 'bird', 'bird', 'bird']; +$predictedLabels = ['cat', 'cat', 'bird', 'bird', 'ant']; + +$report = new ClassificationReport($actualLabels, $predictedLabels); +``` + +### Metrics + +After creating the report you can draw its individual metrics: + +* precision (`getPrecision()`) - fraction of retrieved instances that are relevant +* recall (`getRecall()`) - fraction of relevant instances that are retrieved +* F1 score (`getF1score()`) - measure of a test's accuracy +* support (`getSupport()`) - count of testes samples + +``` +$precision = $report->getPrecision(); + +// $precision = ['cat' => 0.5, 'ant' => 0.0, 'bird' => 1.0]; +``` + +### Example + +``` +use Phpml\Metric\ClassificationReport; + +$actualLabels = ['cat', 'ant', 'bird', 'bird', 'bird']; +$predictedLabels = ['cat', 'cat', 'bird', 'bird', 'ant']; + +$report = new ClassificationReport($actualLabels, $predictedLabels); + +$report->getPrecision(); +// ['cat' => 0.5, 'ant' => 0.0, 'bird' => 1.0] + +$report->getRecall(); +// ['cat' => 1.0, 'ant' => 0.0, 'bird' => 0.67] + +$report->getF1score(); +// ['cat' => 0.67, 'ant' => 0.0, 'bird' => 0.80] + +$report->getSupport(); +// ['cat' => 1, 'ant' => 1, 'bird' => 3] + +$report->getAverage(); +// ['precision' => 0.75, 'recall' => 0.83, 'f1score' => 0.73] + +``` diff --git a/mkdocs.yml b/mkdocs.yml index f06a08b2..057a1a1a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -15,6 +15,7 @@ pages: - Metric: - Accuracy: machine-learning/metric/accuracy.md - Confusion Matrix: machine-learning/metric/confusion-matrix.md + - Classification Report: machine-learning/metric/classification-report.md - Workflow: - Pipeline: machine-learning/workflow/pipeline.md - Cross Validation: From 52cd58acb0977407c6b38443aab9e443969fc83d Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 20 Jul 2016 09:15:52 +0200 Subject: [PATCH 080/328] add info about minimum php version required --- README.md | 4 ++++ docs/index.md | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 29394a64..0ac39b15 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # PHP-ML - Machine Learning library for PHP +[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.0-8892BF.svg)](https://php.net/) +[![Latest Stable Version](https://img.shields.io/packagist/v/php-ai/php-ml.svg)](https://packagist.org/packages/php-ai/php-ml) [![Build Status](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/build.png?b=develop)](https://scrutinizer-ci.com/g/php-ai/php-ml/build-status/develop) [![Documentation Status](https://readthedocs.org/projects/php-ml/badge/?version=develop)](http://php-ml.readthedocs.org/en/develop/?badge=develop) [![Total Downloads](https://poser.pugx.org/php-ai/php-ml/downloads.svg)](https://packagist.org/packages/php-ai/php-ml) @@ -10,6 +12,8 @@ Fresh approach to Machine Learning in PHP. Algorithms, Cross Validation, Preprocessing, Feature Extraction and much more in one library. +PHP-ML requires PHP >= 7.0. + Simple example of classification: ```php use Phpml\Classification\KNearestNeighbors; diff --git a/docs/index.md b/docs/index.md index 757f2785..5aaf8eaf 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,5 +1,7 @@ # PHP-ML - Machine Learning library for PHP +[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.0-8892BF.svg)](https://php.net/) +[![Latest Stable Version](https://img.shields.io/packagist/v/php-ai/php-ml.svg)](https://packagist.org/packages/php-ai/php-ml) [![Build Status](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/build.png?b=develop)](https://scrutinizer-ci.com/g/php-ai/php-ml/build-status/develop) [![Documentation Status](https://readthedocs.org/projects/php-ml/badge/?version=develop)](http://php-ml.readthedocs.org/en/develop/?badge=develop) [![Total Downloads](https://poser.pugx.org/php-ai/php-ml/downloads.svg)](https://packagist.org/packages/php-ai/php-ml) @@ -8,7 +10,9 @@ ![PHP-ML - Machine Learning library for PHP](assets/php-ml-logo.png) -Fresh approach to Machine Learning in PHP. Note that at the moment PHP is not the best choice for machine learning but maybe this will change ... +Fresh approach to Machine Learning in PHP. Algorithms, Cross Validation, Preprocessing, Feature Extraction and much more in one library. + +PHP-ML requires PHP >= 7.0. Simple example of classification: ```php From 6ed4761427c008b88fe42480232cdd6c383bd957 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 24 Jul 2016 13:35:13 +0200 Subject: [PATCH 081/328] add examples link to readme --- README.md | 4 ++++ docs/index.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index 0ac39b15..e34a69c4 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,10 @@ Currently this library is in the process of developing, but You can install it w composer require php-ai/php-ml ``` +## Examples + +Example scripts are available in a separate repository [php-ai/php-ml-examples](https://github.com/php-ai/php-ml-examples). + ## Features * Classification diff --git a/docs/index.md b/docs/index.md index 5aaf8eaf..38eca658 100644 --- a/docs/index.md +++ b/docs/index.md @@ -40,6 +40,10 @@ Currently this library is in the process of developing, but You can install it w composer require php-ai/php-ml ``` +## Examples + +Example scripts are available in a separate repository [php-ai/php-ml-examples](https://github.com/php-ai/php-ml-examples). + ## Features * Classification From 2a76cbb402b5f65bc28305eaf053abfe830064c0 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 24 Jul 2016 13:42:50 +0200 Subject: [PATCH 082/328] add .coverage to git ignore --- .gitignore | 1 + tests/Phpml/Preprocessing/NormalizerTest.php | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/.gitignore b/.gitignore index 73854f23..8a409f4b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /vendor/ humbuglog.* /bin/phpunit +.coverage diff --git a/tests/Phpml/Preprocessing/NormalizerTest.php b/tests/Phpml/Preprocessing/NormalizerTest.php index f0e21c99..d33881f6 100644 --- a/tests/Phpml/Preprocessing/NormalizerTest.php +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -79,4 +79,24 @@ public function testFitNotChangeNormalizerBehavior() $this->assertEquals($normalized, $samples, '', $delta = 0.01); } + + public function testL1NormWithZeroSumCondition() + { + $samples = [ + [0, 0, 0], + [2, 0, 0], + [0, 1, -1], + ]; + + $normalized = [ + [0.33, 0.33, 0.33], + [1.0, 0.0, 0.0], + [0.0, 0.5, -0.5], + ]; + + $normalizer = new Normalizer(Normalizer::NORM_L1); + $normalizer->transform($samples); + + $this->assertEquals($normalized, $samples, '', $delta = 0.01); + } } From a298bdc8de42c138e1d91715c92b5bf86ee94de0 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 24 Jul 2016 13:45:54 +0200 Subject: [PATCH 083/328] create script for code coverage generation --- tools/code-coverage.sh | 4 ++++ 1 file changed, 4 insertions(+) create mode 100755 tools/code-coverage.sh diff --git a/tools/code-coverage.sh b/tools/code-coverage.sh new file mode 100755 index 00000000..a24c0e8d --- /dev/null +++ b/tools/code-coverage.sh @@ -0,0 +1,4 @@ +#!/bin/bash +echo "Run PHPUnit with code coverage" +bin/phpunit --coverage-html .coverage +google-chrome .coverage/index.html From 448eaafd78e79bfe2966449d61af698776d5d775 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 24 Jul 2016 13:52:52 +0200 Subject: [PATCH 084/328] remove unused exception --- CHANGELOG.md | 7 ++++++- src/Phpml/Exception/NormalizerException.php | 9 +-------- tests/Phpml/PipelineTest.php | 11 +++++++++++ 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74021004..bef3451f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,15 @@ CHANGELOG This changelog references the relevant changes done in PHP-ML library. -* 0.1.2 (in plan/progress) +* 0.1.3 (in plan/progress) + * SSE, SSTo, SSR [Regression] - sum of the squared + * + +* 0.1.2 (2016-07-24) * feature [Dataset] - FilesDataset - load dataset from files (folder names as targets) * feature [Metric] - ClassificationReport - report about trained classifier * bug [Feature Extraction] - fix problem with token count vectorizer array order + * tests [General] - add more tests for specific conditions * 0.1.1 (2016-07-12) * feature [Cross Validation] Stratified Random Split - equal distribution for targets in split diff --git a/src/Phpml/Exception/NormalizerException.php b/src/Phpml/Exception/NormalizerException.php index 31abfeb1..d1b689a1 100644 --- a/src/Phpml/Exception/NormalizerException.php +++ b/src/Phpml/Exception/NormalizerException.php @@ -13,12 +13,5 @@ public static function unknownNorm() { return new self('Unknown norm supplied.'); } - - /** - * @return NormalizerException - */ - public static function fitNotAllowed() - { - return new self('Fit is not allowed for this preprocessor.'); - } + } diff --git a/tests/Phpml/PipelineTest.php b/tests/Phpml/PipelineTest.php index 4e5815b9..108a24cd 100644 --- a/tests/Phpml/PipelineTest.php +++ b/tests/Phpml/PipelineTest.php @@ -10,6 +10,7 @@ use Phpml\Preprocessing\Imputer; use Phpml\Preprocessing\Normalizer; use Phpml\Preprocessing\Imputer\Strategy\MostFrequentStrategy; +use Phpml\Regression\SVR; class PipelineTest extends \PHPUnit_Framework_TestCase { @@ -26,6 +27,16 @@ public function testPipelineConstruction() $this->assertEquals($estimator, $pipeline->getEstimator()); } + public function testPipelineEstimatorSetter() + { + $pipeline = new Pipeline([new TfIdfTransformer()], new SVC()); + + $estimator = new SVR(); + $pipeline->setEstimator($estimator); + + $this->assertEquals($estimator, $pipeline->getEstimator()); + } + public function testPipelineWorkflow() { $transformers = [ From 403824d23b4e284b4833f952ddb0e9c30fee819c Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 24 Jul 2016 14:01:17 +0200 Subject: [PATCH 085/328] test exception on kmeans --- src/Phpml/Exception/NormalizerException.php | 1 - tests/Phpml/Clustering/KMeansTest.php | 8 ++++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Phpml/Exception/NormalizerException.php b/src/Phpml/Exception/NormalizerException.php index d1b689a1..9f88f0c5 100644 --- a/src/Phpml/Exception/NormalizerException.php +++ b/src/Phpml/Exception/NormalizerException.php @@ -13,5 +13,4 @@ public static function unknownNorm() { return new self('Unknown norm supplied.'); } - } diff --git a/tests/Phpml/Clustering/KMeansTest.php b/tests/Phpml/Clustering/KMeansTest.php index 5a85b38d..79b5ce79 100644 --- a/tests/Phpml/Clustering/KMeansTest.php +++ b/tests/Phpml/Clustering/KMeansTest.php @@ -48,4 +48,12 @@ public function testKMeansInitializationMethods() $clusters = $kmeans->cluster($samples); $this->assertEquals(4, count($clusters)); } + + /** + * @expectedException \Phpml\Exception\InvalidArgumentException + */ + public function testThrowExceptionOnInvalidClusterNumber() + { + new KMeans(0); + } } From 38deaaeb2ed2ab2d5b3e7fe3e69fcce3c174c8ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Jo=C3=A1n=20Iglesias?= Date: Tue, 26 Jul 2016 02:13:52 -0400 Subject: [PATCH 086/328] testScalarProduct check for non numeric values (#13) * testScalarProduct check for non numeric values test for non numeric values. * updating pr #13 using global namespace fro stdClass --- tests/Phpml/Math/ProductTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Phpml/Math/ProductTest.php b/tests/Phpml/Math/ProductTest.php index aba0ff2a..05ca5b59 100644 --- a/tests/Phpml/Math/ProductTest.php +++ b/tests/Phpml/Math/ProductTest.php @@ -13,5 +13,8 @@ public function testScalarProduct() $this->assertEquals(10, Product::scalar([2, 3], [-1, 4])); $this->assertEquals(-0.1, Product::scalar([1, 4, 1], [-2, 0.5, -0.1])); $this->assertEquals(8, Product::scalar([2], [4])); + + //test for non numeric values + $this->assertEquals(0, Product::scalar(['', null, [], new \stdClass()], [null])); } } From bbbf5cfc9dc46f58776954a18c0f60e342410a17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Jo=C3=A1n=20Iglesias?= Date: Tue, 26 Jul 2016 02:14:57 -0400 Subject: [PATCH 087/328] For each body should be wrapped in an if statement (#14) unit test to go with commit --- src/Phpml/Math/Product.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Phpml/Math/Product.php b/src/Phpml/Math/Product.php index 70accb98..678dd71b 100644 --- a/src/Phpml/Math/Product.php +++ b/src/Phpml/Math/Product.php @@ -16,7 +16,9 @@ public static function scalar(array $a, array $b) { $product = 0; foreach ($a as $index => $value) { - $product += $value * $b[$index]; + if (is_numeric($value) && is_numeric($b[$index])) { + $product += $value * $b[$index]; + } } return $product; From 2f5b09018870753130a7c5a8089157d401c1e348 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 26 Jul 2016 21:57:15 +0200 Subject: [PATCH 088/328] create contributing guide --- CONTRIBUTING.md | 43 +++++++++++++++++++++++++++++++++++++++++++ README.md | 7 +------ docs/index.md | 6 +----- 3 files changed, 45 insertions(+), 11 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..8084dc89 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,43 @@ +# Contributing to PHP-ML + +PHP-ML is an open source project. If you'd like to contribute, please read the following text. Before I can merge your +Pull-Request here are some guidelines that you need to follow. These guidelines exist not to annoy you, but to keep the +code base clean, unified and future proof. + +## Branch + +You should only open pull requests against the develop branch. + +## Unit-Tests + +Please try to add a test for your pull-request. You can run the unit-tests by calling: + +``` +bin/phpunit +``` + +## Travis + +GitHub automatically run your pull request through Travis CI against PHP 7. +If you break the tests, I cannot merge your code, so please make sure that your code is working +before opening up a Pull-Request. + +## Merge + +Please allow me time to review your pull requests. I will give my best to review everything as fast as possible, but cannot always live up to my own expectations. + +## Coding Standards + +When contributing code to PHP-ML, you must follow its coding standards. To make a long story short, here is the golden tool: + +``` +tools/php-cs-fixer.sh +``` + +This script run PHP Coding Standards Fixer with `--level=symfony` param. + +More about PHP-CS-Fixer: [http://cs.sensiolabs.org/](http://cs.sensiolabs.org/) + +--- + +Thank you very much again for your contribution! diff --git a/README.md b/README.md index e34a69c4..07ce0994 100644 --- a/README.md +++ b/README.md @@ -84,17 +84,12 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * [Matrix](http://php-ml.readthedocs.io/en/latest/math/matrix/) * [Statistic](http://php-ml.readthedocs.io/en/latest/math/statistic/) - ## Contribute - Issue Tracker: github.com/php-ai/php-ml/issues - Source Code: github.com/php-ai/php-ml -After installation, you can launch the test suite in project root directory (you will need to install dev requirements with Composer) - -``` -bin/phpunit -``` +You can find more about contributing in [CONTRIBUTING.md](CONTRIBUTING.md). ## License diff --git a/docs/index.md b/docs/index.md index 38eca658..6b0ce8cc 100644 --- a/docs/index.md +++ b/docs/index.md @@ -90,11 +90,7 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( - Issue Tracker: github.com/php-ai/php-ml/issues - Source Code: github.com/php-ai/php-ml -After installation, you can launch the test suite in project root directory (you will need to install dev requirements with Composer) - -``` -bin/phpunit -``` +You can find more about contributing in [CONTRIBUTING.md](CONTRIBUTING.md). ## License From 637fd613b84675d0a8cc12449971bf51273c4432 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 2 Aug 2016 13:07:47 +0200 Subject: [PATCH 089/328] implement activation function for neural network --- .../NeuralNetwork/ActivationFunction.php | 15 +++++++ .../ActivationFunction/BinaryStep.php | 20 ++++++++++ .../ActivationFunction/Gaussian.php | 20 ++++++++++ .../ActivationFunction/HyperbolicTangent.php | 33 ++++++++++++++++ .../ActivationFunction/Sigmoid.php | 33 ++++++++++++++++ src/Phpml/NeuralNetwork/Node/Neuron.php | 9 +++++ tests/Phpml/Math/ProductTest.php | 2 +- .../ActivationFunction/BinaryStepTest.php | 35 +++++++++++++++++ .../ActivationFunction/GaussianTest.php | 37 ++++++++++++++++++ .../HyperboliTangentTest.php | 39 +++++++++++++++++++ .../ActivationFunction/SigmoidTest.php | 39 +++++++++++++++++++ 11 files changed, 281 insertions(+), 1 deletion(-) create mode 100644 src/Phpml/NeuralNetwork/ActivationFunction.php create mode 100644 src/Phpml/NeuralNetwork/ActivationFunction/BinaryStep.php create mode 100644 src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php create mode 100644 src/Phpml/NeuralNetwork/ActivationFunction/HyperbolicTangent.php create mode 100644 src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php create mode 100644 src/Phpml/NeuralNetwork/Node/Neuron.php create mode 100644 tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php create mode 100644 tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php create mode 100644 tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php create mode 100644 tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php diff --git a/src/Phpml/NeuralNetwork/ActivationFunction.php b/src/Phpml/NeuralNetwork/ActivationFunction.php new file mode 100644 index 00000000..9b7d9847 --- /dev/null +++ b/src/Phpml/NeuralNetwork/ActivationFunction.php @@ -0,0 +1,15 @@ += 0 ? 1.0 : 0.0; + } +} diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php b/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php new file mode 100644 index 00000000..cdbe4aee --- /dev/null +++ b/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php @@ -0,0 +1,20 @@ +beta = $beta; + } + + /** + * @param float|int $value + * + * @return float + */ + public function compute($value): float + { + return tanh($this->beta * $value); + } +} diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php b/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php new file mode 100644 index 00000000..ee7b7be5 --- /dev/null +++ b/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php @@ -0,0 +1,33 @@ +beta = $beta; + } + + /** + * @param float|int $value + * + * @return float + */ + public function compute($value): float + { + return 1 / (1 + exp(-$this->beta * $value)); + } +} diff --git a/src/Phpml/NeuralNetwork/Node/Neuron.php b/src/Phpml/NeuralNetwork/Node/Neuron.php new file mode 100644 index 00000000..52b38e7e --- /dev/null +++ b/src/Phpml/NeuralNetwork/Node/Neuron.php @@ -0,0 +1,9 @@ +assertEquals(10, Product::scalar([2, 3], [-1, 4])); $this->assertEquals(-0.1, Product::scalar([1, 4, 1], [-2, 0.5, -0.1])); $this->assertEquals(8, Product::scalar([2], [4])); - + //test for non numeric values $this->assertEquals(0, Product::scalar(['', null, [], new \stdClass()], [null])); } diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php new file mode 100644 index 00000000..c074955a --- /dev/null +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php @@ -0,0 +1,35 @@ +assertEquals($expected, $binaryStep->compute($value)); + } + + /** + * @return array + */ + public function binaryStepProvider() + { + return [ + [1, 1], + [1, 0], + [0, -0.1], + ]; + } +} diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php new file mode 100644 index 00000000..4780a534 --- /dev/null +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php @@ -0,0 +1,37 @@ +assertEquals($expected, $gaussian->compute($value), '', 0.001); + } + + /** + * @return array + */ + public function gaussianProvider() + { + return [ + [0.367, 1], + [1, 0], + [0.367, -1], + [0, 3], + [0, -3], + ]; + } +} diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php new file mode 100644 index 00000000..92f4b97a --- /dev/null +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php @@ -0,0 +1,39 @@ +assertEquals($expected, $tanh->compute($value), '', 0.001); + } + + /** + * @return array + */ + public function tanhProvider() + { + return [ + [1.0, 0.761, 1], + [1.0, 0, 0], + [1.0, 1, 4], + [1.0, -1, -4], + [0.5, 0.462, 1], + [0.3, 0, 0], + ]; + } +} diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php new file mode 100644 index 00000000..c84a20b2 --- /dev/null +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php @@ -0,0 +1,39 @@ +assertEquals($expected, $sigmoid->compute($value), '', 0.001); + } + + /** + * @return array + */ + public function sigmoidProvider() + { + return [ + [1.0, 1, 7.25], + [2.0, 1, 3.75], + [1.0, 0.5, 0], + [0.5, 0.5, 0], + [1.0, 0, -7.25], + [2.0, 0, -3.75], + ]; + } +} From f186aa9c0bb9a46b619f7141d805c2da27959b00 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 2 Aug 2016 13:23:58 +0200 Subject: [PATCH 090/328] extract functions from loops and remove unused code --- src/Phpml/Clustering/KMeans/Space.php | 8 ++++---- src/Phpml/Math/Matrix.php | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Phpml/Clustering/KMeans/Space.php b/src/Phpml/Clustering/KMeans/Space.php index 2904e2f9..89a0d092 100644 --- a/src/Phpml/Clustering/KMeans/Space.php +++ b/src/Phpml/Clustering/KMeans/Space.php @@ -61,7 +61,7 @@ public function newPoint(array $coordinates) */ public function addPoint(array $coordinates, $data = null) { - return $this->attach($this->newPoint($coordinates), $data); + $this->attach($this->newPoint($coordinates), $data); } /** @@ -74,7 +74,7 @@ public function attach($point, $data = null) throw new InvalidArgumentException('can only attach points to spaces'); } - return parent::attach($point, $data); + parent::attach($point, $data); } /** @@ -230,8 +230,8 @@ private function initializeRandomClusters(int $clustersNumber) protected function initializeKMPPClusters(int $clustersNumber) { $clusters = []; - $position = rand(1, count($this)); - for ($i = 1, $this->rewind(); $i < $position && $this->valid(); $i++, $this->next()); + $this->rewind(); + $clusters[] = new Cluster($this, $this->current()->getCoordinates()); $distances = new SplObjectStorage(); diff --git a/src/Phpml/Math/Matrix.php b/src/Phpml/Math/Matrix.php index 208b10d0..808472cf 100644 --- a/src/Phpml/Math/Matrix.php +++ b/src/Phpml/Math/Matrix.php @@ -193,7 +193,8 @@ public function multiply(Matrix $matrix) $product = []; $multiplier = $matrix->toArray(); for ($i = 0; $i < $this->rows; ++$i) { - for ($j = 0; $j < $matrix->getColumns(); ++$j) { + $columns = $matrix->getColumns(); + for ($j = 0; $j < $columns; ++$j) { $product[$i][$j] = 0; for ($k = 0; $k < $this->columns; ++$k) { $product[$i][$j] += $this->matrix[$i][$k] * $multiplier[$k][$j]; From 7062ee29e14b9f2571b29f79947f27c4172d21c8 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 2 Aug 2016 20:30:20 +0200 Subject: [PATCH 091/328] add Neuron and Synapse classes --- src/Phpml/NeuralNetwork/Node.php | 13 ++++ src/Phpml/NeuralNetwork/Node/Neuron.php | 67 +++++++++++++++++- src/Phpml/NeuralNetwork/Node/Synapse.php | 70 +++++++++++++++++++ tests/Phpml/NeuralNetwork/Node/NeuronTest.php | 65 +++++++++++++++++ .../Phpml/NeuralNetwork/Node/SynapseTest.php | 52 ++++++++++++++ 5 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 src/Phpml/NeuralNetwork/Node.php create mode 100644 src/Phpml/NeuralNetwork/Node/Synapse.php create mode 100644 tests/Phpml/NeuralNetwork/Node/NeuronTest.php create mode 100644 tests/Phpml/NeuralNetwork/Node/SynapseTest.php diff --git a/src/Phpml/NeuralNetwork/Node.php b/src/Phpml/NeuralNetwork/Node.php new file mode 100644 index 00000000..77e0c008 --- /dev/null +++ b/src/Phpml/NeuralNetwork/Node.php @@ -0,0 +1,13 @@ +activationFunction = $activationFunction ?: new ActivationFunction\Sigmoid(); + $this->synapses = []; + $this->output = 0; + } + + /** + * @param Synapse $synapse + */ + public function addSynapse(Synapse $synapse) + { + $this->synapses[] = $synapse; + } + + /** + * @return Synapse[] + */ + public function getSynapses() + { + return $this->synapses; + } + + /** + * @return float + */ + public function getOutput(): float + { + if (0 === $this->output) { + $sum = 0; + foreach ($this->synapses as $synapse) { + $sum += $synapse->getOutput(); + } + + $this->output = $this->activationFunction->compute($sum); + } + + return $this->output; + } + + public function refresh() + { + $this->output = 0; + } } diff --git a/src/Phpml/NeuralNetwork/Node/Synapse.php b/src/Phpml/NeuralNetwork/Node/Synapse.php new file mode 100644 index 00000000..923c4ffe --- /dev/null +++ b/src/Phpml/NeuralNetwork/Node/Synapse.php @@ -0,0 +1,70 @@ +node = $node; + $this->weight = $weight ?: $this->generateRandomWeight(); + } + + /** + * @return float + */ + protected function generateRandomWeight(): float + { + return 1 / rand(5, 25) * (rand(0, 1) ? -1 : 1); + } + + /** + * @return float + */ + public function getOutput(): float + { + return $this->weight * $this->node->getOutput(); + } + + /** + * @param float $delta + */ + public function changeWeight($delta) + { + $this->weight += $delta; + } + + /** + * @return float + */ + public function getWeight() + { + return $this->weight; + } + + /** + * @return Node + */ + public function getNode() + { + return $this->node; + } +} diff --git a/tests/Phpml/NeuralNetwork/Node/NeuronTest.php b/tests/Phpml/NeuralNetwork/Node/NeuronTest.php new file mode 100644 index 00000000..526041bc --- /dev/null +++ b/tests/Phpml/NeuralNetwork/Node/NeuronTest.php @@ -0,0 +1,65 @@ +assertEquals([], $neuron->getSynapses()); + $this->assertEquals(0.5, $neuron->getOutput()); + } + + public function testNeuronActivationFunction() + { + $activationFunction = $this->getMock(BinaryStep::class); + $activationFunction->method('compute')->with(0)->willReturn($output = 0.69); + + $neuron = new Neuron($activationFunction); + + $this->assertEquals($output, $neuron->getOutput()); + } + + public function testNeuronWithSynapse() + { + $neuron = new Neuron(); + $neuron->addSynapse($synapse = $this->getSynapseMock()); + + $this->assertEquals([$synapse], $neuron->getSynapses()); + $this->assertEquals(0.88, $neuron->getOutput(), '', 0.01); + } + + public function testNeuronRefresh() + { + $neuron = new Neuron(); + $neuron->getOutput(); + $neuron->addSynapse($this->getSynapseMock()); + + $this->assertEquals(0.5, $neuron->getOutput(), '', 0.01); + + $neuron->refresh(); + + $this->assertEquals(0.88, $neuron->getOutput(), '', 0.01); + } + + /** + * @param int $output + * + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function getSynapseMock($output = 2) + { + $synapse = $this->getMock(Synapse::class, [], [], '', false); + $synapse->method('getOutput')->willReturn($output); + + return $synapse; + } +} diff --git a/tests/Phpml/NeuralNetwork/Node/SynapseTest.php b/tests/Phpml/NeuralNetwork/Node/SynapseTest.php new file mode 100644 index 00000000..41fc9374 --- /dev/null +++ b/tests/Phpml/NeuralNetwork/Node/SynapseTest.php @@ -0,0 +1,52 @@ +getNodeMock($nodeOutput = 0.5); + + $synapse = new Synapse($node, $weight = 0.75); + + $this->assertEquals($node, $synapse->getNode()); + $this->assertEquals($weight, $synapse->getWeight()); + $this->assertEquals($weight * $nodeOutput, $synapse->getOutput()); + + $synapse = new Synapse($node); + + $this->assertInternalType('float', $synapse->getWeight()); + } + + public function testSynapseWeightChange() + { + $node = $this->getNodeMock(); + $synapse = new Synapse($node, $weight = 0.75); + $synapse->changeWeight(1.0); + + $this->assertEquals(1.75, $synapse->getWeight()); + + $synapse->changeWeight(-2.0); + + $this->assertEquals(-0.25, $synapse->getWeight()); + } + + /** + * @param int $output + * + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function getNodeMock($output = 1) + { + $node = $this->getMock(Neuron::class); + $node->method('getOutput')->willReturn($nodeOutput = 0.5); + + return $node; + } +} From 95b29d40b1322c9add7e3aa219ee71291dfeb4e4 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 5 Aug 2016 10:20:31 +0200 Subject: [PATCH 092/328] add Layer, Input and Bias for neutal network --- .../Exception/InvalidArgumentException.php | 8 +++ src/Phpml/NeuralNetwork/Layer.php | 49 ++++++++++++++++ src/Phpml/NeuralNetwork/Node/Bias.php | 18 ++++++ src/Phpml/NeuralNetwork/Node/Input.php | 39 +++++++++++++ src/Phpml/NeuralNetwork/Node/Neuron.php | 1 + .../Node/{ => Neuron}/Synapse.php | 4 +- tests/Phpml/NeuralNetwork/LayerTest.php | 56 +++++++++++++++++++ tests/Phpml/NeuralNetwork/Node/BiasTest.php | 17 ++++++ tests/Phpml/NeuralNetwork/Node/InputTest.php | 27 +++++++++ .../Node/{ => Neuron}/SynapseTest.php | 4 +- tests/Phpml/NeuralNetwork/Node/NeuronTest.php | 4 +- 11 files changed, 221 insertions(+), 6 deletions(-) create mode 100644 src/Phpml/NeuralNetwork/Layer.php create mode 100644 src/Phpml/NeuralNetwork/Node/Bias.php create mode 100644 src/Phpml/NeuralNetwork/Node/Input.php rename src/Phpml/NeuralNetwork/Node/{ => Neuron}/Synapse.php (94%) create mode 100644 tests/Phpml/NeuralNetwork/LayerTest.php create mode 100644 tests/Phpml/NeuralNetwork/Node/BiasTest.php create mode 100644 tests/Phpml/NeuralNetwork/Node/InputTest.php rename tests/Phpml/NeuralNetwork/Node/{ => Neuron}/SynapseTest.php (93%) diff --git a/src/Phpml/Exception/InvalidArgumentException.php b/src/Phpml/Exception/InvalidArgumentException.php index 798532de..d2802967 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -73,4 +73,12 @@ public static function invalidStopWordsLanguage(string $language) { return new self(sprintf('Can\'t find %s language for StopWords', $language)); } + + /** + * @return InvalidArgumentException + */ + public static function invalidLayerNodeClass() + { + return new self('Layer node class must implement Node interface'); + } } diff --git a/src/Phpml/NeuralNetwork/Layer.php b/src/Phpml/NeuralNetwork/Layer.php new file mode 100644 index 00000000..67001640 --- /dev/null +++ b/src/Phpml/NeuralNetwork/Layer.php @@ -0,0 +1,49 @@ +nodes[] = new $nodeClass(); + } + } + + /** + * @param Node $node + */ + public function addNode(Node $node) + { + $this->nodes[] = $node; + } + + /** + * @return Node[] + */ + public function getNodes() + { + return $this->nodes; + } +} diff --git a/src/Phpml/NeuralNetwork/Node/Bias.php b/src/Phpml/NeuralNetwork/Node/Bias.php new file mode 100644 index 00000000..f19dcb6d --- /dev/null +++ b/src/Phpml/NeuralNetwork/Node/Bias.php @@ -0,0 +1,18 @@ +input = $input; + } + + /** + * @return float + */ + public function getOutput(): float + { + return $this->input; + } + + /** + * @param float $input + */ + public function setInput(float $input) + { + $this->input = $input; + } +} diff --git a/src/Phpml/NeuralNetwork/Node/Neuron.php b/src/Phpml/NeuralNetwork/Node/Neuron.php index 8d2fb4aa..677831eb 100644 --- a/src/Phpml/NeuralNetwork/Node/Neuron.php +++ b/src/Phpml/NeuralNetwork/Node/Neuron.php @@ -5,6 +5,7 @@ namespace Phpml\NeuralNetwork\Node; use Phpml\NeuralNetwork\ActivationFunction; +use Phpml\NeuralNetwork\Node\Neuron\Synapse; use Phpml\NeuralNetwork\Node; class Neuron implements Node diff --git a/src/Phpml/NeuralNetwork/Node/Synapse.php b/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php similarity index 94% rename from src/Phpml/NeuralNetwork/Node/Synapse.php rename to src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php index 923c4ffe..3813d719 100644 --- a/src/Phpml/NeuralNetwork/Node/Synapse.php +++ b/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php @@ -2,11 +2,11 @@ declare (strict_types = 1); -namespace Phpml\NeuralNetwork\Node; +namespace Phpml\NeuralNetwork\Node\Neuron; use Phpml\NeuralNetwork\Node; -class Synapse implements Node +class Synapse { /** * @var float diff --git a/tests/Phpml/NeuralNetwork/LayerTest.php b/tests/Phpml/NeuralNetwork/LayerTest.php new file mode 100644 index 00000000..5706ab46 --- /dev/null +++ b/tests/Phpml/NeuralNetwork/LayerTest.php @@ -0,0 +1,56 @@ +assertEquals([], $layer->getNodes()); + } + + public function testLayerInitializationWithDefaultNodesType() + { + $layer = new Layer($number = 5); + + $this->assertCount($number, $layer->getNodes()); + foreach ($layer->getNodes() as $node) { + $this->assertInstanceOf(Neuron::class, $node); + } + } + + public function testLayerInitializationWithExplicitNodesType() + { + $layer = new Layer($number = 5, $class = Bias::class); + + $this->assertCount($number, $layer->getNodes()); + foreach ($layer->getNodes() as $node) { + $this->assertInstanceOf($class, $node); + } + } + + /** + * @expectedException \Phpml\Exception\InvalidArgumentException + */ + public function testThrowExceptionOnInvalidNodeClass() + { + new Layer(1, \stdClass::class); + } + + public function testAddNodesToLayer() + { + $layer = new Layer(); + $layer->addNode($node1 = new Neuron()); + $layer->addNode($node2 = new Neuron()); + + $this->assertEquals([$node1, $node2], $layer->getNodes()); + } +} diff --git a/tests/Phpml/NeuralNetwork/Node/BiasTest.php b/tests/Phpml/NeuralNetwork/Node/BiasTest.php new file mode 100644 index 00000000..c0ece3fc --- /dev/null +++ b/tests/Phpml/NeuralNetwork/Node/BiasTest.php @@ -0,0 +1,17 @@ +assertEquals(1.0, $bias->getOutput()); + } +} diff --git a/tests/Phpml/NeuralNetwork/Node/InputTest.php b/tests/Phpml/NeuralNetwork/Node/InputTest.php new file mode 100644 index 00000000..b0abdcc2 --- /dev/null +++ b/tests/Phpml/NeuralNetwork/Node/InputTest.php @@ -0,0 +1,27 @@ +assertEquals(0.0, $input->getOutput()); + + $input = new Input($value = 9.6); + $this->assertEquals($value, $input->getOutput()); + } + + public function testSetInput() + { + $input = new Input(); + $input->setInput($value = 6.9); + + $this->assertEquals($value, $input->getOutput()); + } +} diff --git a/tests/Phpml/NeuralNetwork/Node/SynapseTest.php b/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php similarity index 93% rename from tests/Phpml/NeuralNetwork/Node/SynapseTest.php rename to tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php index 41fc9374..9ad733d0 100644 --- a/tests/Phpml/NeuralNetwork/Node/SynapseTest.php +++ b/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php @@ -2,10 +2,10 @@ declare (strict_types = 1); -namespace tests\Phpml\NeuralNetwork\Node; +namespace tests\Phpml\NeuralNetwork\Node\Neuron; +use Phpml\NeuralNetwork\Node\Neuron\Synapse; use Phpml\NeuralNetwork\Node\Neuron; -use Phpml\NeuralNetwork\Node\Synapse; class SynapseTest extends \PHPUnit_Framework_TestCase { diff --git a/tests/Phpml/NeuralNetwork/Node/NeuronTest.php b/tests/Phpml/NeuralNetwork/Node/NeuronTest.php index 526041bc..c416ffd5 100644 --- a/tests/Phpml/NeuralNetwork/Node/NeuronTest.php +++ b/tests/Phpml/NeuralNetwork/Node/NeuronTest.php @@ -6,7 +6,7 @@ use Phpml\NeuralNetwork\ActivationFunction\BinaryStep; use Phpml\NeuralNetwork\Node\Neuron; -use Phpml\NeuralNetwork\Node\Synapse; +use Phpml\NeuralNetwork\Node\Neuron\Synapse; class NeuronTest extends \PHPUnit_Framework_TestCase { @@ -53,7 +53,7 @@ public function testNeuronRefresh() /** * @param int $output * - * @return \PHPUnit_Framework_MockObject_MockObject + * @return Synapse|\PHPUnit_Framework_MockObject_MockObject */ private function getSynapseMock($output = 2) { From 12ee62bbca4acc3ad29e0eb1867a0683cf965402 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 5 Aug 2016 16:12:39 +0200 Subject: [PATCH 093/328] create Network and Training contracts --- src/Phpml/NeuralNetwork/Network.php | 20 +++++++++++ .../NeuralNetwork/Network/LayeredNetwork.php | 36 +++++++++++++++++++ .../Network/MultilayerPerceptron.php | 10 ++++++ src/Phpml/NeuralNetwork/Training.php | 16 +++++++++ .../Training/Backpropagation.php | 23 ++++++++++++ 5 files changed, 105 insertions(+) create mode 100644 src/Phpml/NeuralNetwork/Network.php create mode 100644 src/Phpml/NeuralNetwork/Network/LayeredNetwork.php create mode 100644 src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php create mode 100644 src/Phpml/NeuralNetwork/Training.php create mode 100644 src/Phpml/NeuralNetwork/Training/Backpropagation.php diff --git a/src/Phpml/NeuralNetwork/Network.php b/src/Phpml/NeuralNetwork/Network.php new file mode 100644 index 00000000..be59b342 --- /dev/null +++ b/src/Phpml/NeuralNetwork/Network.php @@ -0,0 +1,20 @@ + Date: Sun, 7 Aug 2016 23:41:02 +0200 Subject: [PATCH 094/328] test abstraction from LayeredNetwork --- src/Phpml/NeuralNetwork/Network.php | 14 +++++-- .../NeuralNetwork/Network/LayeredNetwork.php | 37 +++++++++++++++++-- .../Network/MultilayerPerceptron.php | 1 - src/Phpml/NeuralNetwork/Training.php | 2 +- .../Training/Backpropagation.php | 5 +-- 5 files changed, 46 insertions(+), 13 deletions(-) diff --git a/src/Phpml/NeuralNetwork/Network.php b/src/Phpml/NeuralNetwork/Network.php index be59b342..269351f2 100644 --- a/src/Phpml/NeuralNetwork/Network.php +++ b/src/Phpml/NeuralNetwork/Network.php @@ -4,9 +4,8 @@ namespace Phpml\NeuralNetwork; -interface Network extends Node +interface Network { - /** * @param mixed $input */ @@ -15,6 +14,15 @@ public function setInput($input); /** * @return array */ - public function getLayers(): array; + public function getOutput(): array; + + /** + * @param Layer $layer + */ + public function addLayer(Layer $layer); + /** + * @return Layer[] + */ + public function getLayers(): array; } diff --git a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php index a46b267c..699c4d4b 100644 --- a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php +++ b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php @@ -4,25 +4,51 @@ namespace Phpml\NeuralNetwork\Network; +use Phpml\NeuralNetwork\Layer; use Phpml\NeuralNetwork\Network; abstract class LayeredNetwork implements Network { + /** + * @var Layer[] + */ + protected $layers; /** - * @return array + * @param Layer $layer + */ + public function addLayer(Layer $layer) + { + $this->layers[] = $layer; + } + + /** + * @return Layer[] */ public function getLayers(): array { + return $this->layers; + } + /** + * @return Layer + */ + public function getOutputLayer(): Layer + { + return $this->layers[count($this->layers) - 1]; } /** - * @return float + * @return array */ - public function getOutput(): float + public function getOutput(): array { + $result = []; + foreach ($this->getOutputLayer()->getNodes() as $neuron) { + $result[] = $neuron->getOutput(); + } + return $result; } /** @@ -30,7 +56,10 @@ public function getOutput(): float */ public function setInput($input) { + $firstLayer = $this->layers[0]; + foreach ($firstLayer->getNodes() as $key => $neuron) { + $neuron->setInput($input[$key]); + } } - } diff --git a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php index ce5a6158..c0f7df33 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -6,5 +6,4 @@ class MultilayerPerceptron extends LayeredNetwork { - } diff --git a/src/Phpml/NeuralNetwork/Training.php b/src/Phpml/NeuralNetwork/Training.php index 9870c112..932f1899 100644 --- a/src/Phpml/NeuralNetwork/Training.php +++ b/src/Phpml/NeuralNetwork/Training.php @@ -10,7 +10,7 @@ interface Training * @param array $samples * @param array $targets * @param float $desiredError - * @param int $maxIterations + * @param int $maxIterations */ public function train(array $samples, array $targets, float $desiredError = 0.001, int $maxIterations = 10000); } diff --git a/src/Phpml/NeuralNetwork/Training/Backpropagation.php b/src/Phpml/NeuralNetwork/Training/Backpropagation.php index 17ca44c1..ce9f5e6a 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -8,16 +8,13 @@ class Backpropagation implements Training { - /** * @param array $samples * @param array $targets * @param float $desiredError - * @param int $maxIterations + * @param int $maxIterations */ public function train(array $samples, array $targets, float $desiredError = 0.001, int $maxIterations = 10000) { - } - } From 64859f263f378e2c2f73423eaf6d106bf23e2899 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 7 Aug 2016 23:41:08 +0200 Subject: [PATCH 095/328] test abstraction from LayeredNetwork --- .../Network/LayeredNetworkTest.php | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php diff --git a/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php b/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php new file mode 100644 index 00000000..11fe9145 --- /dev/null +++ b/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php @@ -0,0 +1,53 @@ +getLayeredNetworkMock(); + + $network->addLayer($layer1 = new Layer()); + $network->addLayer($layer2 = new Layer()); + + $this->assertEquals([$layer1, $layer2], $network->getLayers()); + } + + public function testGetLastLayerAsOutputLayer() + { + $network = $this->getLayeredNetworkMock(); + $network->addLayer($layer1 = new Layer()); + + $this->assertEquals($layer1, $network->getOutputLayer()); + + $network->addLayer($layer2 = new Layer()); + $this->assertEquals($layer2, $network->getOutputLayer()); + } + + public function testSetInputAndGetOutput() + { + $network = $this->getLayeredNetworkMock(); + $network->addLayer(new Layer(2, Input::class)); + + $network->setInput($input = [34, 43]); + $this->assertEquals($input, $network->getOutput()); + + $network->addLayer(new Layer(1)); + $this->assertEquals([0.5], $network->getOutput()); + } + + /** + * @return LayeredNetwork + */ + private function getLayeredNetworkMock() + { + return $this->getMockForAbstractClass(LayeredNetwork::class); + } +} From 72afeb7040104bd9825d0555637fb735eb8c9e5e Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 9 Aug 2016 13:27:43 +0200 Subject: [PATCH 096/328] implements and test multilayer perceptron methods --- .../Exception/InvalidArgumentException.php | 8 ++ .../Network/MultilayerPerceptron.php | 83 +++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/src/Phpml/Exception/InvalidArgumentException.php b/src/Phpml/Exception/InvalidArgumentException.php index d2802967..86cfd860 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -81,4 +81,12 @@ public static function invalidLayerNodeClass() { return new self('Layer node class must implement Node interface'); } + + /** + * @return InvalidArgumentException + */ + public static function invalidLayersNumber() + { + return new self('Provide at least 2 layers: 1 input and 1 output'); + } } diff --git a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php index c0f7df33..40798222 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -4,6 +4,89 @@ namespace Phpml\NeuralNetwork\Network; +use Phpml\Exception\InvalidArgumentException; +use Phpml\NeuralNetwork\Layer; +use Phpml\NeuralNetwork\Node\Bias; +use Phpml\NeuralNetwork\Node\Input; +use Phpml\NeuralNetwork\Node\Neuron; +use Phpml\NeuralNetwork\Node\Neuron\Synapse; + class MultilayerPerceptron extends LayeredNetwork { + /** + * @param array $layers + * + * @throws InvalidArgumentException + */ + public function __construct(array $layers) + { + if (count($layers) < 2) { + throw InvalidArgumentException::invalidLayersNumber(); + } + + $this->addInputLayer(array_shift($layers)); + $this->addNeuronLayers($layers); + $this->addBiasNodes(); + $this->generateSynapses(); + } + + /** + * @param int $nodes + */ + private function addInputLayer(int $nodes) + { + $this->addLayer(new Layer($nodes, Input::class)); + } + + /** + * @param array $layers + */ + private function addNeuronLayers(array $layers) + { + foreach ($layers as $neurons) { + $this->addLayer(new Layer($neurons, Neuron::class)); + } + } + + private function generateSynapses() + { + $layersNumber = count($this->layers) - 1; + for ($i = 0; $i < $layersNumber; ++$i) { + $currentLayer = $this->layers[$i]; + $nextLayer = $this->layers[$i + 1]; + $this->generateLayerSynapses($nextLayer, $currentLayer); + } + } + + private function addBiasNodes() + { + $biasLayers = count($this->layers) - 1; + for ($i = 0;$i < $biasLayers;++$i) { + $this->layers[$i]->addNode(new Bias()); + } + } + + /** + * @param Layer $nextLayer + * @param Layer $currentLayer + */ + private function generateLayerSynapses(Layer $nextLayer, Layer $currentLayer) + { + foreach ($nextLayer->getNodes() as $nextNeuron) { + if ($nextNeuron instanceof Neuron) { + $this->generateNeuronSynapses($currentLayer, $nextNeuron); + } + } + } + + /** + * @param Layer $currentLayer + * @param Neuron $nextNeuron + */ + private function generateNeuronSynapses(Layer $currentLayer, Neuron $nextNeuron) + { + foreach ($currentLayer->getNodes() as $currentNeuron) { + $nextNeuron->addSynapse(new Synapse($currentNeuron)); + } + } } From e5d39ee18a5154df4173121cc89c357a952d6078 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 9 Aug 2016 13:27:48 +0200 Subject: [PATCH 097/328] implements and test multilayer perceptron methods --- .../Network/MultilayerPerceptronTest.php | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php diff --git a/tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php b/tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php new file mode 100644 index 00000000..1ac1621d --- /dev/null +++ b/tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php @@ -0,0 +1,73 @@ +assertCount(3, $mlp->getLayers()); + + $layers = $mlp->getLayers(); + + // input layer + $this->assertCount(3, $layers[0]->getNodes()); + $this->assertNotContainsOnly(Neuron::class, $layers[0]->getNodes()); + + // hidden layer + $this->assertCount(3, $layers[1]->getNodes()); + $this->assertNotContainsOnly(Neuron::class, $layers[0]->getNodes()); + + // output layer + $this->assertCount(1, $layers[2]->getNodes()); + $this->assertContainsOnly(Neuron::class, $layers[2]->getNodes()); + } + + public function testSynapsesGeneration() + { + $mlp = new MultilayerPerceptron([2, 2, 1]); + $layers = $mlp->getLayers(); + + foreach ($layers[1]->getNodes() as $node) { + if ($node instanceof Neuron) { + $synapses = $node->getSynapses(); + $this->assertCount(3, $synapses); + + $synapsesNodes = $this->getSynapsesNodes($synapses); + foreach ($layers[0]->getNodes() as $prevNode) { + $this->assertContains($prevNode, $synapsesNodes); + } + } + } + } + + /** + * @param array $synapses + * + * @return array + */ + private function getSynapsesNodes(array $synapses): array + { + $nodes = []; + foreach ($synapses as $synapse) { + $nodes[] = $synapse->getNode(); + } + + return $nodes; + } + + /** + * @expectedException \Phpml\Exception\InvalidArgumentException + */ + public function testThrowExceptionOnInvalidLayersNumber() + { + new MultilayerPerceptron([2]); + } +} From 66d029e94fb6bb024dee5781504be0d32011e651 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 10 Aug 2016 22:43:47 +0200 Subject: [PATCH 098/328] implement and test Backpropagation training --- src/Phpml/NeuralNetwork/Network.php | 2 + .../NeuralNetwork/Network/LayeredNetwork.php | 18 ++- .../Training/Backpropagation.php | 114 ++++++++++++++++++ .../Training/Backpropagation/Sigma.php | 46 +++++++ .../Training/BackpropagationTest.php | 29 +++++ 5 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php create mode 100644 tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php diff --git a/src/Phpml/NeuralNetwork/Network.php b/src/Phpml/NeuralNetwork/Network.php index 269351f2..a03b8b64 100644 --- a/src/Phpml/NeuralNetwork/Network.php +++ b/src/Phpml/NeuralNetwork/Network.php @@ -8,6 +8,8 @@ interface Network { /** * @param mixed $input + * + * @return self */ public function setInput($input); diff --git a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php index 699c4d4b..44134038 100644 --- a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php +++ b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php @@ -6,6 +6,8 @@ use Phpml\NeuralNetwork\Layer; use Phpml\NeuralNetwork\Network; +use Phpml\NeuralNetwork\Node\Input; +use Phpml\NeuralNetwork\Node\Neuron; abstract class LayeredNetwork implements Network { @@ -53,13 +55,27 @@ public function getOutput(): array /** * @param mixed $input + * + * @return $this */ public function setInput($input) { $firstLayer = $this->layers[0]; foreach ($firstLayer->getNodes() as $key => $neuron) { - $neuron->setInput($input[$key]); + if ($neuron instanceof Input) { + $neuron->setInput($input[$key]); + } } + + foreach ($this->getLayers() as $layer) { + foreach ($layer->getNodes() as $node) { + if ($node instanceof Neuron) { + $node->refresh(); + } + } + } + + return $this; } } diff --git a/src/Phpml/NeuralNetwork/Training/Backpropagation.php b/src/Phpml/NeuralNetwork/Training/Backpropagation.php index ce9f5e6a..e6691e29 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -4,10 +4,33 @@ namespace Phpml\NeuralNetwork\Training; +use Phpml\NeuralNetwork\Network; +use Phpml\NeuralNetwork\Node\Neuron; use Phpml\NeuralNetwork\Training; +use Phpml\NeuralNetwork\Training\Backpropagation\Sigma; class Backpropagation implements Training { + /** + * @var Network + */ + private $network; + + /** + * @var int + */ + private $theta; + + /** + * @param Network $network + * @param int $theta + */ + public function __construct(Network $network, int $theta = 1) + { + $this->network = $network; + $this->theta = $theta; + } + /** * @param array $samples * @param array $targets @@ -16,5 +39,96 @@ class Backpropagation implements Training */ public function train(array $samples, array $targets, float $desiredError = 0.001, int $maxIterations = 10000) { + for ($i = 0; $i < $maxIterations; ++$i) { + $resultsWithinError = $this->trainSamples($samples, $targets, $desiredError); + + if ($resultsWithinError == count($samples)) { + break; + } + } + } + + /** + * @param array $samples + * @param array $targets + * @param float $desiredError + * + * @return int + */ + private function trainSamples(array $samples, array $targets, float $desiredError): int + { + $resultsWithinError = 0; + foreach ($targets as $key => $target) { + $result = $this->network->setInput($samples[$key])->getOutput(); + + if ($this->isResultWithinError($result, $target, $desiredError)) { + ++$resultsWithinError; + } else { + $this->trainSample($samples[$key], $target); + } + } + + return $resultsWithinError; + } + + private function trainSample(array $sample, array $target) + { + $this->network->setInput($sample)->getOutput(); + + $sigmas = []; + $layers = $this->network->getLayers(); + $layersNumber = count($layers); + + for ($i = $layersNumber; $i > 1; --$i) { + foreach ($layers[$i - 1]->getNodes() as $key => $neuron) { + if ($neuron instanceof Neuron) { + $neuronOutput = $neuron->getOutput(); + $sigma = $neuronOutput * (1 - $neuronOutput) * ($i == $layersNumber ? ($target[$key] - $neuronOutput) : $this->getPrevSigma($sigmas, $neuron)); + $sigmas[] = new Sigma($neuron, $sigma); + foreach ($neuron->getSynapses() as $synapse) { + $synapse->changeWeight($this->theta * $sigma * $synapse->getNode()->getOutput()); + } + } + } + } + } + + /** + * @param Sigma[] $sigmas + * @param Neuron $forNeuron + * + * @return float + */ + private function getPrevSigma(array $sigmas, Neuron $forNeuron): float + { + $sigma = 0.0; + + foreach ($sigmas as $neuronSigma) { + foreach ($neuronSigma->getNeuron()->getSynapses() as $synapse) { + if ($synapse->getNode() == $forNeuron) { + $sigma += $synapse->getWeight() * $neuronSigma->getSigma(); + } + } + } + + return $sigma; + } + + /** + * @param array $result + * @param array $target + * @param float $desiredError + * + * @return bool + */ + private function isResultWithinError(array $result, array $target, float $desiredError) + { + foreach ($target as $key => $value) { + if ($result[$key] > $value + $desiredError || $result[$key] < $value - $desiredError) { + return false; + } + } + + return true; } } diff --git a/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php b/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php new file mode 100644 index 00000000..8ce397b1 --- /dev/null +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php @@ -0,0 +1,46 @@ +neuron = $neuron; + $this->sigma = $sigma; + } + + /** + * @return Neuron + */ + public function getNeuron() + { + return $this->neuron; + } + + /** + * @return float + */ + public function getSigma() + { + return $this->sigma; + } +} diff --git a/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php b/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php new file mode 100644 index 00000000..a44c1d54 --- /dev/null +++ b/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php @@ -0,0 +1,29 @@ +train( + [[1, 0], [0, 1], [1, 1], [0, 0]], + [[1], [1], [0], [0]], + $desiredError = 0.2, + 10000 + ); + + $this->assertEquals(0, $network->setInput([1, 1])->getOutput()[0], '', $desiredError); + $this->assertEquals(0, $network->setInput([0, 0])->getOutput()[0], '', $desiredError); + $this->assertEquals(1, $network->setInput([1, 0])->getOutput()[0], '', $desiredError); + $this->assertEquals(1, $network->setInput([0, 1])->getOutput()[0], '', $desiredError); + } +} From c506a84164c203634f5d5e3a92b89aced51cc9a5 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 10 Aug 2016 23:03:02 +0200 Subject: [PATCH 099/328] refactor Backpropagation methods and simplify things --- .../Training/Backpropagation.php | 52 ++++++++++++++----- .../Training/Backpropagation/Sigma.php | 18 +++++++ .../Training/BackpropagationTest.php | 2 +- 3 files changed, 58 insertions(+), 14 deletions(-) diff --git a/src/Phpml/NeuralNetwork/Training/Backpropagation.php b/src/Phpml/NeuralNetwork/Training/Backpropagation.php index e6691e29..3f80bf5a 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -21,6 +21,11 @@ class Backpropagation implements Training */ private $theta; + /** + * @var array + */ + private $sigmas; + /** * @param Network $network * @param int $theta @@ -71,20 +76,22 @@ private function trainSamples(array $samples, array $targets, float $desiredErro return $resultsWithinError; } + /** + * @param array $sample + * @param array $target + */ private function trainSample(array $sample, array $target) { $this->network->setInput($sample)->getOutput(); + $this->sigmas = []; - $sigmas = []; $layers = $this->network->getLayers(); $layersNumber = count($layers); for ($i = $layersNumber; $i > 1; --$i) { foreach ($layers[$i - 1]->getNodes() as $key => $neuron) { if ($neuron instanceof Neuron) { - $neuronOutput = $neuron->getOutput(); - $sigma = $neuronOutput * (1 - $neuronOutput) * ($i == $layersNumber ? ($target[$key] - $neuronOutput) : $this->getPrevSigma($sigmas, $neuron)); - $sigmas[] = new Sigma($neuron, $sigma); + $sigma = $this->getSigma($neuron, $target, $key, $i == $layersNumber); foreach ($neuron->getSynapses() as $synapse) { $synapse->changeWeight($this->theta * $sigma * $synapse->getNode()->getOutput()); } @@ -94,21 +101,40 @@ private function trainSample(array $sample, array $target) } /** - * @param Sigma[] $sigmas - * @param Neuron $forNeuron + * @param Neuron $neuron + * @param array $target + * @param int $key + * @param bool $lastLayer + * + * @return float + */ + private function getSigma(Neuron $neuron, array $target, int $key, bool $lastLayer): float + { + $neuronOutput = $neuron->getOutput(); + $sigma = $neuronOutput * (1 - $neuronOutput); + + if ($lastLayer) { + $sigma *= ($target[$key] - $neuronOutput); + } else { + $sigma *= $this->getPrevSigma($neuron); + } + + $this->sigmas[] = new Sigma($neuron, $sigma); + + return $sigma; + } + + /** + * @param Neuron $neuron * * @return float */ - private function getPrevSigma(array $sigmas, Neuron $forNeuron): float + private function getPrevSigma(Neuron $neuron): float { $sigma = 0.0; - foreach ($sigmas as $neuronSigma) { - foreach ($neuronSigma->getNeuron()->getSynapses() as $synapse) { - if ($synapse->getNode() == $forNeuron) { - $sigma += $synapse->getWeight() * $neuronSigma->getSigma(); - } - } + foreach ($this->sigmas as $neuronSigma) { + $sigma += $neuronSigma->getSigmaForNeuron($neuron); } return $sigma; diff --git a/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php b/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php index 8ce397b1..85203544 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php @@ -43,4 +43,22 @@ public function getSigma() { return $this->sigma; } + + /** + * @param Neuron $neuron + * + * @return float + */ + public function getSigmaForNeuron(Neuron $neuron): float + { + $sigma = 0.0; + + foreach ($this->neuron->getSynapses() as $synapse) { + if ($synapse->getNode() == $neuron) { + $sigma += $synapse->getWeight() * $this->getSigma(); + } + } + + return $sigma; + } } diff --git a/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php b/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php index a44c1d54..265d936d 100644 --- a/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php +++ b/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php @@ -18,7 +18,7 @@ public function testBackpropagationForXORLearning() [[1, 0], [0, 1], [1, 1], [0, 0]], [[1], [1], [0], [0]], $desiredError = 0.2, - 10000 + 30000 ); $this->assertEquals(0, $network->setInput([1, 1])->getOutput()[0], '', $desiredError); From 2412f15923514d3c14b1b1ad09d3c3f6c5c34558 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 11 Aug 2016 13:21:22 +0200 Subject: [PATCH 100/328] Add activationFunction parameter for Perceptron and Layer --- src/Phpml/NeuralNetwork/Layer.php | 24 +++++++++++++++---- .../Network/MultilayerPerceptron.php | 15 +++++++----- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/Phpml/NeuralNetwork/Layer.php b/src/Phpml/NeuralNetwork/Layer.php index 67001640..b94da216 100644 --- a/src/Phpml/NeuralNetwork/Layer.php +++ b/src/Phpml/NeuralNetwork/Layer.php @@ -15,22 +15,38 @@ class Layer private $nodes = []; /** - * @param int $nodesNumber - * @param string $nodeClass + * @param int $nodesNumber + * @param string $nodeClass + * @param ActivationFunction|null $activationFunction * * @throws InvalidArgumentException */ - public function __construct(int $nodesNumber = 0, string $nodeClass = Neuron::class) + public function __construct(int $nodesNumber = 0, string $nodeClass = Neuron::class, ActivationFunction $activationFunction = null) { if (!in_array(Node::class, class_implements($nodeClass))) { throw InvalidArgumentException::invalidLayerNodeClass(); } for ($i = 0; $i < $nodesNumber; ++$i) { - $this->nodes[] = new $nodeClass(); + $this->nodes[] = $this->createNode($nodeClass, $activationFunction); } } + /** + * @param string $nodeClass + * @param ActivationFunction|null $activationFunction + * + * @return Neuron + */ + private function createNode(string $nodeClass, ActivationFunction $activationFunction = null) + { + if (Neuron::class == $nodeClass) { + return new Neuron($activationFunction); + } + + return new $nodeClass(); + } + /** * @param Node $node */ diff --git a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php index 40798222..e97e0459 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -5,6 +5,7 @@ namespace Phpml\NeuralNetwork\Network; use Phpml\Exception\InvalidArgumentException; +use Phpml\NeuralNetwork\ActivationFunction; use Phpml\NeuralNetwork\Layer; use Phpml\NeuralNetwork\Node\Bias; use Phpml\NeuralNetwork\Node\Input; @@ -14,18 +15,19 @@ class MultilayerPerceptron extends LayeredNetwork { /** - * @param array $layers + * @param array $layers + * @param ActivationFunction|null $activationFunction * * @throws InvalidArgumentException */ - public function __construct(array $layers) + public function __construct(array $layers, ActivationFunction $activationFunction = null) { if (count($layers) < 2) { throw InvalidArgumentException::invalidLayersNumber(); } $this->addInputLayer(array_shift($layers)); - $this->addNeuronLayers($layers); + $this->addNeuronLayers($layers, $activationFunction); $this->addBiasNodes(); $this->generateSynapses(); } @@ -39,12 +41,13 @@ private function addInputLayer(int $nodes) } /** - * @param array $layers + * @param array $layers + * @param ActivationFunction|null $activationFunction */ - private function addNeuronLayers(array $layers) + private function addNeuronLayers(array $layers, ActivationFunction $activationFunction = null) { foreach ($layers as $neurons) { - $this->addLayer(new Layer($neurons, Neuron::class)); + $this->addLayer(new Layer($neurons, Neuron::class, $activationFunction)); } } From f0bd5ae4244a5c3b8e71400bbb9727d7a68ab35f Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 12 Aug 2016 16:29:50 +0200 Subject: [PATCH 101/328] Create MLP Regressor draft --- src/Phpml/Regression/MLPRegressor.php | 81 +++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/Phpml/Regression/MLPRegressor.php diff --git a/src/Phpml/Regression/MLPRegressor.php b/src/Phpml/Regression/MLPRegressor.php new file mode 100644 index 00000000..b00b4d11 --- /dev/null +++ b/src/Phpml/Regression/MLPRegressor.php @@ -0,0 +1,81 @@ +hiddenLayers = $hiddenLayers; + $this->desiredError = $desiredError; + $this->maxIterations = $maxIterations; + $this->activationFunction = $activationFunction; + } + + + /** + * @param array $samples + * @param array $targets + */ + public function train(array $samples, array $targets) + { + $layers = [count($samples[0])] + $this->hiddenLayers + [count($targets[0])]; + + $this->perceptron = new MultilayerPerceptron($layers, $this->activationFunction); + + $trainer = new Backpropagation($this->perceptron); + $trainer->train($samples, $targets, $this->desiredError, $this->maxIterations); + } + + /** + * @param array $sample + * + * @return array + */ + protected function predictSample(array $sample) + { + return $this->perceptron->setInput($sample)->getOutput(); + } + +} From 638119fc986f86ffc9e315c58cc9d1b67beab708 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 14 Aug 2016 18:27:08 +0200 Subject: [PATCH 102/328] code style fixes --- src/Phpml/Regression/MLPRegressor.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Phpml/Regression/MLPRegressor.php b/src/Phpml/Regression/MLPRegressor.php index b00b4d11..9a842141 100644 --- a/src/Phpml/Regression/MLPRegressor.php +++ b/src/Phpml/Regression/MLPRegressor.php @@ -4,7 +4,6 @@ namespace Phpml\Regression; - use Phpml\Helper\Predictable; use Phpml\NeuralNetwork\ActivationFunction; use Phpml\NeuralNetwork\Network\MultilayerPerceptron; @@ -40,12 +39,12 @@ class MLPRegressor implements Regression private $activationFunction; /** - * @param array $hiddenLayers - * @param float $desiredError - * @param int $maxIterations + * @param array $hiddenLayers + * @param float $desiredError + * @param int $maxIterations * @param ActivationFunction $activationFunction */ - public function __construct(array $hiddenLayers = [100], float $desiredError, int $maxIterations, ActivationFunction $activationFunction = null) + public function __construct(array $hiddenLayers = [10], float $desiredError = 0.01, int $maxIterations = 10000, ActivationFunction $activationFunction = null) { $this->hiddenLayers = $hiddenLayers; $this->desiredError = $desiredError; @@ -53,14 +52,15 @@ public function __construct(array $hiddenLayers = [100], float $desiredError, in $this->activationFunction = $activationFunction; } - /** * @param array $samples * @param array $targets */ public function train(array $samples, array $targets) { - $layers = [count($samples[0])] + $this->hiddenLayers + [count($targets[0])]; + $layers = $this->hiddenLayers; + array_unshift($layers, count($samples[0])); + $layers[] = count($targets[0]); $this->perceptron = new MultilayerPerceptron($layers, $this->activationFunction); @@ -77,5 +77,4 @@ protected function predictSample(array $sample) { return $this->perceptron->setInput($sample)->getOutput(); } - } From b1978cf5ca0d0bd2361c4d9fbe64e0d6718fac9e Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 14 Aug 2016 18:35:17 +0200 Subject: [PATCH 103/328] update changelog --- CHANGELOG.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bef3451f..3b636d20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,11 @@ CHANGELOG This changelog references the relevant changes done in PHP-ML library. -* 0.1.3 (in plan/progress) - * SSE, SSTo, SSR [Regression] - sum of the squared - * +* 0.2.1 (in plan/progress) + * feature [Regression] - SSE, SSTo, SSR - sum of the squared + +* 0.2.0 (2016-08-14) + * feature [NeuralNetwork] - MultilayerPerceptron and Backpropagation training * 0.1.2 (2016-07-24) * feature [Dataset] - FilesDataset - load dataset from files (folder names as targets) From 3599367ce8673876c276f1cfa3bfc2af265e68bb Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 14 Aug 2016 19:14:56 +0200 Subject: [PATCH 104/328] Add docs for neural network --- README.md | 5 +++- composer.json | 2 +- docs/index.md | 3 ++ .../classification/k-nearest-neighbors.md | 6 ++-- .../neural-network/backpropagation.md | 29 +++++++++++++++++++ .../neural-network/multilayer-perceptron.md | 29 +++++++++++++++++++ mkdocs.yml | 3 ++ 7 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 docs/machine-learning/neural-network/backpropagation.md create mode 100644 docs/machine-learning/neural-network/multilayer-perceptron.md diff --git a/README.md b/README.md index 07ce0994..ea1ff4f4 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ ![PHP-ML - Machine Learning library for PHP](docs/assets/php-ml-logo.png) -Fresh approach to Machine Learning in PHP. Algorithms, Cross Validation, Preprocessing, Feature Extraction and much more in one library. +Fresh approach to Machine Learning in PHP. Algorithms, Cross Validation, Neural Network, Preprocessing, Feature Extraction and much more in one library. PHP-ML requires PHP >= 7.0. @@ -62,6 +62,9 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * [Classification Report](http://php-ml.readthedocs.io/en/latest/machine-learning/metric/classification-report/) * Workflow * [Pipeline](http://php-ml.readthedocs.io/en/latest/machine-learning/workflow/pipeline) +* Neural Network + * [Multilayer Perceptron](http://php-ml.readthedocs.io/en/latest/machine-learning/neural-network/multilayer-perceptron/) + * [Backpropagation training](http://php-ml.readthedocs.io/en/latest/machine-learning/neural-network/backpropagation/) * Cross Validation * [Random Split](http://php-ml.readthedocs.io/en/latest/machine-learning/cross-validation/random-split/) * [Stratified Random Split](http://php-ml.readthedocs.io/en/latest/machine-learning/cross-validation/stratified-random-split/) diff --git a/composer.json b/composer.json index 041f8189..eeccc53e 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "type": "library", "description": "PHP-ML - Machine Learning library for PHP", "license": "MIT", - "keywords": ["machine learning","pattern recognition","computational learning theory","artificial intelligence"], + "keywords": ["machine learning","pattern recognition","neural network","computational learning theory","artificial intelligence"], "homepage": "https://github.com/php-ai/php-ml", "authors": [ { diff --git a/docs/index.md b/docs/index.md index 6b0ce8cc..1a386428 100644 --- a/docs/index.md +++ b/docs/index.md @@ -62,6 +62,9 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * [Classification Report](machine-learning/metric/classification-report/) * Workflow * [Pipeline](machine-learning/workflow/pipeline) +* Neural Network + * [Multilayer Perceptron](machine-learning/neural-network/multilayer-perceptron/) + * [Backpropagation training](machine-learning/neural-network/backpropagation/) * Cross Validation * [Random Split](machine-learning/cross-validation/random-split/) * [Stratified Random Split](machine-learning/cross-validation/stratified-random-split/) diff --git a/docs/machine-learning/classification/k-nearest-neighbors.md b/docs/machine-learning/classification/k-nearest-neighbors.md index 3d5aa274..2de597c6 100644 --- a/docs/machine-learning/classification/k-nearest-neighbors.md +++ b/docs/machine-learning/classification/k-nearest-neighbors.md @@ -2,7 +2,7 @@ Classifier implementing the k-nearest neighbors algorithm. -### Constructor Parameters +## Constructor Parameters * $k - number of nearest neighbors to scan (default: 3) * $distanceMetric - Distance object, default Euclidean (see [distance documentation](math/distance/)) @@ -12,7 +12,7 @@ $classifier = new KNearestNeighbors($k=4); $classifier = new KNearestNeighbors($k=3, new Minkowski($lambda=4)); ``` -### Train +## Train To train a classifier simply provide train samples and labels (as `array`). Example: @@ -24,7 +24,7 @@ $classifier = new KNearestNeighbors(); $classifier->train($samples, $labels); ``` -### Predict +## Predict To predict sample label use `predict` method. You can provide one sample or array of samples: diff --git a/docs/machine-learning/neural-network/backpropagation.md b/docs/machine-learning/neural-network/backpropagation.md new file mode 100644 index 00000000..8c9b5609 --- /dev/null +++ b/docs/machine-learning/neural-network/backpropagation.md @@ -0,0 +1,29 @@ +# Backpropagation + +Backpropagation, an abbreviation for "backward propagation of errors", is a common method of training artificial neural networks used in conjunction with an optimization method such as gradient descent. + +## Constructor Parameters + +* $network (Network) - network to train (for example MultilayerPerceptron instance) +* $theta (int) - network theta parameter + +``` +use Phpml\NeuralNetwork\Network\MultilayerPerceptron; +use Phpml\NeuralNetwork\Training\Backpropagation; + +$network = new MultilayerPerceptron([2, 2, 1]); +$training = new Backpropagation($network); +``` + +## Training + +Example of XOR training: + +``` +$training->train( + $samples = [[1, 0], [0, 1], [1, 1], [0, 0]], + $targets = [[1], [1], [0], [0]], + $desiredError = 0.2, + $maxIteraions = 30000 +); +``` diff --git a/docs/machine-learning/neural-network/multilayer-perceptron.md b/docs/machine-learning/neural-network/multilayer-perceptron.md new file mode 100644 index 00000000..c1c0eef4 --- /dev/null +++ b/docs/machine-learning/neural-network/multilayer-perceptron.md @@ -0,0 +1,29 @@ +# MultilayerPerceptron + +A multilayer perceptron (MLP) is a feedforward artificial neural network model that maps sets of input data onto a set of appropriate outputs. + +## Constructor Parameters + +* $layers (array) - array with layers configuration, each value represent number of neurons in each layers +* $activationFunction (ActivationFunction) - neuron activation function + +``` +use Phpml\NeuralNetwork\Network\MultilayerPerceptron; +$mlp = new MultilayerPerceptron([2, 2, 1]); + +// 2 nodes in input layer, 2 nodes in first hidden layer and 1 node in output layer +``` + +## Methods + +* setInput(array $input) +* getOutput() +* getLayers() +* addLayer(Layer $layer) + +## Activation Functions + +* BinaryStep +* Gaussian +* HyperbolicTangent +* Sigmoid (default) diff --git a/mkdocs.yml b/mkdocs.yml index 057a1a1a..4fa6c21e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -18,6 +18,9 @@ pages: - Classification Report: machine-learning/metric/classification-report.md - Workflow: - Pipeline: machine-learning/workflow/pipeline.md + - Neural Network: + - Multilayer Perceptron: machine-learning/neural-network/multilayer-perceptron.md + - Backpropagation training: machine-learning/neural-network/backpropagation.md - Cross Validation: - RandomSplit: machine-learning/cross-validation/random-split.md - Stratified Random Split: machine-learning/cross-validation/stratified-random-split.md From 6421a2ba41587c004d8378c597b7cda964857f73 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 21 Aug 2016 14:03:20 +0200 Subject: [PATCH 105/328] Develop to master (#18) * Fix Backpropagation test with explicit random generator seed * remove custom seed - not working :( * Updated links in readme --- README.md | 4 ++-- tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ea1ff4f4..93574fc1 100644 --- a/README.md +++ b/README.md @@ -89,8 +89,8 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( ## Contribute -- Issue Tracker: github.com/php-ai/php-ml/issues -- Source Code: github.com/php-ai/php-ml +- [Issue Tracker: github.com/php-ai/php-ml](https://github.com/php-ai/php-ml/issues) +- [Source Code: github.com/php-ai/php-ml](https://github.com/php-ai/php-ml) You can find more about contributing in [CONTRIBUTING.md](CONTRIBUTING.md). diff --git a/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php b/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php index 265d936d..32deb647 100644 --- a/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php +++ b/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php @@ -17,7 +17,7 @@ public function testBackpropagationForXORLearning() $training->train( [[1, 0], [0, 1], [1, 1], [0, 0]], [[1], [1], [0], [0]], - $desiredError = 0.2, + $desiredError = 0.3, 30000 ); From c8bd8db6019e0fdeb4c7907282e306fbd8675013 Mon Sep 17 00:00:00 2001 From: Patrick Florek Date: Tue, 23 Aug 2016 15:44:53 +0200 Subject: [PATCH 106/328] # Association rule learning - Apriori algorithm * Generating frequent k-length item sets * Generating rules based on frequent item sets * Algorithm has exponential complexity, be aware of it * Apriori algorithm is split into apriori and candidates method * Second step rule generation is implemented by rules method * Internal methods are invoked for fine grain unit tests * Wikipedia's train samples and an alternative are provided for test cases * Small documentation for public interface is also shipped --- .gitignore | 1 + docs/machine-learning/association/apriori.md | 54 +++ src/Phpml/Association/Apriori.php | 325 +++++++++++++++++++ src/Phpml/Association/Associator.php | 11 + tests/Phpml/Association/AprioriTest.php | 187 +++++++++++ 5 files changed, 578 insertions(+) create mode 100644 docs/machine-learning/association/apriori.md create mode 100644 src/Phpml/Association/Apriori.php create mode 100644 src/Phpml/Association/Associator.php create mode 100644 tests/Phpml/Association/AprioriTest.php diff --git a/.gitignore b/.gitignore index 8a409f4b..e85e1fde 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/.idea/ /vendor/ humbuglog.* /bin/phpunit diff --git a/docs/machine-learning/association/apriori.md b/docs/machine-learning/association/apriori.md new file mode 100644 index 00000000..c5986f40 --- /dev/null +++ b/docs/machine-learning/association/apriori.md @@ -0,0 +1,54 @@ +# Apriori Associator + +Association rule learning based on [Apriori algorithm](https://en.wikipedia.org/wiki/Apriori_algorithm) for frequent item set mining. + +### Constructor Parameters + +* $support - [confidence](https://en.wikipedia.org/wiki/Association_rule_learning#Support), minimum relative amount of frequent item set in train sample +* $confidence - [confidence](https://en.wikipedia.org/wiki/Association_rule_learning#Confidence), minimum relative amount of item set in frequent item sets + +``` +$associator = new \Phpml\Association\Apriori($support = 0.5, $confidence = 0.5); +``` + +### Train + +To train a associator simply provide train samples and labels (as `array`). Example: + +``` +$samples = [['alpha', 'beta', 'epsilon'], ['alpha', 'beta', 'theta'], ['alpha', 'beta', 'epsilon'], ['alpha', 'beta', 'theta']]; +$labels = []; + +$associator = new \Phpml\Association\Apriori(0.5, 0.5); +$associator->train($samples, $labels); +``` + +### Predict + +To predict sample label use `predict` method. You can provide one sample or array of samples: + +``` +$associator->predict(['alpha','theta']); +// return [[['beta']]] + +$associator->predict([['alpha','epsilon'],['beta','theta']]); +// return [[['beta']], [['alpha']]] +``` + +### Associating + +Generating association rules simply use `rules` method. + +``` +$associator->rules(); +// return [['antecedent' => ['alpha', 'theta'], 'consequent' => ['beta], 'support' => 1.0, 'confidence' => 1.0], ... ] +``` + +### Frequent item sets + +Generating k-length frequent item sets simply use `apriori` method. + +``` +$associator->apriori(); +// return [ 1 => [['alpha'], ['beta'], ['theta'], ['epsilon']], 2 => [...], ...] +``` diff --git a/src/Phpml/Association/Apriori.php b/src/Phpml/Association/Apriori.php new file mode 100644 index 00000000..bf52c27a --- /dev/null +++ b/src/Phpml/Association/Apriori.php @@ -0,0 +1,325 @@ +support = $support; + $this->confidence = $confidence; + } + + /** + * Generates apriori association rules. + * + * @return mixed[][] + */ + public function rules() + { + if (!$this->large) { + $this->large = $this->apriori(); + } + + if ($this->rules) { + return $this->rules; + } + + $this->rules = []; + + for ($k = 2; !empty($this->large[$k]); ++$k) { + foreach ($this->large[$k] as $frequent) { + foreach ($this->antecedents($frequent) as $antecedent) { + if ($this->confidence <= ($confidence = $this->confidence($frequent, $antecedent))) { + $consequent = array_values(array_diff($frequent, $antecedent)); + $this->rules[] = [ + self::ARRAY_KEY_ANTECEDENT => $antecedent, + self::ARRAY_KEY_CONSEQUENT => $consequent, + self::ARRAY_KEY_SUPPORT => $this->support($consequent), + self::ARRAY_KEY_CONFIDENCE => $confidence, + ]; + } + } + } + } + + return $this->rules; + } + + /** + * Generates frequent item sets + * + * @return mixed[][][] + */ + public function apriori() + { + $L = []; + $L[1] = $this->items(); + $L[1] = $this->frequent($L[1]); + + for ($k = 2; !empty($L[$k - 1]); ++$k) { + $L[$k] = $this->candidates($L[$k - 1]); + $L[$k] = $this->frequent($L[$k]); + } + + return $L; + } + + /** + * @param mixed[] $sample + * + * @return mixed[][] + */ + protected function predictSample(array $sample) + { + $predicts = array_values(array_filter($this->rules(), function($rule) use ($sample) { + return $this->equals($rule[self::ARRAY_KEY_ANTECEDENT], $sample); + })); + + return array_map(function($rule) { return $rule[self::ARRAY_KEY_CONSEQUENT]; }, $predicts); + } + + /** + * Generates the power set for given item set $sample. + * + * @param mixed[] $sample + * + * @return mixed[][] + */ + private function powerSet(array $sample) + { + $results = [[]]; + foreach ($sample as $item) { + foreach ($results as $combination) { + $results[] = array_merge(array($item), $combination); + } + } + + return $results; + } + + /** + * Generates all proper subsets for given set $sample without the empty set. + * + * @param mixed[] $sample + * + * @return mixed[][] + */ + private function antecedents(array $sample) + { + $cardinality = count($sample); + $antecedents = $this->powerSet($sample); + + return array_filter($antecedents, function($antecedent) use ($cardinality) { + return (count($antecedent) != $cardinality) && ($antecedent != []); + }); + } + + /** + * Calculates frequent k = 1 item sets. + * + * @return mixed[][] + */ + private function items() + { + $items = []; + + foreach ($this->samples as $sample) { + foreach ($sample as $item) { + if (!in_array($item, $items, true)) { + $items[] = $item; + } + } + } + + return array_map(function($entry) { + return [$entry]; + }, $items); + } + + /** + * Returns frequent item sets only. + * + * @param mixed[][] $samples + * + * @return mixed[][] + */ + private function frequent(array $samples) + { + return array_filter($samples, function($entry) { + return $this->support($entry) >= $this->support; + }); + } + + /** + * Calculates frequent k item sets, where count($samples) == $k - 1. + * + * @param mixed[][] $samples + * + * @return mixed[][] + */ + private function candidates(array $samples) + { + $candidates = []; + + foreach ($samples as $p) { + foreach ($samples as $q) { + if (count(array_merge(array_diff($p, $q), array_diff($q, $p))) != 2) { + continue; + } + + $candidate = array_unique(array_merge($p, $q)); + + if ($this->contains($candidates, $candidate)) { + continue; + } + + foreach ((array)$this->samples as $sample) { + if ($this->subset($sample, $candidate)) { + $candidates[] = $candidate; + continue 2; + } + } + } + } + + return $candidates; + } + + /** + * Calculates confidence for $set. Confidence is the relative amount of sets containing $subset which also contain + * $set. + * + * @param mixed[] $set + * @param mixed[] $subset + * + * @return float + */ + private function confidence(array $set, array $subset) + { + return $this->support($set) / $this->support($subset); + } + + /** + * Calculates support for item set $sample. Support is the relative amount of sets containing $sample in the data + * pool. + * + * @see \Phpml\Association\Apriori::samples + * + * @param mixed[] $sample + * + * @return float + */ + private function support(array $sample) + { + return $this->frequency($sample) / count($this->samples); + } + + /** + * Counts occurrences of $sample as subset in data pool. + * + * @see \Phpml\Association\Apriori::samples + * + * @param mixed[] $sample + * + * @return int + */ + private function frequency(array $sample) + { + return count(array_filter($this->samples, function($entry) use ($sample) { + return $this->subset($entry, $sample); + })); + } + + /** + * Returns true if set is an element of system. + * + * @see \Phpml\Association\Apriori::equals() + * + * @param mixed[][] $system + * @param mixed[] $set + * + * @return bool + */ + private function contains(array $system, array $set) + { + return (bool)array_filter($system, function($entry) use ($set) { + return $this->equals($entry, $set); + }); + } + + /** + * Returns true if subset is a (proper) subset of set by its items string representation. + * + * @param mixed[] $set + * @param mixed[] $subset + * + * @return bool + */ + private function subset(array $set, array $subset) + { + return !array_diff($subset, array_intersect($subset, $set)); + } + + /** + * Returns true if string representation of items does not differ. + * + * @param mixed[] $set1 + * @param mixed[] $set2 + * + * @return bool + */ + private function equals(array $set1, array $set2) + { + return array_diff($set1, $set2) == array_diff($set2, $set1); + } +} diff --git a/src/Phpml/Association/Associator.php b/src/Phpml/Association/Associator.php new file mode 100644 index 00000000..c339b5e6 --- /dev/null +++ b/src/Phpml/Association/Associator.php @@ -0,0 +1,11 @@ +train($this->sampleGreek, []); + + $this->assertEquals('beta', $apriori->predict([['alpha', 'epsilon'], ['beta', 'theta']])[0][0][0]); + $this->assertEquals('alpha', $apriori->predict([['alpha', 'epsilon'], ['beta', 'theta']])[1][0][0]); + } + + public function testPowerSet() + { + $apriori = new Apriori(); + + $this->assertCount(8, $this->invoke($apriori, 'powerSet', [['a', 'b', 'c']])); + } + + public function testApriori() + { + $apriori = new Apriori(3 / 7); + $apriori->train($this->sampleBasket, []); + + $L = $apriori->apriori(); + + $this->assertCount(0, $L[3]); + $this->assertCount(4, $L[2]); + $this->assertTrue($this->invoke($apriori, 'contains', [$L[2], [1, 2]])); + $this->assertFalse($this->invoke($apriori, 'contains', [$L[2], [1, 3]])); + $this->assertFalse($this->invoke($apriori, 'contains', [$L[2], [1, 4]])); + $this->assertTrue($this->invoke($apriori, 'contains', [$L[2], [2, 3]])); + $this->assertTrue($this->invoke($apriori, 'contains', [$L[2], [2, 4]])); + $this->assertTrue($this->invoke($apriori, 'contains', [$L[2], [3, 4]])); + } + + public function testRules() + { + $apriori = new Apriori(0.4, 0.8); + $apriori->train($this->sampleChars, []); + + $this->assertCount(19, $apriori->rules()); + } + + public function testAntecedents() + { + $apriori = new Apriori(); + + $this->assertCount(6, $this->invoke($apriori, 'antecedents', [['a', 'b', 'c']])); + } + + public function testItems() + { + $apriori = new Apriori(); + $apriori->train($this->sampleGreek, []); + $this->assertCount(4, $this->invoke($apriori, 'items', [])); + } + + public function testFrequent() + { + $apriori = new Apriori(0.51); + $apriori->train($this->sampleGreek, []); + + $this->assertCount(0, $this->invoke($apriori, 'frequent', [[['epsilon'], ['theta']]])); + $this->assertCount(2, $this->invoke($apriori, 'frequent', [[['alpha'], ['beta']]])); + } + + public function testCandidates() + { + $apriori = new Apriori(); + $apriori->train($this->sampleGreek, []); + + $this->assertArraySubset([0 => ['alpha', 'beta']], $this->invoke($apriori, 'candidates', [[['alpha'], ['beta'], ['theta']]])); + $this->assertArraySubset([1 => ['alpha', 'theta']], $this->invoke($apriori, 'candidates', [[['alpha'], ['beta'], ['theta']]])); + $this->assertArraySubset([2 => ['beta', 'theta']], $this->invoke($apriori, 'candidates', [[['alpha'], ['beta'], ['theta']]])); + $this->assertCount(3, $this->invoke($apriori, 'candidates', [[['alpha'], ['beta'], ['theta']]])); + } + + public function testConfidence() + { + $apriori = new Apriori(); + $apriori->train($this->sampleGreek, []); + + $this->assertEquals(0.5, $this->invoke($apriori, 'confidence', [['alpha', 'beta', 'theta'], ['alpha', 'beta']])); + $this->assertEquals(1, $this->invoke($apriori, 'confidence', [['alpha', 'beta'], ['alpha']])); + } + + public function testSupport() + { + $apriori = new Apriori(); + $apriori->train($this->sampleGreek, []); + + $this->assertEquals(1.0, $this->invoke($apriori, 'support', [['alpha', 'beta']])); + $this->assertEquals(0.5, $this->invoke($apriori, 'support', [['epsilon']])); + } + + public function testFrequency() + { + $apriori = new Apriori(); + $apriori->train($this->sampleGreek, []); + + $this->assertEquals(4, $this->invoke($apriori, 'frequency', [['alpha', 'beta']])); + $this->assertEquals(2, $this->invoke($apriori, 'frequency', [['epsilon']])); + } + + public function testContains() + { + $apriori = new Apriori(); + + $this->assertTrue($this->invoke($apriori, 'contains', [[['a'], ['b']], ['a']])); + $this->assertTrue($this->invoke($apriori, 'contains', [[[1, 2]], [1, 2]])); + $this->assertFalse($this->invoke($apriori, 'contains', [[['a'], ['b']], ['c']])); + } + + public function testSubset() + { + $apriori = new Apriori(); + + $this->assertTrue($this->invoke($apriori, 'subset', [['a', 'b'], ['a']])); + $this->assertTrue($this->invoke($apriori, 'subset', [['a'], ['a']])); + $this->assertFalse($this->invoke($apriori, 'subset', [['a'], ['a', 'b']])); + } + + public function testEquals() + { + $apriori = new Apriori(); + + $this->assertTrue($this->invoke($apriori, 'equals', [['a'], ['a']])); + $this->assertFalse($this->invoke($apriori, 'equals', [['a'], []])); + $this->assertFalse($this->invoke($apriori, 'equals', [['a'], ['b', 'a']])); + } + + /** + * Invokes objects method. Private/protected will be set accessible. + * + * @param object &$object Instantiated object to be called on + * @param string $method Method name to be called + * @param array $params Array of params to be passed + * + * @return mixed + */ + public function invoke(&$object, $method, array $params = array()) + { + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($method); + $method->setAccessible(true); + + return $method->invokeArgs($object, $params); + } +} From 90038befa9e3505ca794ed14291dc8dd1d615b21 Mon Sep 17 00:00:00 2001 From: Patrick Florek Date: Fri, 2 Sep 2016 00:18:50 +0200 Subject: [PATCH 107/328] Apply comments / coding styles * Remove user-specific gitignore * Add return type hints * Avoid global namespace in docs * Rename rules -> getRules * Split up rule generation Todo: * Move set theory out to math * Extract rule generation --- .gitignore | 1 - docs/machine-learning/association/apriori.md | 12 ++- src/Phpml/Association/Apriori.php | 106 +++++++++++-------- tests/Phpml/Association/AprioriTest.php | 4 +- 4 files changed, 73 insertions(+), 50 deletions(-) diff --git a/.gitignore b/.gitignore index e85e1fde..8a409f4b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -/.idea/ /vendor/ humbuglog.* /bin/phpunit diff --git a/docs/machine-learning/association/apriori.md b/docs/machine-learning/association/apriori.md index c5986f40..544406e0 100644 --- a/docs/machine-learning/association/apriori.md +++ b/docs/machine-learning/association/apriori.md @@ -8,7 +8,9 @@ Association rule learning based on [Apriori algorithm](https://en.wikipedia.org/ * $confidence - [confidence](https://en.wikipedia.org/wiki/Association_rule_learning#Confidence), minimum relative amount of item set in frequent item sets ``` -$associator = new \Phpml\Association\Apriori($support = 0.5, $confidence = 0.5); +use Phpml\Association\Apriori; + +$associator = new Apriori($support = 0.5, $confidence = 0.5); ``` ### Train @@ -19,7 +21,9 @@ To train a associator simply provide train samples and labels (as `array`). Exam $samples = [['alpha', 'beta', 'epsilon'], ['alpha', 'beta', 'theta'], ['alpha', 'beta', 'epsilon'], ['alpha', 'beta', 'theta']]; $labels = []; -$associator = new \Phpml\Association\Apriori(0.5, 0.5); +use Phpml\Association\Apriori; + +$associator = new Apriori($support = 0.5, $confidence = 0.5); $associator->train($samples, $labels); ``` @@ -37,10 +41,10 @@ $associator->predict([['alpha','epsilon'],['beta','theta']]); ### Associating -Generating association rules simply use `rules` method. +Get generated association rules simply use `rules` method. ``` -$associator->rules(); +$associator->getRules(); // return [['antecedent' => ['alpha', 'theta'], 'consequent' => ['beta], 'support' => 1.0, 'confidence' => 1.0], ... ] ``` diff --git a/src/Phpml/Association/Apriori.php b/src/Phpml/Association/Apriori.php index bf52c27a..48556917 100644 --- a/src/Phpml/Association/Apriori.php +++ b/src/Phpml/Association/Apriori.php @@ -1,6 +1,6 @@ support = $support; + $this->support = $support; $this->confidence = $confidence; } /** - * Generates apriori association rules. + * Get all association rules which are generated for every k-length frequent item set. * * @return mixed[][] */ - public function rules() + public function getRules() : array { if (!$this->large) { $this->large = $this->apriori(); @@ -76,33 +76,19 @@ public function rules() $this->rules = []; - for ($k = 2; !empty($this->large[$k]); ++$k) { - foreach ($this->large[$k] as $frequent) { - foreach ($this->antecedents($frequent) as $antecedent) { - if ($this->confidence <= ($confidence = $this->confidence($frequent, $antecedent))) { - $consequent = array_values(array_diff($frequent, $antecedent)); - $this->rules[] = [ - self::ARRAY_KEY_ANTECEDENT => $antecedent, - self::ARRAY_KEY_CONSEQUENT => $consequent, - self::ARRAY_KEY_SUPPORT => $this->support($consequent), - self::ARRAY_KEY_CONFIDENCE => $confidence, - ]; - } - } - } - } + $this->generateAllRules(); return $this->rules; } /** - * Generates frequent item sets + * Generates frequent item sets. * * @return mixed[][][] */ - public function apriori() + public function apriori() : array { - $L = []; + $L = []; $L[1] = $this->items(); $L[1] = $this->frequent($L[1]); @@ -119,13 +105,47 @@ public function apriori() * * @return mixed[][] */ - protected function predictSample(array $sample) + protected function predictSample(array $sample) : array { - $predicts = array_values(array_filter($this->rules(), function($rule) use ($sample) { + $predicts = array_values(array_filter($this->getRules(), function ($rule) use ($sample) { return $this->equals($rule[self::ARRAY_KEY_ANTECEDENT], $sample); })); - return array_map(function($rule) { return $rule[self::ARRAY_KEY_CONSEQUENT]; }, $predicts); + return array_map(function ($rule) { + return $rule[self::ARRAY_KEY_CONSEQUENT]; + }, $predicts); + } + + /** + * Generate rules for each k-length frequent item set. + */ + private function generateAllRules() + { + for ($k = 2; !empty($this->large[$k]); ++$k) { + foreach ($this->large[$k] as $frequent) { + $this->generateRules($frequent); + } + } + } + + /** + * Generate confident rules for frequent item set. + * + * @param mixed[] $frequent + */ + private function generateRules(array $frequent) + { + foreach ($this->antecedents($frequent) as $antecedent) { + if ($this->confidence <= ($confidence = $this->confidence($frequent, $antecedent))) { + $consequent = array_values(array_diff($frequent, $antecedent)); + $this->rules[] = [ + self::ARRAY_KEY_ANTECEDENT => $antecedent, + self::ARRAY_KEY_CONSEQUENT => $consequent, + self::ARRAY_KEY_SUPPORT => $this->support($consequent), + self::ARRAY_KEY_CONFIDENCE => $confidence, + ]; + } + } } /** @@ -135,7 +155,7 @@ protected function predictSample(array $sample) * * @return mixed[][] */ - private function powerSet(array $sample) + private function powerSet(array $sample) : array { $results = [[]]; foreach ($sample as $item) { @@ -154,12 +174,12 @@ private function powerSet(array $sample) * * @return mixed[][] */ - private function antecedents(array $sample) + private function antecedents(array $sample) : array { $cardinality = count($sample); $antecedents = $this->powerSet($sample); - return array_filter($antecedents, function($antecedent) use ($cardinality) { + return array_filter($antecedents, function ($antecedent) use ($cardinality) { return (count($antecedent) != $cardinality) && ($antecedent != []); }); } @@ -169,7 +189,7 @@ private function antecedents(array $sample) * * @return mixed[][] */ - private function items() + private function items() : array { $items = []; @@ -181,7 +201,7 @@ private function items() } } - return array_map(function($entry) { + return array_map(function ($entry) { return [$entry]; }, $items); } @@ -193,9 +213,9 @@ private function items() * * @return mixed[][] */ - private function frequent(array $samples) + private function frequent(array $samples) : array { - return array_filter($samples, function($entry) { + return array_filter($samples, function ($entry) { return $this->support($entry) >= $this->support; }); } @@ -207,7 +227,7 @@ private function frequent(array $samples) * * @return mixed[][] */ - private function candidates(array $samples) + private function candidates(array $samples) : array { $candidates = []; @@ -223,7 +243,7 @@ private function candidates(array $samples) continue; } - foreach ((array)$this->samples as $sample) { + foreach ((array) $this->samples as $sample) { if ($this->subset($sample, $candidate)) { $candidates[] = $candidate; continue 2; @@ -244,7 +264,7 @@ private function candidates(array $samples) * * @return float */ - private function confidence(array $set, array $subset) + private function confidence(array $set, array $subset) : float { return $this->support($set) / $this->support($subset); } @@ -259,7 +279,7 @@ private function confidence(array $set, array $subset) * * @return float */ - private function support(array $sample) + private function support(array $sample) : float { return $this->frequency($sample) / count($this->samples); } @@ -273,9 +293,9 @@ private function support(array $sample) * * @return int */ - private function frequency(array $sample) + private function frequency(array $sample) : int { - return count(array_filter($this->samples, function($entry) use ($sample) { + return count(array_filter($this->samples, function ($entry) use ($sample) { return $this->subset($entry, $sample); })); } @@ -290,9 +310,9 @@ private function frequency(array $sample) * * @return bool */ - private function contains(array $system, array $set) + private function contains(array $system, array $set) : bool { - return (bool)array_filter($system, function($entry) use ($set) { + return (bool) array_filter($system, function ($entry) use ($set) { return $this->equals($entry, $set); }); } @@ -305,7 +325,7 @@ private function contains(array $system, array $set) * * @return bool */ - private function subset(array $set, array $subset) + private function subset(array $set, array $subset) : bool { return !array_diff($subset, array_intersect($subset, $set)); } @@ -318,7 +338,7 @@ private function subset(array $set, array $subset) * * @return bool */ - private function equals(array $set1, array $set2) + private function equals(array $set1, array $set2) : bool { return array_diff($set1, $set2) == array_diff($set2, $set1); } diff --git a/tests/Phpml/Association/AprioriTest.php b/tests/Phpml/Association/AprioriTest.php index 9cc595d1..b249ff6f 100644 --- a/tests/Phpml/Association/AprioriTest.php +++ b/tests/Phpml/Association/AprioriTest.php @@ -71,12 +71,12 @@ public function testApriori() $this->assertTrue($this->invoke($apriori, 'contains', [$L[2], [3, 4]])); } - public function testRules() + public function testGetRules() { $apriori = new Apriori(0.4, 0.8); $apriori->train($this->sampleChars, []); - $this->assertCount(19, $apriori->rules()); + $this->assertCount(19, $apriori->getRules()); } public function testAntecedents() From fa87eca3755b8304b0a504b28fa5b9ba43a0999a Mon Sep 17 00:00:00 2001 From: Patrick Florek Date: Sat, 10 Sep 2016 13:24:43 +0200 Subject: [PATCH 108/328] Add new class Set for simple Set-theoretical operations ### Features * Works only with primitive types int, float, string * Implements set theortic operations union, intersection, complement * Modifies set by adding, removing elements * Implements \IteratorAggregate for use in loops ### Implementation details Based on array functions: * array_diff, * array_merge, * array_intersection, * array_unique, * array_values, * sort. ### Drawbacks * **Do not work with objects.** * Power set and Cartesian product returning array of Set --- docs/math/set.md | 127 +++++++++++++++++++++ src/Phpml/Math/Set.php | 211 +++++++++++++++++++++++++++++++++++ tests/Phpml/Math/SetTest.php | 93 +++++++++++++++ 3 files changed, 431 insertions(+) create mode 100644 docs/math/set.md create mode 100644 src/Phpml/Math/Set.php create mode 100644 tests/Phpml/Math/SetTest.php diff --git a/docs/math/set.md b/docs/math/set.md new file mode 100644 index 00000000..fa016ed9 --- /dev/null +++ b/docs/math/set.md @@ -0,0 +1,127 @@ +# Set + +Class that wraps PHP arrays containing primitive types to mathematical sets. + +### Creation + +To create Set use flat arrays containing primitives only: + +``` +use \Phpml\Math\Set; + +$set = new Set([1, 2, 2, 3, 1.1, -1, -10]); +$set->toArray(); +// return [-10, -1, 1, 1.1, 2, 3] + +$set = new Set(['B', '', 'A']); +$set->toArray(); +// return ['', 'A', 'B'] +``` + +Injected array is sorted by SORT_ASC, duplicates are removed and index is rewritten. + +### Union + +Create the union of two Sets: + +``` +use \Phpml\Math\Set; + +$union = Set::union(new Set([1, 3]), new Set([1, 2])); +$union->toArray(); +//return [1, 2, 3] +``` + +### Intersection + +Create the intersection of two Sets: + +``` +use \Phpml\Math\Set; + +$intersection = Set::intersection(new Set(['A', 'C']), new Set(['B', 'C'])); +$intersection->toArray(); +//return ['C'] +``` + +### Complement + +Create the set-theoretic difference of two Sets: + +``` +use \Phpml\Math\Set; + +$difference = Set::difference(new Set(['A', 'B', 'C']), new Set(['A'])); +$union->toArray(); +//return ['B', 'C'] +``` + +### Adding elements + +``` +use \Phpml\Math\Set; + +$set = new Set([1, 2]); +$set->addAll([3]); +$set->add(4); +$set->toArray(); +//return [1, 2, 3, 4] +``` + +### Removing elements + +``` +use \Phpml\Math\Set; + +$set = new Set([1, 2]); +$set->removeAll([2]); +$set->remove(1); +$set->toArray(); +//return [] +``` + +### Check membership + +``` +use \Phpml\Math\Set; + +$set = new Set([1, 2]); +$set->containsAll([2, 3]); +//return false +$set->contains(1); +//return true +``` + +### Cardinality + +``` +use \Phpml\Math\Set; + +$set = new Set([1, 2]); +$set->cardinality(); +//return 2 +``` + +### Is empty + +``` +use \Phpml\Math\Set; + +$set = new Set(); +$set->isEmpty(); +//return true +``` + +### Working with loops + +``` +use \Phpml\Math\Set; + +$set = new Set(['A', 'B', 'C']); + +foreach($set as $element) { + echo "$element, "; +} + +// echoes A, B, C +``` diff --git a/src/Phpml/Math/Set.php b/src/Phpml/Math/Set.php new file mode 100644 index 00000000..20fc7809 --- /dev/null +++ b/src/Phpml/Math/Set.php @@ -0,0 +1,211 @@ +elements = self::sanitize($elements); + } + + /** + * Creates the union of A and B. + * + * @param Set $a + * @param Set $b + * + * @return Set + */ + public static function union(Set $a, Set $b) : Set + { + return new self(array_merge($a->toArray(), $b->toArray())); + } + + /** + * Creates the intersection of A and B. + * + * @param Set $a + * @param Set $b + * + * @return Set + */ + public static function intersection(Set $a, Set $b) : Set + { + return new self(array_intersect($a->toArray(), $b->toArray())); + } + + /** + * Creates the difference of A and B. + * + * @param Set $a + * @param Set $b + * + * @return Set + */ + public static function difference(Set $a, Set $b) : Set + { + return new self(array_diff($a->toArray(), $b->toArray())); + } + + /** + * Creates the Cartesian product of A and B. + * + * @param Set $a + * @param Set $b + * + * @return Set[] + */ + public static function cartesian(Set $a, Set $b) : array + { + $cartesian = []; + + foreach ($a as $multiplier) { + foreach ($b as $multiplicand) { + $cartesian[] = new self(array_merge([$multiplicand], [$multiplier])); + } + } + + return $cartesian; + } + + /** + * Creates the power set of A. + * + * @param Set $a + * + * @return Set[] + */ + public static function power(Set $a) : array + { + $power = [new self()]; + + foreach ($a as $multiplicand) { + foreach ($power as $multiplier) { + $power[] = new self(array_merge([$multiplicand], $multiplier->toArray())); + } + } + + return $power; + } + + /** + * Removes duplicates and rewrites index. + * + * @param string[]|int[]|float[] $elements + * + * @return string[]|int[]|float[] + */ + private static function sanitize(array $elements) : array + { + sort($elements, SORT_ASC); + + return array_values(array_unique($elements, SORT_ASC)); + } + + /** + * @param string|int|float $element + * + * @return Set + */ + public function add($element) : Set + { + return $this->addAll([$element]); + } + + /** + * @param string[]|int[]|float[] $elements + * + * @return Set + */ + public function addAll(array $elements) : Set + { + $this->elements = self::sanitize(array_merge($this->elements, $elements)); + + return $this; + } + + /** + * @param string|int|float $element + * + * @return Set + */ + public function remove($element) : Set + { + return $this->removeAll([$element]); + } + + /** + * @param string[]|int[]|float[] $elements + * + * @return Set + */ + public function removeAll(array $elements) : Set + { + $this->elements = self::sanitize(array_diff($this->elements, $elements)); + + return $this; + } + + /** + * @param string|int|float $element + * + * @return bool + */ + public function contains($element) : bool + { + return $this->containsAll([$element]); + } + + /** + * @param string[]|int[]|float[] $elements + * + * @return bool + */ + public function containsAll(array $elements) : bool + { + return !array_diff($elements, $this->elements); + } + + /** + * @return string[]|int[]|float[] + */ + public function toArray() : array + { + return $this->elements; + } + + /** + * @return \ArrayIterator + */ + public function getIterator() : \ArrayIterator + { + return new \ArrayIterator($this->elements); + } + + /** + * @return bool + */ + public function isEmpty() : bool + { + return $this->cardinality() == 0; + } + + /** + * @return int + */ + public function cardinality() : int + { + return count($this->elements); + } +} diff --git a/tests/Phpml/Math/SetTest.php b/tests/Phpml/Math/SetTest.php new file mode 100644 index 00000000..cdf4460e --- /dev/null +++ b/tests/Phpml/Math/SetTest.php @@ -0,0 +1,93 @@ +assertInstanceOf('\Phpml\Math\Set', $union); + $this->assertEquals(new Set([1, 2, 3]), $union); + $this->assertEquals(3, $union->cardinality()); + } + + public function testIntersection() + { + $intersection = Set::intersection(new Set(['C', 'A']), new Set(['B', 'C'])); + + $this->assertInstanceOf('\Phpml\Math\Set', $intersection); + $this->assertEquals(new Set(['C']), $intersection); + $this->assertEquals(1, $intersection->cardinality()); + } + + public function testDifference() + { + $difference = Set::difference(new Set(['C', 'A', 'B']), new Set(['A'])); + + $this->assertInstanceOf('\Phpml\Math\Set', $difference); + $this->assertEquals(new Set(['B', 'C']), $difference); + $this->assertEquals(2, $difference->cardinality()); + } + + public function testPower() + { + $power = Set::power(new Set(['A', 'B'])); + + $this->assertInternalType('array', $power); + $this->assertEquals([new Set(), new Set(['A']), new Set(['B']), new Set(['A', 'B'])], $power); + $this->assertEquals(4, count($power)); + } + + public function testCartesian() + { + $cartesian = Set::cartesian(new Set(['A']), new Set([1, 2])); + + $this->assertInternalType('array', $cartesian); + $this->assertEquals([new Set(['A', 1]), new Set(['A', 2])], $cartesian); + $this->assertEquals(2, count($cartesian)); + } + + public function testContains() + { + $set = new Set(['B', 'A', 2, 1]); + + $this->assertTrue($set->contains('B')); + $this->assertTrue($set->containsAll(['A', 'B'])); + + $this->assertFalse($set->contains('C')); + $this->assertFalse($set->containsAll(['A', 'B', 'C'])); + } + + public function testRemove() + { + $set = new Set(['B', 'A', 2, 1]); + + $this->assertEquals((new Set([1, 2, 2, 2, 'B']))->toArray(), $set->remove('A')->toArray()); + } + + public function testAdd() + { + $set = new Set(['B', 'A', 2, 1]); + $set->addAll(['foo', 'bar']); + $this->assertEquals(6, $set->cardinality()); + } + + public function testEmpty() + { + $set = new Set([1, 2]); + $set->removeAll([2, 1]); + $this->assertEquals(new Set(), $set); + $this->assertTrue($set->isEmpty()); + } + + public function testToArray() + { + $set = new Set([1, 2, 2, 3, 'A', false, '', 1.1, -1, -10, 'B']); + + $this->assertEquals([-10, '', -1, 'A', 'B', 1, 1.1, 2, 3], $set->toArray()); + } +} From 1ff455ebedeed1dba101a474aa2810eb0ebc61f1 Mon Sep 17 00:00:00 2001 From: Patrick Florek Date: Sat, 17 Sep 2016 22:02:48 +0200 Subject: [PATCH 109/328] Add index entries --- README.md | 3 +++ docs/index.md | 3 +++ 2 files changed, 6 insertions(+) diff --git a/README.md b/README.md index 93574fc1..31fdb603 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,8 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( ## Features +* Association rule learning + * [Apriori](http://php-ml.readthedocs.io/en/latest/machine-learning/association/apriori/) * Classification * [SVC](http://php-ml.readthedocs.io/en/latest/machine-learning/classification/svc/) * [k-Nearest Neighbors](http://php-ml.readthedocs.io/en/latest/machine-learning/classification/k-nearest-neighbors/) @@ -85,6 +87,7 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * Math * [Distance](http://php-ml.readthedocs.io/en/latest/math/distance/) * [Matrix](http://php-ml.readthedocs.io/en/latest/math/matrix/) + * [Set](http://php-ml.readthedocs.io/en/latest/math/set/) * [Statistic](http://php-ml.readthedocs.io/en/latest/math/statistic/) ## Contribute diff --git a/docs/index.md b/docs/index.md index 1a386428..423877fc 100644 --- a/docs/index.md +++ b/docs/index.md @@ -46,6 +46,8 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( ## Features +* Association rule Lerning + * [Apriori](machine-learning/association/apriori/) * Classification * [SVC](machine-learning/classification/svc/) * [k-Nearest Neighbors](machine-learning/classification/k-nearest-neighbors/) @@ -85,6 +87,7 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * Math * [Distance](math/distance/) * [Matrix](math/matrix/) + * [Set](math/set/) * [Statistic](math/statistic/) From 8072ddb2bfbf2a2f47129da53995f9638abb91a7 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 21 Sep 2016 21:46:16 +0200 Subject: [PATCH 110/328] Update phpunit to 5.5 --- composer.json | 2 +- composer.lock | 332 +++++++++++++----- .../NeuralNetwork/Node/Neuron/SynapseTest.php | 4 +- tests/Phpml/NeuralNetwork/Node/NeuronTest.php | 4 +- 4 files changed, 252 insertions(+), 90 deletions(-) diff --git a/composer.json b/composer.json index eeccc53e..97841547 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "php": ">=7.0.0" }, "require-dev": { - "phpunit/phpunit": "^5.2" + "phpunit/phpunit": "^5.5" }, "config": { "bin-dir": "bin" diff --git a/composer.lock b/composer.lock index bf1fd1f3..27f18291 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "f3e2d9975d300b3ea4c3568de44d8499", - "content-hash": "087091d0c339e9fa3a551a189ea658bf", + "hash": "471cccec358c6643fd2526258b91a0ba", + "content-hash": "be926d8a68fbc47e08c64340c062a392", "packages": [], "packages-dev": [ { @@ -64,16 +64,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.5.1", + "version": "1.5.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "a8773992b362b58498eed24bf85005f363c34771" + "reference": "ea74994a3dc7f8d2f65a06009348f2d63c81e61f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/a8773992b362b58498eed24bf85005f363c34771", - "reference": "a8773992b362b58498eed24bf85005f363c34771", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/ea74994a3dc7f8d2f65a06009348f2d63c81e61f", + "reference": "ea74994a3dc7f8d2f65a06009348f2d63c81e61f", "shasum": "" }, "require": { @@ -102,41 +102,138 @@ "object", "object graph" ], - "time": "2015-11-20 12:04:31" + "time": "2016-09-16 13:37:59" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2015-12-27 11:43:31" }, { "name": "phpdocumentor/reflection-docblock", - "version": "2.0.4", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" + "reference": "9270140b940ff02e58ec577c237274e92cd40cdd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", - "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9270140b940ff02e58ec577c237274e92cd40cdd", + "reference": "9270140b940ff02e58ec577c237274e92cd40cdd", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0@dev", + "phpdocumentor/type-resolver": "^0.2.0", + "webmozart/assert": "^1.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" }, - "suggest": { - "dflydev/markdown": "~1.0", - "erusev/parsedown": "~1.0" + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2016-06-10 09:48:41" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b39c7a5b194f9ed7bd0dd345c751007a41862443", + "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { - "psr-0": { - "phpDocumentor": [ + "psr-4": { + "phpDocumentor\\Reflection\\": [ "src/" ] } @@ -148,39 +245,39 @@ "authors": [ { "name": "Mike van Riel", - "email": "mike.vanriel@naenius.com" + "email": "me@mikevanriel.com" } ], - "time": "2015-02-03 12:10:50" + "time": "2016-06-10 07:14:17" }, { "name": "phpspec/prophecy", - "version": "v1.6.0", + "version": "v1.6.1", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972" + "reference": "58a8137754bc24b25740d4281399a4a3596058e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3c91bdf81797d725b14cb62906f9a4ce44235972", - "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/58a8137754bc24b25740d4281399a4a3596058e0", + "reference": "58a8137754bc24b25740d4281399a4a3596058e0", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "~2.0", - "sebastian/comparator": "~1.1", - "sebastian/recursion-context": "~1.0" + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", + "sebastian/comparator": "^1.1", + "sebastian/recursion-context": "^1.0" }, "require-dev": { - "phpspec/phpspec": "~2.0" + "phpspec/phpspec": "^2.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.5.x-dev" + "dev-master": "1.6.x-dev" } }, "autoload": { @@ -213,20 +310,20 @@ "spy", "stub" ], - "time": "2016-02-15 07:46:21" + "time": "2016-06-07 08:13:47" }, { "name": "phpunit/php-code-coverage", - "version": "3.3.1", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "2431befdd451fac43fbcde94d1a92fb3b8b68f86" + "reference": "5f3f7e736d6319d5f1fc402aff8b026da26709a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2431befdd451fac43fbcde94d1a92fb3b8b68f86", - "reference": "2431befdd451fac43fbcde94d1a92fb3b8b68f86", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5f3f7e736d6319d5f1fc402aff8b026da26709a3", + "reference": "5f3f7e736d6319d5f1fc402aff8b026da26709a3", "shasum": "" }, "require": { @@ -235,12 +332,12 @@ "phpunit/php-text-template": "~1.2", "phpunit/php-token-stream": "^1.4.2", "sebastian/code-unit-reverse-lookup": "~1.0", - "sebastian/environment": "^1.3.2", + "sebastian/environment": "^1.3.2 || ^2.0", "sebastian/version": "~1.0|~2.0" }, "require-dev": { "ext-xdebug": ">=2.1.4", - "phpunit/phpunit": "~5" + "phpunit/phpunit": "^5.4" }, "suggest": { "ext-dom": "*", @@ -250,7 +347,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3.x-dev" + "dev-master": "4.0.x-dev" } }, "autoload": { @@ -276,7 +373,7 @@ "testing", "xunit" ], - "time": "2016-04-08 08:14:53" + "time": "2016-07-26 14:39:29" }, { "name": "phpunit/php-file-iterator", @@ -368,21 +465,24 @@ }, { "name": "phpunit/php-timer", - "version": "1.0.7", + "version": "1.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b" + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b", - "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260", "shasum": "" }, "require": { "php": ">=5.3.3" }, + "require-dev": { + "phpunit/phpunit": "~4|~5" + }, "type": "library", "autoload": { "classmap": [ @@ -405,7 +505,7 @@ "keywords": [ "timer" ], - "time": "2015-06-21 08:01:12" + "time": "2016-05-12 18:03:57" }, { "name": "phpunit/php-token-stream", @@ -458,35 +558,35 @@ }, { "name": "phpunit/phpunit", - "version": "5.3.2", + "version": "5.5.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "2c6da3536035617bae3fe3db37283c9e0eb63ab3" + "reference": "a57126dc681b08289fef6ac96a48e30656f84350" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2c6da3536035617bae3fe3db37283c9e0eb63ab3", - "reference": "2c6da3536035617bae3fe3db37283c9e0eb63ab3", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a57126dc681b08289fef6ac96a48e30656f84350", + "reference": "a57126dc681b08289fef6ac96a48e30656f84350", "shasum": "" }, "require": { "ext-dom": "*", "ext-json": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-spl": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", "myclabs/deep-copy": "~1.3", "php": "^5.6 || ^7.0", "phpspec/prophecy": "^1.3.1", - "phpunit/php-code-coverage": "^3.3.0", + "phpunit/php-code-coverage": "^4.0.1", "phpunit/php-file-iterator": "~1.4", "phpunit/php-text-template": "~1.2", "phpunit/php-timer": "^1.0.6", - "phpunit/phpunit-mock-objects": "^3.1", + "phpunit/phpunit-mock-objects": "^3.2", "sebastian/comparator": "~1.1", "sebastian/diff": "~1.2", - "sebastian/environment": "~1.3", + "sebastian/environment": "^1.3 || ^2.0", "sebastian/exporter": "~1.2", "sebastian/global-state": "~1.0", "sebastian/object-enumerator": "~1.0", @@ -494,7 +594,15 @@ "sebastian/version": "~1.0|~2.0", "symfony/yaml": "~2.1|~3.0" }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2" + }, + "require-dev": { + "ext-pdo": "*" + }, "suggest": { + "ext-tidy": "*", + "ext-xdebug": "*", "phpunit/php-invoker": "~1.1" }, "bin": [ @@ -503,7 +611,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.3.x-dev" + "dev-master": "5.5.x-dev" } }, "autoload": { @@ -529,30 +637,33 @@ "testing", "xunit" ], - "time": "2016-04-12 16:20:08" + "time": "2016-09-21 14:40:13" }, { "name": "phpunit/phpunit-mock-objects", - "version": "3.1.3", + "version": "3.2.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "151c96874bff6fe61a25039df60e776613a61489" + "reference": "546898a2c0c356ef2891b39dd7d07f5d82c8ed0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/151c96874bff6fe61a25039df60e776613a61489", - "reference": "151c96874bff6fe61a25039df60e776613a61489", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/546898a2c0c356ef2891b39dd7d07f5d82c8ed0a", + "reference": "546898a2c0c356ef2891b39dd7d07f5d82c8ed0a", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", - "php": ">=5.6", - "phpunit/php-text-template": "~1.2", - "sebastian/exporter": "~1.2" + "php": "^5.6 || ^7.0", + "phpunit/php-text-template": "^1.2", + "sebastian/exporter": "^1.2" + }, + "conflict": { + "phpunit/phpunit": "<5.4.0" }, "require-dev": { - "phpunit/phpunit": "~5" + "phpunit/phpunit": "^5.4" }, "suggest": { "ext-soap": "*" @@ -560,7 +671,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-master": "3.2.x-dev" } }, "autoload": { @@ -585,7 +696,7 @@ "mock", "xunit" ], - "time": "2016-04-20 14:39:26" + "time": "2016-09-06 16:07:45" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -750,23 +861,23 @@ }, { "name": "sebastian/environment", - "version": "1.3.6", + "version": "1.3.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "2292b116f43c272ff4328083096114f84ea46a56" + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/2292b116f43c272ff4328083096114f84ea46a56", - "reference": "2292b116f43c272ff4328083096114f84ea46a56", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^5.3.3 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^4.8 || ^5.0" }, "type": "library", "extra": { @@ -796,20 +907,20 @@ "environment", "hhvm" ], - "time": "2016-05-04 07:59:13" + "time": "2016-08-18 05:49:44" }, { "name": "sebastian/exporter", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "7ae5513327cb536431847bcc0c10edba2701064e" + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e", - "reference": "7ae5513327cb536431847bcc0c10edba2701064e", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", "shasum": "" }, "require": { @@ -817,12 +928,13 @@ "sebastian/recursion-context": "~1.0" }, "require-dev": { + "ext-mbstring": "*", "phpunit/phpunit": "~4.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2.x-dev" + "dev-master": "1.3.x-dev" } }, "autoload": { @@ -862,7 +974,7 @@ "export", "exporter" ], - "time": "2015-06-21 07:55:53" + "time": "2016-06-17 09:04:28" }, { "name": "sebastian/global-state", @@ -1101,16 +1213,16 @@ }, { "name": "symfony/yaml", - "version": "v3.0.5", + "version": "v3.1.4", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "0047c8366744a16de7516622c5b7355336afae96" + "reference": "f291ed25eb1435bddbe8a96caaef16469c2a092d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/0047c8366744a16de7516622c5b7355336afae96", - "reference": "0047c8366744a16de7516622c5b7355336afae96", + "url": "https://api.github.com/repos/symfony/yaml/zipball/f291ed25eb1435bddbe8a96caaef16469c2a092d", + "reference": "f291ed25eb1435bddbe8a96caaef16469c2a092d", "shasum": "" }, "require": { @@ -1119,7 +1231,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.1-dev" } }, "autoload": { @@ -1146,7 +1258,57 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2016-03-04 07:55:57" + "time": "2016-09-02 02:12:52" + }, + { + "name": "webmozart/assert", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "bb2d123231c095735130cc8f6d31385a44c7b308" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/bb2d123231c095735130cc8f6d31385a44c7b308", + "reference": "bb2d123231c095735130cc8f6d31385a44c7b308", + "shasum": "" + }, + "require": { + "php": "^5.3.3|^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2016-08-09 15:02:57" } ], "aliases": [], diff --git a/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php b/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php index 9ad733d0..82560f1b 100644 --- a/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php +++ b/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php @@ -44,8 +44,8 @@ public function testSynapseWeightChange() */ private function getNodeMock($output = 1) { - $node = $this->getMock(Neuron::class); - $node->method('getOutput')->willReturn($nodeOutput = 0.5); + $node = $this->getMockBuilder(Neuron::class)->getMock(); + $node->method('getOutput')->willReturn($output); return $node; } diff --git a/tests/Phpml/NeuralNetwork/Node/NeuronTest.php b/tests/Phpml/NeuralNetwork/Node/NeuronTest.php index c416ffd5..13bf6c55 100644 --- a/tests/Phpml/NeuralNetwork/Node/NeuronTest.php +++ b/tests/Phpml/NeuralNetwork/Node/NeuronTest.php @@ -20,7 +20,7 @@ public function testNeuronInitialization() public function testNeuronActivationFunction() { - $activationFunction = $this->getMock(BinaryStep::class); + $activationFunction = $this->getMockBuilder(BinaryStep::class)->getMock(); $activationFunction->method('compute')->with(0)->willReturn($output = 0.69); $neuron = new Neuron($activationFunction); @@ -57,7 +57,7 @@ public function testNeuronRefresh() */ private function getSynapseMock($output = 2) { - $synapse = $this->getMock(Synapse::class, [], [], '', false); + $synapse = $this->getMockBuilder(Synapse::class)->disableOriginalConstructor()->getMock(); $synapse->method('getOutput')->willReturn($output); return $synapse; From 1ce6bb544be2cec491594a3dc69f136e10c90edf Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 21 Sep 2016 21:51:19 +0200 Subject: [PATCH 111/328] Run php-cs-fixer --- src/Phpml/Association/Apriori.php | 2 +- src/Phpml/Association/Associator.php | 2 +- src/Phpml/Math/Set.php | 2 +- tests/Phpml/Association/AprioriTest.php | 10 +++++----- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Phpml/Association/Apriori.php b/src/Phpml/Association/Apriori.php index 48556917..bec1510f 100644 --- a/src/Phpml/Association/Apriori.php +++ b/src/Phpml/Association/Apriori.php @@ -1,6 +1,6 @@ getMethod($method); + $method = $reflection->getMethod($method); $method->setAccessible(true); return $method->invokeArgs($object, $params); From 84af842f0442101af64679b09e23922d04bde1dd Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 27 Sep 2016 20:07:21 +0200 Subject: [PATCH 112/328] Fix division by zero in ClassificationReport #21 --- src/Phpml/Metric/ClassificationReport.php | 43 ++++++++++++++++--- .../Phpml/Metric/ClassificationReportTest.php | 20 +++++++++ 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/src/Phpml/Metric/ClassificationReport.php b/src/Phpml/Metric/ClassificationReport.php index 31ee0b76..b1b135f4 100644 --- a/src/Phpml/Metric/ClassificationReport.php +++ b/src/Phpml/Metric/ClassificationReport.php @@ -37,7 +37,7 @@ class ClassificationReport */ public function __construct(array $actualLabels, array $predictedLabels) { - $truePositive = $falsePositive = $falseNegative = $this->support = self::getLabelIndexedArray($actualLabels); + $truePositive = $falsePositive = $falseNegative = $this->support = self::getLabelIndexedArray($actualLabels, $predictedLabels); foreach ($actualLabels as $index => $actual) { $predicted = $predictedLabels[$index]; @@ -103,8 +103,8 @@ public function getAverage() private function computeMetrics(array $truePositive, array $falsePositive, array $falseNegative) { foreach ($truePositive as $label => $tp) { - $this->precision[$label] = $tp / ($tp + $falsePositive[$label]); - $this->recall[$label] = $tp / ($tp + $falseNegative[$label]); + $this->precision[$label] = $this->computePrecision($tp, $falsePositive[$label]); + $this->recall[$label] = $this->computeRecall($tp, $falseNegative[$label]); $this->f1score[$label] = $this->computeF1Score((float) $this->precision[$label], (float) $this->recall[$label]); } } @@ -117,6 +117,36 @@ private function computeAverage() } } + /** + * @param int $truePositive + * @param int $falsePositive + * + * @return float|string + */ + private function computePrecision(int $truePositive, int $falsePositive) + { + if (0 == ($divider = $truePositive + $falsePositive)) { + return 0.0; + } + + return $truePositive / $divider; + } + + /** + * @param int $truePositive + * @param int $falseNegative + * + * @return float|string + */ + private function computeRecall(int $truePositive, int $falseNegative) + { + if (0 == ($divider = $truePositive + $falseNegative)) { + return 0.0; + } + + return $truePositive / $divider; + } + /** * @param float $precision * @param float $recall @@ -133,13 +163,14 @@ private function computeF1Score(float $precision, float $recall): float } /** - * @param array $labels + * @param array $actualLabels + * @param array $predictedLabels * * @return array */ - private static function getLabelIndexedArray(array $labels): array + private static function getLabelIndexedArray(array $actualLabels, array $predictedLabels): array { - $labels = array_values(array_unique($labels)); + $labels = array_values(array_unique(array_merge($actualLabels, $predictedLabels))); sort($labels); $labels = array_combine($labels, array_fill(0, count($labels), 0)); diff --git a/tests/Phpml/Metric/ClassificationReportTest.php b/tests/Phpml/Metric/ClassificationReportTest.php index f0f1cd36..6ccc21d1 100644 --- a/tests/Phpml/Metric/ClassificationReportTest.php +++ b/tests/Phpml/Metric/ClassificationReportTest.php @@ -47,4 +47,24 @@ public function testClassificationReportGenerateWithNumericLabels() $this->assertEquals($support, $report->getSupport(), '', 0.01); $this->assertEquals($average, $report->getAverage(), '', 0.01); } + + public function testPreventDivideByZeroWhenTruePositiveAndFalsePositiveSumEqualsZero() + { + $labels = [1, 2]; + $predicted = [2, 2]; + + $report = new ClassificationReport($labels, $predicted); + + $this->assertEquals([1 => 0.0, 2 => 0.5], $report->getPrecision(), '', 0.01); + } + + public function testPreventDivideByZeroWhenTruePositiveAndFalseNegativeSumEqualsZero() + { + $labels = [2, 2, 1]; + $predicted = [2, 2, 3]; + + $report = new ClassificationReport($labels, $predicted); + + $this->assertEquals([1 => 0.0, 2 => 1, 3 => 0], $report->getPrecision(), '', 0.01); + } } From 349ea16f01bced107daf2fa070fb2b27517e8c2b Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 30 Sep 2016 14:02:08 +0200 Subject: [PATCH 113/328] Rename demo datasets and add Dataset suffix --- src/Phpml/Dataset/Demo/{Glass.php => GlassDataset.php} | 2 +- src/Phpml/Dataset/Demo/{Iris.php => IrisDataset.php} | 2 +- src/Phpml/Dataset/Demo/{Wine.php => WineDataset.php} | 2 +- .../Dataset/Demo/{GlassTest.php => GlassDatasetTest.php} | 6 +++--- .../Dataset/Demo/{IrisTest.php => IrisDatasetTest.php} | 6 +++--- .../Dataset/Demo/{WineTest.php => WineDatasetTest.php} | 6 +++--- tests/Phpml/Metric/AccuracyTest.php | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) rename src/Phpml/Dataset/Demo/{Glass.php => GlassDataset.php} (93%) rename src/Phpml/Dataset/Demo/{Iris.php => IrisDataset.php} (90%) rename src/Phpml/Dataset/Demo/{Wine.php => WineDataset.php} (91%) rename tests/Phpml/Dataset/Demo/{GlassTest.php => GlassDatasetTest.php} (74%) rename tests/Phpml/Dataset/Demo/{IrisTest.php => IrisDatasetTest.php} (74%) rename tests/Phpml/Dataset/Demo/{WineTest.php => WineDatasetTest.php} (75%) diff --git a/src/Phpml/Dataset/Demo/Glass.php b/src/Phpml/Dataset/Demo/GlassDataset.php similarity index 93% rename from src/Phpml/Dataset/Demo/Glass.php rename to src/Phpml/Dataset/Demo/GlassDataset.php index 2a3d7e26..aa591f32 100644 --- a/src/Phpml/Dataset/Demo/Glass.php +++ b/src/Phpml/Dataset/Demo/GlassDataset.php @@ -18,7 +18,7 @@ * Samples total: 214 * Features per sample: 9. */ -class Glass extends CsvDataset +class GlassDataset extends CsvDataset { public function __construct() { diff --git a/src/Phpml/Dataset/Demo/Iris.php b/src/Phpml/Dataset/Demo/IrisDataset.php similarity index 90% rename from src/Phpml/Dataset/Demo/Iris.php rename to src/Phpml/Dataset/Demo/IrisDataset.php index 923f0bac..509d9f4c 100644 --- a/src/Phpml/Dataset/Demo/Iris.php +++ b/src/Phpml/Dataset/Demo/IrisDataset.php @@ -12,7 +12,7 @@ * Samples total: 150 * Features per sample: 4. */ -class Iris extends CsvDataset +class IrisDataset extends CsvDataset { public function __construct() { diff --git a/src/Phpml/Dataset/Demo/Wine.php b/src/Phpml/Dataset/Demo/WineDataset.php similarity index 91% rename from src/Phpml/Dataset/Demo/Wine.php rename to src/Phpml/Dataset/Demo/WineDataset.php index 3bc71a96..90ddfe49 100644 --- a/src/Phpml/Dataset/Demo/Wine.php +++ b/src/Phpml/Dataset/Demo/WineDataset.php @@ -12,7 +12,7 @@ * Samples total: 178 * Features per sample: 13. */ -class Wine extends CsvDataset +class WineDataset extends CsvDataset { public function __construct() { diff --git a/tests/Phpml/Dataset/Demo/GlassTest.php b/tests/Phpml/Dataset/Demo/GlassDatasetTest.php similarity index 74% rename from tests/Phpml/Dataset/Demo/GlassTest.php rename to tests/Phpml/Dataset/Demo/GlassDatasetTest.php index d4f13139..7ce3d376 100644 --- a/tests/Phpml/Dataset/Demo/GlassTest.php +++ b/tests/Phpml/Dataset/Demo/GlassDatasetTest.php @@ -4,13 +4,13 @@ namespace tests\Phpml\Dataset\Demo; -use Phpml\Dataset\Demo\Glass; +use Phpml\Dataset\Demo\GlassDataset; -class GlassTest extends \PHPUnit_Framework_TestCase +class GlassDatasetTest extends \PHPUnit_Framework_TestCase { public function testLoadingWineDataset() { - $glass = new Glass(); + $glass = new GlassDataset(); // whole dataset $this->assertEquals(214, count($glass->getSamples())); diff --git a/tests/Phpml/Dataset/Demo/IrisTest.php b/tests/Phpml/Dataset/Demo/IrisDatasetTest.php similarity index 74% rename from tests/Phpml/Dataset/Demo/IrisTest.php rename to tests/Phpml/Dataset/Demo/IrisDatasetTest.php index 354329e0..8e27db0e 100644 --- a/tests/Phpml/Dataset/Demo/IrisTest.php +++ b/tests/Phpml/Dataset/Demo/IrisDatasetTest.php @@ -4,13 +4,13 @@ namespace tests\Phpml\Dataset\Demo; -use Phpml\Dataset\Demo\Iris; +use Phpml\Dataset\Demo\IrisDataset; -class IrisTest extends \PHPUnit_Framework_TestCase +class IrisDatasetTest extends \PHPUnit_Framework_TestCase { public function testLoadingIrisDataset() { - $iris = new Iris(); + $iris = new IrisDataset(); // whole dataset $this->assertEquals(150, count($iris->getSamples())); diff --git a/tests/Phpml/Dataset/Demo/WineTest.php b/tests/Phpml/Dataset/Demo/WineDatasetTest.php similarity index 75% rename from tests/Phpml/Dataset/Demo/WineTest.php rename to tests/Phpml/Dataset/Demo/WineDatasetTest.php index 34c93fae..cf6c53d4 100644 --- a/tests/Phpml/Dataset/Demo/WineTest.php +++ b/tests/Phpml/Dataset/Demo/WineDatasetTest.php @@ -4,13 +4,13 @@ namespace tests\Phpml\Dataset\Demo; -use Phpml\Dataset\Demo\Wine; +use Phpml\Dataset\Demo\WineDataset; -class WineTest extends \PHPUnit_Framework_TestCase +class WineDatasetTest extends \PHPUnit_Framework_TestCase { public function testLoadingWineDataset() { - $wine = new Wine(); + $wine = new WineDataset(); // whole dataset $this->assertEquals(178, count($wine->getSamples())); diff --git a/tests/Phpml/Metric/AccuracyTest.php b/tests/Phpml/Metric/AccuracyTest.php index 6f28d946..436cb31a 100644 --- a/tests/Phpml/Metric/AccuracyTest.php +++ b/tests/Phpml/Metric/AccuracyTest.php @@ -6,7 +6,7 @@ use Phpml\Classification\SVC; use Phpml\CrossValidation\RandomSplit; -use Phpml\Dataset\Demo\Iris; +use Phpml\Dataset\Demo\IrisDataset; use Phpml\Metric\Accuracy; use Phpml\SupportVectorMachine\Kernel; @@ -41,7 +41,7 @@ public function testCalculateNotNormalizedScore() public function testAccuracyOnDemoDataset() { - $dataset = new RandomSplit(new Iris(), 0.5, 123); + $dataset = new RandomSplit(new IrisDataset(), 0.5, 123); $classifier = new SVC(Kernel::RBF); $classifier->train($dataset->getTrainSamples(), $dataset->getTrainLabels()); From 452626d9c44a19cc5bfd3a20128f0a8d611b00e3 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sat, 15 Oct 2016 20:50:16 +0200 Subject: [PATCH 114/328] Fix documentaion badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 31fdb603..bf33f905 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.0-8892BF.svg)](https://php.net/) [![Latest Stable Version](https://img.shields.io/packagist/v/php-ai/php-ml.svg)](https://packagist.org/packages/php-ai/php-ml) [![Build Status](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/build.png?b=develop)](https://scrutinizer-ci.com/g/php-ai/php-ml/build-status/develop) -[![Documentation Status](https://readthedocs.org/projects/php-ml/badge/?version=develop)](http://php-ml.readthedocs.org/en/develop/?badge=develop) +[![Documentation Status](https://readthedocs.org/projects/php-ml/badge/?version=master)](http://php-ml.readthedocs.org/) [![Total Downloads](https://poser.pugx.org/php-ai/php-ml/downloads.svg)](https://packagist.org/packages/php-ai/php-ml) [![License](https://poser.pugx.org/php-ai/php-ml/license.svg)](https://packagist.org/packages/php-ai/php-ml) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/quality-score.png?b=develop)](https://scrutinizer-ci.com/g/php-ai/php-ml/?branch=develop) From af2c2492418b5936dc8c4bfcc654956ff7e5f1ba Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sat, 15 Oct 2016 20:54:41 +0200 Subject: [PATCH 115/328] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b636d20..5c8f2ae5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ This changelog references the relevant changes done in PHP-ML library. * 0.2.1 (in plan/progress) * feature [Regression] - SSE, SSTo, SSR - sum of the squared + * feature [Association] - Apriori algorithm implementation + * bug [Metric] - division by zero * 0.2.0 (2016-08-14) * feature [NeuralNetwork] - MultilayerPerceptron and Backpropagation training From 8a0a9f09e22da55a9dafd9c1073a8d95e82ccbe7 Mon Sep 17 00:00:00 2001 From: Ken Seah Date: Fri, 4 Nov 2016 00:03:49 +1100 Subject: [PATCH 116/328] Update array-dataset.md Method has already changed name to getTargets() instead of getLabels() --- docs/machine-learning/datasets/array-dataset.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/machine-learning/datasets/array-dataset.md b/docs/machine-learning/datasets/array-dataset.md index 5081ed86..de495569 100644 --- a/docs/machine-learning/datasets/array-dataset.md +++ b/docs/machine-learning/datasets/array-dataset.md @@ -17,5 +17,5 @@ To get samples or labels you can use getters: ``` $dataset->getSamples(); -$dataset->getLabels(); +$dataset->getTargets(); ``` From bca2196b57702183c391e9afc2a9d462396b5d26 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 20 Nov 2016 22:49:26 +0100 Subject: [PATCH 117/328] Prevent Division by zero error in classification report --- src/Phpml/Metric/ClassificationReport.php | 4 ++++ tests/Phpml/Metric/ClassificationReportTest.php | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/Phpml/Metric/ClassificationReport.php b/src/Phpml/Metric/ClassificationReport.php index b1b135f4..f56fb2a5 100644 --- a/src/Phpml/Metric/ClassificationReport.php +++ b/src/Phpml/Metric/ClassificationReport.php @@ -113,6 +113,10 @@ private function computeAverage() { foreach (['precision', 'recall', 'f1score'] as $metric) { $values = array_filter($this->$metric); + if(0==count($values)) { + $this->average[$metric] = 0.0; + continue; + } $this->average[$metric] = array_sum($values) / count($values); } } diff --git a/tests/Phpml/Metric/ClassificationReportTest.php b/tests/Phpml/Metric/ClassificationReportTest.php index 6ccc21d1..515f97c5 100644 --- a/tests/Phpml/Metric/ClassificationReportTest.php +++ b/tests/Phpml/Metric/ClassificationReportTest.php @@ -67,4 +67,19 @@ public function testPreventDivideByZeroWhenTruePositiveAndFalseNegativeSumEquals $this->assertEquals([1 => 0.0, 2 => 1, 3 => 0], $report->getPrecision(), '', 0.01); } + + public function testPreventDividedByZeroWhenPredictedLabelsAllNotMatch() + { + $labels = [1,2,3,4,5]; + $predicted = [2,3,4,5,6]; + + $report = new ClassificationReport($labels, $predicted); + + $this->assertEquals([ + 'precision' => 0, + 'recall' => 0, + 'f1score' => 0 + ], $report->getAverage(), '', 0.01); + } + } From cbdc0495266ff2adc042bf58c3489f772d298484 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 20 Nov 2016 22:53:17 +0100 Subject: [PATCH 118/328] Update php-cs-fixer --- CHANGELOG.md | 4 +++- src/Phpml/Association/Apriori.php | 2 +- src/Phpml/Association/Associator.php | 2 +- src/Phpml/Classification/Classifier.php | 2 +- src/Phpml/Classification/KNearestNeighbors.php | 2 +- src/Phpml/Classification/NaiveBayes.php | 2 +- src/Phpml/Classification/SVC.php | 2 +- src/Phpml/Clustering/Clusterer.php | 2 +- src/Phpml/Clustering/DBSCAN.php | 2 +- src/Phpml/Clustering/KMeans.php | 2 +- src/Phpml/Clustering/KMeans/Cluster.php | 2 +- src/Phpml/Clustering/KMeans/Point.php | 4 ++-- src/Phpml/Clustering/KMeans/Space.php | 2 +- src/Phpml/CrossValidation/RandomSplit.php | 2 +- src/Phpml/CrossValidation/Split.php | 2 +- src/Phpml/CrossValidation/StratifiedRandomSplit.php | 2 +- src/Phpml/Dataset/ArrayDataset.php | 2 +- src/Phpml/Dataset/CsvDataset.php | 2 +- src/Phpml/Dataset/Dataset.php | 2 +- src/Phpml/Dataset/Demo/GlassDataset.php | 2 +- src/Phpml/Dataset/Demo/IrisDataset.php | 2 +- src/Phpml/Dataset/Demo/WineDataset.php | 2 +- src/Phpml/Dataset/FilesDataset.php | 2 +- src/Phpml/Estimator.php | 2 +- src/Phpml/Exception/DatasetException.php | 2 +- src/Phpml/Exception/InvalidArgumentException.php | 2 +- src/Phpml/Exception/MatrixException.php | 2 +- src/Phpml/Exception/NormalizerException.php | 2 +- src/Phpml/FeatureExtraction/StopWords.php | 2 +- src/Phpml/FeatureExtraction/StopWords/English.php | 2 +- src/Phpml/FeatureExtraction/StopWords/Polish.php | 2 +- src/Phpml/FeatureExtraction/TfIdfTransformer.php | 2 +- src/Phpml/FeatureExtraction/TokenCountVectorizer.php | 4 ++-- src/Phpml/Helper/Predictable.php | 2 +- src/Phpml/Helper/Trainable.php | 2 +- src/Phpml/Math/Distance.php | 2 +- src/Phpml/Math/Distance/Chebyshev.php | 2 +- src/Phpml/Math/Distance/Euclidean.php | 2 +- src/Phpml/Math/Distance/Manhattan.php | 2 +- src/Phpml/Math/Distance/Minkowski.php | 2 +- src/Phpml/Math/Kernel.php | 2 +- src/Phpml/Math/Kernel/RBF.php | 2 +- src/Phpml/Math/Matrix.php | 4 ++-- src/Phpml/Math/Product.php | 2 +- src/Phpml/Math/Set.php | 2 +- src/Phpml/Math/Statistic/Correlation.php | 4 ++-- src/Phpml/Math/Statistic/Mean.php | 2 +- src/Phpml/Math/Statistic/StandardDeviation.php | 4 ++-- src/Phpml/Metric/Accuracy.php | 2 +- src/Phpml/Metric/ClassificationReport.php | 4 ++-- src/Phpml/Metric/ConfusionMatrix.php | 2 +- src/Phpml/NeuralNetwork/ActivationFunction.php | 2 +- .../NeuralNetwork/ActivationFunction/BinaryStep.php | 2 +- .../NeuralNetwork/ActivationFunction/Gaussian.php | 2 +- .../ActivationFunction/HyperbolicTangent.php | 2 +- .../NeuralNetwork/ActivationFunction/Sigmoid.php | 2 +- src/Phpml/NeuralNetwork/Layer.php | 2 +- src/Phpml/NeuralNetwork/Network.php | 2 +- src/Phpml/NeuralNetwork/Network/LayeredNetwork.php | 2 +- .../NeuralNetwork/Network/MultilayerPerceptron.php | 4 ++-- src/Phpml/NeuralNetwork/Node.php | 2 +- src/Phpml/NeuralNetwork/Node/Bias.php | 2 +- src/Phpml/NeuralNetwork/Node/Input.php | 2 +- src/Phpml/NeuralNetwork/Node/Neuron.php | 2 +- src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php | 2 +- src/Phpml/NeuralNetwork/Training.php | 2 +- src/Phpml/NeuralNetwork/Training/Backpropagation.php | 4 ++-- .../NeuralNetwork/Training/Backpropagation/Sigma.php | 2 +- src/Phpml/Pipeline.php | 4 ++-- src/Phpml/Preprocessing/Imputer.php | 6 +++--- src/Phpml/Preprocessing/Imputer/Strategy.php | 2 +- .../Preprocessing/Imputer/Strategy/MeanStrategy.php | 4 ++-- .../Preprocessing/Imputer/Strategy/MedianStrategy.php | 4 ++-- .../Imputer/Strategy/MostFrequentStrategy.php | 4 ++-- src/Phpml/Preprocessing/Normalizer.php | 2 +- src/Phpml/Preprocessing/Preprocessor.php | 2 +- src/Phpml/Regression/LeastSquares.php | 2 +- src/Phpml/Regression/MLPRegressor.php | 2 +- src/Phpml/Regression/Regression.php | 2 +- src/Phpml/Regression/SVR.php | 2 +- src/Phpml/SupportVectorMachine/DataTransformer.php | 2 +- src/Phpml/SupportVectorMachine/Kernel.php | 2 +- .../SupportVectorMachine/SupportVectorMachine.php | 2 +- src/Phpml/SupportVectorMachine/Type.php | 2 +- src/Phpml/Tokenization/Tokenizer.php | 4 ++-- src/Phpml/Tokenization/WhitespaceTokenizer.php | 2 +- src/Phpml/Tokenization/WordTokenizer.php | 2 +- src/Phpml/Transformer.php | 2 +- tests/Phpml/Association/AprioriTest.php | 2 +- tests/Phpml/Classification/KNearestNeighborsTest.php | 2 +- tests/Phpml/Classification/NaiveBayesTest.php | 2 +- tests/Phpml/Classification/SVCTest.php | 2 +- tests/Phpml/Clustering/DBSCANTest.php | 2 +- tests/Phpml/Clustering/KMeansTest.php | 2 +- tests/Phpml/CrossValidation/RandomSplitTest.php | 2 +- .../CrossValidation/StratifiedRandomSplitTest.php | 2 +- tests/Phpml/Dataset/ArrayDatasetTest.php | 2 +- tests/Phpml/Dataset/CsvDatasetTest.php | 2 +- tests/Phpml/Dataset/Demo/GlassDatasetTest.php | 2 +- tests/Phpml/Dataset/Demo/IrisDatasetTest.php | 2 +- tests/Phpml/Dataset/Demo/WineDatasetTest.php | 2 +- tests/Phpml/Dataset/FilesDatasetTest.php | 2 +- tests/Phpml/FeatureExtraction/StopWordsTest.php | 2 +- .../Phpml/FeatureExtraction/TfIdfTransformerTest.php | 2 +- .../FeatureExtraction/TokenCountVectorizerTest.php | 2 +- tests/Phpml/Math/Distance/ChebyshevTest.php | 2 +- tests/Phpml/Math/Distance/EuclideanTest.php | 2 +- tests/Phpml/Math/Distance/ManhattanTest.php | 2 +- tests/Phpml/Math/Distance/MinkowskiTest.php | 2 +- tests/Phpml/Math/Kernel/RBFTest.php | 2 +- tests/Phpml/Math/MatrixTest.php | 2 +- tests/Phpml/Math/ProductTest.php | 2 +- tests/Phpml/Math/Statistic/CorrelationTest.php | 2 +- tests/Phpml/Math/Statistic/MeanTest.php | 2 +- tests/Phpml/Math/Statistic/StandardDeviationTest.php | 2 +- tests/Phpml/Metric/AccuracyTest.php | 2 +- tests/Phpml/Metric/ClassificationReportTest.php | 11 +++++------ tests/Phpml/Metric/ConfusionMatrixTest.php | 2 +- .../ActivationFunction/BinaryStepTest.php | 2 +- .../NeuralNetwork/ActivationFunction/GaussianTest.php | 2 +- .../ActivationFunction/HyperboliTangentTest.php | 2 +- .../NeuralNetwork/ActivationFunction/SigmoidTest.php | 2 +- tests/Phpml/NeuralNetwork/LayerTest.php | 2 +- .../NeuralNetwork/Network/LayeredNetworkTest.php | 2 +- .../Network/MultilayerPerceptronTest.php | 2 +- tests/Phpml/NeuralNetwork/Node/BiasTest.php | 2 +- tests/Phpml/NeuralNetwork/Node/InputTest.php | 2 +- tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php | 2 +- tests/Phpml/NeuralNetwork/Node/NeuronTest.php | 2 +- .../NeuralNetwork/Training/BackpropagationTest.php | 2 +- tests/Phpml/PipelineTest.php | 2 +- tests/Phpml/Preprocessing/ImputerTest.php | 2 +- tests/Phpml/Preprocessing/NormalizerTest.php | 2 +- tests/Phpml/Regression/LeastSquaresTest.php | 2 +- tests/Phpml/Regression/SVRTest.php | 2 +- .../SupportVectorMachine/DataTransformerTest.php | 2 +- .../SupportVectorMachine/SupportVectorMachineTest.php | 2 +- tests/Phpml/Tokenization/WhitespaceTokenizerTest.php | 2 +- tests/Phpml/Tokenization/WordTokenizerTest.php | 2 +- 139 files changed, 160 insertions(+), 159 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c8f2ae5..781d5fde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,10 @@ CHANGELOG This changelog references the relevant changes done in PHP-ML library. -* 0.2.1 (in plan/progress) +* 0.2.2 (in plan/progress) * feature [Regression] - SSE, SSTo, SSR - sum of the squared + +* 0.2.1 (2016-11-20) * feature [Association] - Apriori algorithm implementation * bug [Metric] - division by zero diff --git a/src/Phpml/Association/Apriori.php b/src/Phpml/Association/Apriori.php index bec1510f..48556917 100644 --- a/src/Phpml/Association/Apriori.php +++ b/src/Phpml/Association/Apriori.php @@ -1,6 +1,6 @@ dimension; ++$n) { $difference = $this->coordinates[$n] - $point->coordinates[$n]; - $distance += $difference * $difference; + $distance += $difference * $difference; } return $precise ? sqrt((float) $distance) : $distance; diff --git a/src/Phpml/Clustering/KMeans/Space.php b/src/Phpml/Clustering/KMeans/Space.php index 89a0d092..40b1f1d8 100644 --- a/src/Phpml/Clustering/KMeans/Space.php +++ b/src/Phpml/Clustering/KMeans/Space.php @@ -1,6 +1,6 @@ $metric); - if(0==count($values)) { + if (0 == count($values)) { $this->average[$metric] = 0.0; continue; } diff --git a/src/Phpml/Metric/ConfusionMatrix.php b/src/Phpml/Metric/ConfusionMatrix.php index 6aeaa873..664e355e 100644 --- a/src/Phpml/Metric/ConfusionMatrix.php +++ b/src/Phpml/Metric/ConfusionMatrix.php @@ -1,6 +1,6 @@ layers) - 1; - for ($i = 0;$i < $biasLayers;++$i) { + for ($i = 0; $i < $biasLayers; ++$i) { $this->layers[$i]->addNode(new Bias()); } } diff --git a/src/Phpml/NeuralNetwork/Node.php b/src/Phpml/NeuralNetwork/Node.php index 77e0c008..65d5cdcd 100644 --- a/src/Phpml/NeuralNetwork/Node.php +++ b/src/Phpml/NeuralNetwork/Node.php @@ -1,6 +1,6 @@ addTransformer($transformer); diff --git a/src/Phpml/Preprocessing/Imputer.php b/src/Phpml/Preprocessing/Imputer.php index 012bb79f..805d3f62 100644 --- a/src/Phpml/Preprocessing/Imputer.php +++ b/src/Phpml/Preprocessing/Imputer.php @@ -1,6 +1,6 @@ missingValue = $missingValue; $this->strategy = $strategy; @@ -78,7 +78,7 @@ private function preprocessSample(array &$sample) /** * @param int $column * @param array $currentSample - * + * * @return array */ private function getAxis(int $column, array $currentSample): array diff --git a/src/Phpml/Preprocessing/Imputer/Strategy.php b/src/Phpml/Preprocessing/Imputer/Strategy.php index 2cf11440..9125e06f 100644 --- a/src/Phpml/Preprocessing/Imputer/Strategy.php +++ b/src/Phpml/Preprocessing/Imputer/Strategy.php @@ -1,6 +1,6 @@ assertEquals([ 'precision' => 0, 'recall' => 0, - 'f1score' => 0 + 'f1score' => 0, ], $report->getAverage(), '', 0.01); } - } diff --git a/tests/Phpml/Metric/ConfusionMatrixTest.php b/tests/Phpml/Metric/ConfusionMatrixTest.php index d133ae2e..1b2782bc 100644 --- a/tests/Phpml/Metric/ConfusionMatrixTest.php +++ b/tests/Phpml/Metric/ConfusionMatrixTest.php @@ -1,6 +1,6 @@ Date: Sun, 20 Nov 2016 22:56:18 +0100 Subject: [PATCH 119/328] Increase iterations number in Backpropagation test (sometimes it fails) --- tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php b/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php index 30f633fe..dac5066b 100644 --- a/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php +++ b/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php @@ -18,7 +18,7 @@ public function testBackpropagationForXORLearning() [[1, 0], [0, 1], [1, 1], [0, 0]], [[1], [1], [0], [0]], $desiredError = 0.3, - 30000 + 40000 ); $this->assertEquals(0, $network->setInput([1, 1])->getOutput()[0], '', $desiredError); From c4f0d1e3b0ddac38a23ff73a3017e272a9baf6e1 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 6 Dec 2016 08:46:55 +0100 Subject: [PATCH 120/328] Make csv reader binary safe --- src/Phpml/Dataset/CsvDataset.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Phpml/Dataset/CsvDataset.php b/src/Phpml/Dataset/CsvDataset.php index 377f84ca..ab9a2b76 100644 --- a/src/Phpml/Dataset/CsvDataset.php +++ b/src/Phpml/Dataset/CsvDataset.php @@ -21,7 +21,7 @@ public function __construct(string $filepath, int $features, bool $headingRow = throw DatasetException::missingFile(basename($filepath)); } - if (false === $handle = fopen($filepath, 'r')) { + if (false === $handle = fopen($filepath, 'rb')) { throw DatasetException::cantOpenFile(basename($filepath)); } From a61704501d0b91610aa356f94910f2626e7abb1c Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 6 Dec 2016 08:48:45 +0100 Subject: [PATCH 121/328] Fix type compatibility for Minkowski distance --- src/Phpml/Math/Distance/Minkowski.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Phpml/Math/Distance/Minkowski.php b/src/Phpml/Math/Distance/Minkowski.php index cb13a7cd..6ed06603 100644 --- a/src/Phpml/Math/Distance/Minkowski.php +++ b/src/Phpml/Math/Distance/Minkowski.php @@ -43,6 +43,6 @@ public function distance(array $a, array $b): float $distance += pow(abs($a[$i] - $b[$i]), $this->lambda); } - return pow($distance, 1 / $this->lambda); + return (float)pow($distance, 1 / $this->lambda); } } From d00b7e56680e00850ee470e833cadc9b68d47418 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 6 Dec 2016 08:50:18 +0100 Subject: [PATCH 122/328] Secure uniqid usage --- src/Phpml/SupportVectorMachine/SupportVectorMachine.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php index 2bef22f0..cf5ce3b5 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -132,7 +132,7 @@ public function train(array $samples, array $labels) { $this->labels = $labels; $trainingSet = DataTransformer::trainingSet($samples, $labels, in_array($this->type, [Type::EPSILON_SVR, Type::NU_SVR])); - file_put_contents($trainingSetFileName = $this->varPath.uniqid(), $trainingSet); + file_put_contents($trainingSetFileName = $this->varPath.uniqid('phpml', true), $trainingSet); $modelFileName = $trainingSetFileName.'-model'; $command = $this->buildTrainCommand($trainingSetFileName, $modelFileName); @@ -161,7 +161,7 @@ public function getModel() public function predict(array $samples) { $testSet = DataTransformer::testSet($samples); - file_put_contents($testSetFileName = $this->varPath.uniqid(), $testSet); + file_put_contents($testSetFileName = $this->varPath.uniqid('phpml', true), $testSet); file_put_contents($modelFileName = $testSetFileName.'-model', $this->model); $outputFileName = $testSetFileName.'-output'; From 9764890ccb657fda683d1558691606967b552253 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 6 Dec 2016 08:52:33 +0100 Subject: [PATCH 123/328] Change floatvar to float casting (up to 6 times faster) --- src/Phpml/FeatureExtraction/TfIdfTransformer.php | 2 +- src/Phpml/Preprocessing/Normalizer.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Phpml/FeatureExtraction/TfIdfTransformer.php b/src/Phpml/FeatureExtraction/TfIdfTransformer.php index 23605606..da1f25ff 100644 --- a/src/Phpml/FeatureExtraction/TfIdfTransformer.php +++ b/src/Phpml/FeatureExtraction/TfIdfTransformer.php @@ -32,7 +32,7 @@ public function fit(array $samples) $count = count($samples); foreach ($this->idf as &$value) { - $value = log(floatval($count / $value), 10.0); + $value = log((float)($count / $value), 10.0); } } diff --git a/src/Phpml/Preprocessing/Normalizer.php b/src/Phpml/Preprocessing/Normalizer.php index 591ca72b..22ba5554 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -78,7 +78,7 @@ private function normalizeL2(array &$sample) foreach ($sample as $feature) { $norm2 += $feature * $feature; } - $norm2 = sqrt(floatval($norm2)); + $norm2 = sqrt((float)$norm2); if (0 == $norm2) { $sample = array_fill(0, count($sample), 1); From 6d111169946d15406f7f393ba4ff8bcb68ba7e04 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 6 Dec 2016 08:55:52 +0100 Subject: [PATCH 124/328] Fix default prameters values --- src/Phpml/FeatureExtraction/TokenCountVectorizer.php | 2 +- src/Phpml/Math/Distance/Minkowski.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php index 4371a7d5..993af858 100644 --- a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php +++ b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php @@ -39,7 +39,7 @@ class TokenCountVectorizer implements Transformer * @param StopWords $stopWords * @param float $minDF */ - public function __construct(Tokenizer $tokenizer, StopWords $stopWords = null, float $minDF = 0) + public function __construct(Tokenizer $tokenizer, StopWords $stopWords = null, float $minDF = 0.0) { $this->tokenizer = $tokenizer; $this->stopWords = $stopWords; diff --git a/src/Phpml/Math/Distance/Minkowski.php b/src/Phpml/Math/Distance/Minkowski.php index 6ed06603..07881936 100644 --- a/src/Phpml/Math/Distance/Minkowski.php +++ b/src/Phpml/Math/Distance/Minkowski.php @@ -17,7 +17,7 @@ class Minkowski implements Distance /** * @param float $lambda */ - public function __construct(float $lambda = 3) + public function __construct(float $lambda = 3.0) { $this->lambda = $lambda; } From 38a26d185f948322971c3600dd1d84356f3f6b9e Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 6 Dec 2016 08:59:34 +0100 Subject: [PATCH 125/328] Secure index access and type safe comparision in statistic median --- src/Phpml/Math/Statistic/Mean.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Phpml/Math/Statistic/Mean.php b/src/Phpml/Math/Statistic/Mean.php index 07c43fe2..581a1225 100644 --- a/src/Phpml/Math/Statistic/Mean.php +++ b/src/Phpml/Math/Statistic/Mean.php @@ -34,11 +34,11 @@ public static function median(array $numbers) self::checkArrayLength($numbers); $count = count($numbers); - $middleIndex = floor($count / 2); + $middleIndex = (int)floor($count / 2); sort($numbers, SORT_NUMERIC); $median = $numbers[$middleIndex]; - if (0 == $count % 2) { + if (0 === $count % 2) { $median = ($median + $numbers[$middleIndex - 1]) / 2; } From 8aad8afc37759df0ab82d6cdcb58f1bffec3560d Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 8 Dec 2016 00:45:42 +0100 Subject: [PATCH 126/328] Add null coalesce operator in token count vectoriezer --- src/Phpml/FeatureExtraction/TokenCountVectorizer.php | 2 +- src/Phpml/Metric/ClassificationReport.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php index 993af858..226cb55b 100644 --- a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php +++ b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php @@ -132,7 +132,7 @@ private function getTokenIndex(string $token) return false; } - return isset($this->vocabulary[$token]) ? $this->vocabulary[$token] : false; + return $this->vocabulary[$token] ?? false; } /** diff --git a/src/Phpml/Metric/ClassificationReport.php b/src/Phpml/Metric/ClassificationReport.php index 3499590d..c7cc1478 100644 --- a/src/Phpml/Metric/ClassificationReport.php +++ b/src/Phpml/Metric/ClassificationReport.php @@ -163,7 +163,7 @@ private function computeF1Score(float $precision, float $recall): float return 0.0; } - return 2.0 * (($precision * $recall) / ($divider)); + return 2.0 * (($precision * $recall) / $divider); } /** From 325ee1b5b6f8b06a3b6686719732f7bdd1e4c06c Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Mon, 12 Dec 2016 18:10:00 +0100 Subject: [PATCH 127/328] Add new cs fixer cache to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8a409f4b..73f321f5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ humbuglog.* /bin/phpunit .coverage +.php_cs.cache \ No newline at end of file From da56ce4b3a6ea1c40d0251b93b42a46cc6920691 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Mon, 12 Dec 2016 18:10:58 +0100 Subject: [PATCH 128/328] Change php-cs-fixer runner --- .gitignore | 2 +- tools/php-cs-fixer.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 73f321f5..79b5610c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ humbuglog.* /bin/phpunit .coverage -.php_cs.cache \ No newline at end of file +.php_cs.cache diff --git a/tools/php-cs-fixer.sh b/tools/php-cs-fixer.sh index dbf66e48..6eb37cbd 100755 --- a/tools/php-cs-fixer.sh +++ b/tools/php-cs-fixer.sh @@ -1,6 +1,6 @@ #!/bin/bash echo "Fixing src/ folder" -php-cs-fixer fix src/ --level=symfony +php-cs-fixer fix src/ echo "Fixing tests/ folder" -php-cs-fixer fix tests/ --level=symfony +php-cs-fixer fix tests/ From df28656d0d9d47bfb4b328f5b7ab506cb9740db0 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Mon, 12 Dec 2016 18:11:57 +0100 Subject: [PATCH 129/328] Fixes after new php-cs-fixer v2.0 --- src/Phpml/Regression/SVR.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Phpml/Regression/SVR.php b/src/Phpml/Regression/SVR.php index 78c57cb8..e32eeb00 100644 --- a/src/Phpml/Regression/SVR.php +++ b/src/Phpml/Regression/SVR.php @@ -23,7 +23,7 @@ class SVR extends SupportVectorMachine implements Regression */ public function __construct( int $kernel = Kernel::RBF, int $degree = 3, float $epsilon = 0.1, float $cost = 1.0, - float $gamma = null, float $coef0 = 0.0, float $tolerance = 0.001, + float $gamma = null, float $coef0 = 0.0, float $tolerance = 0.001, int $cacheSize = 100, bool $shrinking = true ) { parent::__construct(Type::EPSILON_SVR, $kernel, $cost, 0.5, $degree, $gamma, $coef0, $epsilon, $tolerance, $cacheSize, $shrinking, false); From a4f65bd13fe6889cd73b5d06ad7ced1bc86144ed Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Mon, 12 Dec 2016 18:34:20 +0100 Subject: [PATCH 130/328] Short syntax for applied operations --- src/Phpml/FeatureExtraction/TfIdfTransformer.php | 4 +--- src/Phpml/FeatureExtraction/TokenCountVectorizer.php | 2 -- src/Phpml/Math/Statistic/Correlation.php | 6 +++--- src/Phpml/Metric/Accuracy.php | 2 +- src/Phpml/Preprocessing/Normalizer.php | 4 ++-- 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/Phpml/FeatureExtraction/TfIdfTransformer.php b/src/Phpml/FeatureExtraction/TfIdfTransformer.php index da1f25ff..93357752 100644 --- a/src/Phpml/FeatureExtraction/TfIdfTransformer.php +++ b/src/Phpml/FeatureExtraction/TfIdfTransformer.php @@ -43,15 +43,13 @@ public function transform(array &$samples) { foreach ($samples as &$sample) { foreach ($sample as $index => &$feature) { - $feature = $feature * $this->idf[$index]; + $feature *= $this->idf[$index]; } } } /** * @param array $samples - * - * @return array */ private function countTokensFrequency(array $samples) { diff --git a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php index 226cb55b..f5fab21c 100644 --- a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php +++ b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php @@ -173,8 +173,6 @@ private function updateFrequency(string $token) /** * @param array $samples - * - * @return array */ private function checkDocumentFrequency(array &$samples) { diff --git a/src/Phpml/Math/Statistic/Correlation.php b/src/Phpml/Math/Statistic/Correlation.php index cc1767c4..0f60223f 100644 --- a/src/Phpml/Math/Statistic/Correlation.php +++ b/src/Phpml/Math/Statistic/Correlation.php @@ -33,9 +33,9 @@ public static function pearson(array $x, array $y) for ($i = 0; $i < $count; ++$i) { $a = $x[$i] - $meanX; $b = $y[$i] - $meanY; - $axb = $axb + ($a * $b); - $a2 = $a2 + pow($a, 2); - $b2 = $b2 + pow($b, 2); + $axb += ($a * $b); + $a2 += pow($a, 2); + $b2 += pow($b, 2); } $corr = $axb / sqrt((float) ($a2 * $b2)); diff --git a/src/Phpml/Metric/Accuracy.php b/src/Phpml/Metric/Accuracy.php index 0e9dc032..3dfcb34c 100644 --- a/src/Phpml/Metric/Accuracy.php +++ b/src/Phpml/Metric/Accuracy.php @@ -31,7 +31,7 @@ public static function score(array $actualLabels, array $predictedLabels, bool $ } if ($normalize) { - $score = $score / count($actualLabels); + $score /= count($actualLabels); } return $score; diff --git a/src/Phpml/Preprocessing/Normalizer.php b/src/Phpml/Preprocessing/Normalizer.php index 22ba5554..5cff6e84 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -64,7 +64,7 @@ private function normalizeL1(array &$sample) $sample = array_fill(0, $count, 1.0 / $count); } else { foreach ($sample as &$feature) { - $feature = $feature / $norm1; + $feature /= $norm1; } } } @@ -84,7 +84,7 @@ private function normalizeL2(array &$sample) $sample = array_fill(0, count($sample), 1); } else { foreach ($sample as &$feature) { - $feature = $feature / $norm2; + $feature /= $norm2; } } } From fd850333394be51e465cb663593372a2bac9d71a Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Mon, 12 Dec 2016 18:45:14 +0100 Subject: [PATCH 131/328] Use __DIR__ instead of dirname --- src/Phpml/Dataset/Demo/GlassDataset.php | 2 +- src/Phpml/Dataset/Demo/IrisDataset.php | 2 +- src/Phpml/Dataset/Demo/WineDataset.php | 2 +- src/Phpml/SupportVectorMachine/SupportVectorMachine.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Phpml/Dataset/Demo/GlassDataset.php b/src/Phpml/Dataset/Demo/GlassDataset.php index 6f8eda8d..8f7d56da 100644 --- a/src/Phpml/Dataset/Demo/GlassDataset.php +++ b/src/Phpml/Dataset/Demo/GlassDataset.php @@ -22,7 +22,7 @@ class GlassDataset extends CsvDataset { public function __construct() { - $filepath = dirname(__FILE__).'/../../../../data/glass.csv'; + $filepath = __DIR__.'/../../../../data/glass.csv'; parent::__construct($filepath, 9, true); } } diff --git a/src/Phpml/Dataset/Demo/IrisDataset.php b/src/Phpml/Dataset/Demo/IrisDataset.php index 6a3a4228..0bc96d86 100644 --- a/src/Phpml/Dataset/Demo/IrisDataset.php +++ b/src/Phpml/Dataset/Demo/IrisDataset.php @@ -16,7 +16,7 @@ class IrisDataset extends CsvDataset { public function __construct() { - $filepath = dirname(__FILE__).'/../../../../data/iris.csv'; + $filepath = __DIR__.'/../../../../data/iris.csv'; parent::__construct($filepath, 4, true); } } diff --git a/src/Phpml/Dataset/Demo/WineDataset.php b/src/Phpml/Dataset/Demo/WineDataset.php index f522e016..c65b08cf 100644 --- a/src/Phpml/Dataset/Demo/WineDataset.php +++ b/src/Phpml/Dataset/Demo/WineDataset.php @@ -16,7 +16,7 @@ class WineDataset extends CsvDataset { public function __construct() { - $filepath = dirname(__FILE__).'/../../../../data/wine.csv'; + $filepath = __DIR__.'/../../../../data/wine.csv'; parent::__construct($filepath, 13, true); } } diff --git a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php index cf5ce3b5..55a53057 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -118,7 +118,7 @@ public function __construct( $this->shrinking = $shrinking; $this->probabilityEstimates = $probabilityEstimates; - $rootPath = realpath(implode(DIRECTORY_SEPARATOR, [dirname(__FILE__), '..', '..', '..'])).DIRECTORY_SEPARATOR; + $rootPath = realpath(implode(DIRECTORY_SEPARATOR, [__DIR__, '..', '..', '..'])).DIRECTORY_SEPARATOR; $this->binPath = $rootPath.'bin'.DIRECTORY_SEPARATOR.'libsvm'.DIRECTORY_SEPARATOR; $this->varPath = $rootPath.'var'.DIRECTORY_SEPARATOR; From d32197100e2cbec9c4806616977eac074a350670 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Mon, 12 Dec 2016 18:50:27 +0100 Subject: [PATCH 132/328] Fix docblock --- src/Phpml/Exception/DatasetException.php | 15 ++++++++++++--- src/Phpml/Exception/InvalidArgumentException.php | 2 ++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Phpml/Exception/DatasetException.php b/src/Phpml/Exception/DatasetException.php index 4d333108..85f911f5 100644 --- a/src/Phpml/Exception/DatasetException.php +++ b/src/Phpml/Exception/DatasetException.php @@ -7,22 +7,31 @@ class DatasetException extends \Exception { /** + * @param string $filepath + * * @return DatasetException */ - public static function missingFile($filepath) + public static function missingFile(string $filepath) { return new self(sprintf('Dataset file "%s" missing.', $filepath)); } /** + * @param string $path + * * @return DatasetException */ - public static function missingFolder($path) + public static function missingFolder(string $path) { return new self(sprintf('Dataset root folder "%s" missing.', $path)); } - public static function cantOpenFile($filepath) + /** + * @param string $filepath + * + * @return DatasetException + */ + public static function cantOpenFile(string $filepath) { return new self(sprintf('Dataset file "%s" can\'t be open.', $filepath)); } diff --git a/src/Phpml/Exception/InvalidArgumentException.php b/src/Phpml/Exception/InvalidArgumentException.php index b02da53f..42063f1d 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -67,6 +67,8 @@ public static function invalidClustersNumber() } /** + * @param string $language + * * @return InvalidArgumentException */ public static function invalidStopWordsLanguage(string $language) From 2363bbaa756848b30b48db620b5decb8ad3a439d Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Mon, 12 Dec 2016 19:02:09 +0100 Subject: [PATCH 133/328] Add type hint and exceptions annotation --- src/Phpml/Clustering/KMeans/Cluster.php | 2 ++ src/Phpml/Clustering/KMeans/Point.php | 4 ++-- src/Phpml/Clustering/KMeans/Space.php | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Phpml/Clustering/KMeans/Cluster.php b/src/Phpml/Clustering/KMeans/Cluster.php index c160b6c2..28fa8aff 100644 --- a/src/Phpml/Clustering/KMeans/Cluster.php +++ b/src/Phpml/Clustering/KMeans/Cluster.php @@ -60,6 +60,8 @@ public function toArray() * @param Point $point * * @return Point + * + * @throws \LogicException */ public function attach(Point $point) { diff --git a/src/Phpml/Clustering/KMeans/Point.php b/src/Phpml/Clustering/KMeans/Point.php index a8d72e5b..ce1c44ee 100644 --- a/src/Phpml/Clustering/KMeans/Point.php +++ b/src/Phpml/Clustering/KMeans/Point.php @@ -53,11 +53,11 @@ public function getDistanceWith(self $point, $precise = true) } /** - * @param $points + * @param array $points * * @return mixed */ - public function getClosest($points) + public function getClosest(array $points) { foreach ($points as $point) { $distance = $this->getDistanceWith($point, false); diff --git a/src/Phpml/Clustering/KMeans/Space.php b/src/Phpml/Clustering/KMeans/Space.php index 40b1f1d8..49ded31c 100644 --- a/src/Phpml/Clustering/KMeans/Space.php +++ b/src/Phpml/Clustering/KMeans/Space.php @@ -65,7 +65,7 @@ public function addPoint(array $coordinates, $data = null) } /** - * @param object $point + * @param Point $point * @param null $data */ public function attach($point, $data = null) From 4dc82710c8904e6f7d0db8dd98bbab694529f1c5 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Mon, 12 Dec 2016 19:09:45 +0100 Subject: [PATCH 134/328] Replace rand with newer versions random_int --- src/Phpml/Clustering/KMeans/Space.php | 4 ++-- src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Phpml/Clustering/KMeans/Space.php b/src/Phpml/Clustering/KMeans/Space.php index 49ded31c..c51cc05c 100644 --- a/src/Phpml/Clustering/KMeans/Space.php +++ b/src/Phpml/Clustering/KMeans/Space.php @@ -118,7 +118,7 @@ public function getRandomPoint(Point $min, Point $max) $point = $this->newPoint(array_fill(0, $this->dimension, null)); for ($n = 0; $n < $this->dimension; ++$n) { - $point[$n] = rand($min[$n], $max[$n]); + $point[$n] = random_int($min[$n], $max[$n]); } return $point; @@ -243,7 +243,7 @@ protected function initializeKMPPClusters(int $clustersNumber) $sum += $distances[$point] = $distance; } - $sum = rand(0, (int) $sum); + $sum = random_int(0, (int) $sum); foreach ($this as $point) { if (($sum -= $distances[$point]) > 0) { continue; diff --git a/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php b/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php index 0fb54b10..b9c036fb 100644 --- a/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php +++ b/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php @@ -33,7 +33,7 @@ public function __construct(Node $node, float $weight = null) */ protected function generateRandomWeight(): float { - return 1 / rand(5, 25) * (rand(0, 1) ? -1 : 1); + return 1 / random_int(5, 25) * (random_int(0, 1) ? -1 : 1); } /** From 902d2ddcd9e422d78f7b53d3f8ec4c38d5bd8830 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Mon, 12 Dec 2016 19:12:26 +0100 Subject: [PATCH 135/328] Add php 7.1 to travis --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index ea7c1c84..94f56edf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,9 @@ matrix: - os: linux php: '7.0' + - os: linux + php: '7.1' + - os: osx osx_image: xcode7.3 language: generic From b6fe290c65724827f4fc0d67e841b49fa92ac182 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Mon, 12 Dec 2016 19:28:26 +0100 Subject: [PATCH 136/328] Fix for php7.1 accuracy test score --- tests/Phpml/Metric/AccuracyTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Phpml/Metric/AccuracyTest.php b/tests/Phpml/Metric/AccuracyTest.php index 13a255aa..ef54002e 100644 --- a/tests/Phpml/Metric/AccuracyTest.php +++ b/tests/Phpml/Metric/AccuracyTest.php @@ -50,6 +50,8 @@ public function testAccuracyOnDemoDataset() $accuracy = Accuracy::score($dataset->getTestLabels(), $predicted); - $this->assertEquals(0.959, $accuracy, '', 0.01); + $expected = PHP_VERSION_ID >= 70100 ? 1 : 0.959; + + $this->assertEquals($expected, $accuracy, '', 0.01); } } From a78ebc159a1595c68d07bbfe1ea1e56b4f667e21 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Mon, 12 Dec 2016 19:31:30 +0100 Subject: [PATCH 137/328] Use assertCount in tests --- tests/Phpml/Clustering/KMeansTest.php | 8 ++++---- tests/Phpml/CrossValidation/RandomSplitTest.php | 8 ++++---- tests/Phpml/Dataset/CsvDatasetTest.php | 8 ++++---- tests/Phpml/Dataset/Demo/GlassDatasetTest.php | 6 +++--- tests/Phpml/Dataset/Demo/IrisDatasetTest.php | 6 +++--- tests/Phpml/Dataset/Demo/WineDatasetTest.php | 6 +++--- tests/Phpml/Dataset/FilesDatasetTest.php | 4 ++-- tests/Phpml/Math/SetTest.php | 4 ++-- 8 files changed, 25 insertions(+), 25 deletions(-) diff --git a/tests/Phpml/Clustering/KMeansTest.php b/tests/Phpml/Clustering/KMeansTest.php index aa4d9c39..bd954bed 100644 --- a/tests/Phpml/Clustering/KMeansTest.php +++ b/tests/Phpml/Clustering/KMeansTest.php @@ -15,14 +15,14 @@ public function testKMeansSamplesClustering() $kmeans = new KMeans(2); $clusters = $kmeans->cluster($samples); - $this->assertEquals(2, count($clusters)); + $this->assertCount(2, $clusters); foreach ($samples as $index => $sample) { if (in_array($sample, $clusters[0]) || in_array($sample, $clusters[1])) { unset($samples[$index]); } } - $this->assertEquals(0, count($samples)); + $this->assertCount(0, $samples); } public function testKMeansInitializationMethods() @@ -42,11 +42,11 @@ public function testKMeansInitializationMethods() $kmeans = new KMeans(4, KMeans::INIT_KMEANS_PLUS_PLUS); $clusters = $kmeans->cluster($samples); - $this->assertEquals(4, count($clusters)); + $this->assertCount(4, $clusters); $kmeans = new KMeans(4, KMeans::INIT_RANDOM); $clusters = $kmeans->cluster($samples); - $this->assertEquals(4, count($clusters)); + $this->assertCount(4, $clusters); } /** diff --git a/tests/Phpml/CrossValidation/RandomSplitTest.php b/tests/Phpml/CrossValidation/RandomSplitTest.php index 74bfcd7a..d6f46816 100644 --- a/tests/Phpml/CrossValidation/RandomSplitTest.php +++ b/tests/Phpml/CrossValidation/RandomSplitTest.php @@ -34,13 +34,13 @@ public function testDatasetRandomSplitWithoutSeed() $randomSplit = new RandomSplit($dataset, 0.5); - $this->assertEquals(2, count($randomSplit->getTestSamples())); - $this->assertEquals(2, count($randomSplit->getTrainSamples())); + $this->assertCount(2, $randomSplit->getTestSamples()); + $this->assertCount(2, $randomSplit->getTrainSamples()); $randomSplit2 = new RandomSplit($dataset, 0.25); - $this->assertEquals(1, count($randomSplit2->getTestSamples())); - $this->assertEquals(3, count($randomSplit2->getTrainSamples())); + $this->assertCount(1, $randomSplit2->getTestSamples()); + $this->assertCount(3, $randomSplit2->getTrainSamples()); } public function testDatasetRandomSplitWithSameSeed() diff --git a/tests/Phpml/Dataset/CsvDatasetTest.php b/tests/Phpml/Dataset/CsvDatasetTest.php index 5a3546ca..44e745af 100644 --- a/tests/Phpml/Dataset/CsvDatasetTest.php +++ b/tests/Phpml/Dataset/CsvDatasetTest.php @@ -22,8 +22,8 @@ public function testSampleCsvDatasetWithHeaderRow() $dataset = new CsvDataset($filePath, 2, true); - $this->assertEquals(10, count($dataset->getSamples())); - $this->assertEquals(10, count($dataset->getTargets())); + $this->assertCount(10, $dataset->getSamples()); + $this->assertCount(10, $dataset->getTargets()); } public function testSampleCsvDatasetWithoutHeaderRow() @@ -32,7 +32,7 @@ public function testSampleCsvDatasetWithoutHeaderRow() $dataset = new CsvDataset($filePath, 2, false); - $this->assertEquals(11, count($dataset->getSamples())); - $this->assertEquals(11, count($dataset->getTargets())); + $this->assertCount(11, $dataset->getSamples()); + $this->assertCount(11, $dataset->getTargets()); } } diff --git a/tests/Phpml/Dataset/Demo/GlassDatasetTest.php b/tests/Phpml/Dataset/Demo/GlassDatasetTest.php index b646c722..9755b5de 100644 --- a/tests/Phpml/Dataset/Demo/GlassDatasetTest.php +++ b/tests/Phpml/Dataset/Demo/GlassDatasetTest.php @@ -13,10 +13,10 @@ public function testLoadingWineDataset() $glass = new GlassDataset(); // whole dataset - $this->assertEquals(214, count($glass->getSamples())); - $this->assertEquals(214, count($glass->getTargets())); + $this->assertCount(214, $glass->getSamples()); + $this->assertCount(214, $glass->getTargets()); // one sample features count - $this->assertEquals(9, count($glass->getSamples()[0])); + $this->assertCount(9, $glass->getSamples()[0]); } } diff --git a/tests/Phpml/Dataset/Demo/IrisDatasetTest.php b/tests/Phpml/Dataset/Demo/IrisDatasetTest.php index 5bc45591..3ee67d0f 100644 --- a/tests/Phpml/Dataset/Demo/IrisDatasetTest.php +++ b/tests/Phpml/Dataset/Demo/IrisDatasetTest.php @@ -13,10 +13,10 @@ public function testLoadingIrisDataset() $iris = new IrisDataset(); // whole dataset - $this->assertEquals(150, count($iris->getSamples())); - $this->assertEquals(150, count($iris->getTargets())); + $this->assertCount(150, $iris->getSamples()); + $this->assertCount(150, $iris->getTargets()); // one sample features count - $this->assertEquals(4, count($iris->getSamples()[0])); + $this->assertCount(4, $iris->getSamples()[0]); } } diff --git a/tests/Phpml/Dataset/Demo/WineDatasetTest.php b/tests/Phpml/Dataset/Demo/WineDatasetTest.php index ee79d5b8..dbac4e03 100644 --- a/tests/Phpml/Dataset/Demo/WineDatasetTest.php +++ b/tests/Phpml/Dataset/Demo/WineDatasetTest.php @@ -13,10 +13,10 @@ public function testLoadingWineDataset() $wine = new WineDataset(); // whole dataset - $this->assertEquals(178, count($wine->getSamples())); - $this->assertEquals(178, count($wine->getTargets())); + $this->assertCount(178, $wine->getSamples()); + $this->assertCount(178, $wine->getTargets()); // one sample features count - $this->assertEquals(13, count($wine->getSamples()[0])); + $this->assertCount(13, $wine->getSamples()[0]); } } diff --git a/tests/Phpml/Dataset/FilesDatasetTest.php b/tests/Phpml/Dataset/FilesDatasetTest.php index f6e95553..49f0e206 100644 --- a/tests/Phpml/Dataset/FilesDatasetTest.php +++ b/tests/Phpml/Dataset/FilesDatasetTest.php @@ -22,8 +22,8 @@ public function testLoadFilesDatasetWithBBCData() $dataset = new FilesDataset($rootPath); - $this->assertEquals(50, count($dataset->getSamples())); - $this->assertEquals(50, count($dataset->getTargets())); + $this->assertCount(50, $dataset->getSamples()); + $this->assertCount(50, $dataset->getTargets()); $targets = ['business', 'entertainment', 'politics', 'sport', 'tech']; $this->assertEquals($targets, array_values(array_unique($dataset->getTargets()))); diff --git a/tests/Phpml/Math/SetTest.php b/tests/Phpml/Math/SetTest.php index cdf4460e..5426764f 100644 --- a/tests/Phpml/Math/SetTest.php +++ b/tests/Phpml/Math/SetTest.php @@ -39,7 +39,7 @@ public function testPower() $this->assertInternalType('array', $power); $this->assertEquals([new Set(), new Set(['A']), new Set(['B']), new Set(['A', 'B'])], $power); - $this->assertEquals(4, count($power)); + $this->assertCount(4, $power); } public function testCartesian() @@ -48,7 +48,7 @@ public function testCartesian() $this->assertInternalType('array', $cartesian); $this->assertEquals([new Set(['A', 1]), new Set(['A', 2])], $cartesian); - $this->assertEquals(2, count($cartesian)); + $this->assertCount(2, $cartesian); } public function testContains() From aace5ff0226ab1c5342603876d0d0732be667fe0 Mon Sep 17 00:00:00 2001 From: Robert Boloc Date: Thu, 5 Jan 2017 20:06:10 +0000 Subject: [PATCH 138/328] Fix documentation links --- docs/machine-learning/classification/k-nearest-neighbors.md | 2 +- docs/machine-learning/clustering/dbscan.md | 2 +- docs/machine-learning/datasets/csv-dataset.md | 2 +- docs/machine-learning/datasets/files-dataset.md | 2 +- mkdocs.yml | 3 +++ 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/machine-learning/classification/k-nearest-neighbors.md b/docs/machine-learning/classification/k-nearest-neighbors.md index 2de597c6..6e70c611 100644 --- a/docs/machine-learning/classification/k-nearest-neighbors.md +++ b/docs/machine-learning/classification/k-nearest-neighbors.md @@ -5,7 +5,7 @@ Classifier implementing the k-nearest neighbors algorithm. ## Constructor Parameters * $k - number of nearest neighbors to scan (default: 3) -* $distanceMetric - Distance object, default Euclidean (see [distance documentation](math/distance/)) +* $distanceMetric - Distance object, default Euclidean (see [distance documentation](../../math/distance.md)) ``` $classifier = new KNearestNeighbors($k=4); diff --git a/docs/machine-learning/clustering/dbscan.md b/docs/machine-learning/clustering/dbscan.md index 45dd6311..c82a195e 100644 --- a/docs/machine-learning/clustering/dbscan.md +++ b/docs/machine-learning/clustering/dbscan.md @@ -7,7 +7,7 @@ It is a density-based clustering algorithm: given a set of points in some space, * $epsilon - epsilon, maximum distance between two samples for them to be considered as in the same neighborhood * $minSamples - number of samples in a neighborhood for a point to be considered as a core point (this includes the point itself) -* $distanceMetric - Distance object, default Euclidean (see [distance documentation](math/distance/)) +* $distanceMetric - Distance object, default Euclidean (see [distance documentation](../../math/distance.md)) ``` $dbscan = new DBSCAN($epsilon = 2, $minSamples = 3); diff --git a/docs/machine-learning/datasets/csv-dataset.md b/docs/machine-learning/datasets/csv-dataset.md index 0ea6319b..d2efaaaf 100644 --- a/docs/machine-learning/datasets/csv-dataset.md +++ b/docs/machine-learning/datasets/csv-dataset.md @@ -12,4 +12,4 @@ Helper class that loads data from CSV file. It extends the `ArrayDataset`. $dataset = new CsvDataset('dataset.csv', 2, true); ``` -See [ArrayDataset](machine-learning/datasets/array-dataset/) for more information. +See [ArrayDataset](array-dataset.md) for more information. diff --git a/docs/machine-learning/datasets/files-dataset.md b/docs/machine-learning/datasets/files-dataset.md index 969610cc..f050cfda 100644 --- a/docs/machine-learning/datasets/files-dataset.md +++ b/docs/machine-learning/datasets/files-dataset.md @@ -12,7 +12,7 @@ use Phpml\Dataset\FilesDataset; $dataset = new FilesDataset('path/to/data'); ``` -See [ArrayDataset](machine-learning/datasets/array-dataset/) for more information. +See [ArrayDataset](array-dataset.md) for more information. ### Example diff --git a/mkdocs.yml b/mkdocs.yml index 4fa6c21e..b404e283 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,6 +2,8 @@ site_name: PHP-ML - Machine Learning library for PHP pages: - Home: index.md - Machine Learning: + - Association rule learning: + - Apriori: machine-learning/association/apriori.md - Classification: - SVC: machine-learning/classification/svc.md - KNearestNeighbors: machine-learning/classification/k-nearest-neighbors.md @@ -41,5 +43,6 @@ pages: - Math: - Distance: math/distance.md - Matrix: math/matrix.md + - Set: math/set.md - Statistic: math/statistic.md theme: readthedocs From e603d60841ba18383142940612269e336cab3da6 Mon Sep 17 00:00:00 2001 From: Mustafa Karabulut Date: Tue, 17 Jan 2017 17:21:58 +0200 Subject: [PATCH 139/328] Update NaiveBayes.php (#30) * Update NaiveBayes.php * Update NaiveBayes.php * Update NaiveBayes.php Update to fix "predictSample" function to enable it handle samples given as multi-dimensional arrays. * Update NaiveBayes.php * Update NaiveBayes.php --- src/Phpml/Classification/NaiveBayes.php | 145 ++++++++++++++++++++++-- 1 file changed, 133 insertions(+), 12 deletions(-) diff --git a/src/Phpml/Classification/NaiveBayes.php b/src/Phpml/Classification/NaiveBayes.php index 41441bec..17b0fbee 100644 --- a/src/Phpml/Classification/NaiveBayes.php +++ b/src/Phpml/Classification/NaiveBayes.php @@ -6,31 +6,152 @@ use Phpml\Helper\Predictable; use Phpml\Helper\Trainable; +use Phpml\Math\Statistic\Mean; +use Phpml\Math\Statistic\StandardDeviation; class NaiveBayes implements Classifier { use Trainable, Predictable; + const CONTINUOS = 1; + const NOMINAL = 2; + const EPSILON = 1e-10; + private $std = array(); + private $mean= array(); + private $discreteProb = array(); + private $dataType = array(); + private $p = array(); + private $sampleCount = 0; + private $featureCount = 0; + private $labels = array(); + public function train(array $samples, array $targets) + { + $this->samples = $samples; + $this->targets = $targets; + $this->sampleCount = count($samples); + $this->featureCount = count($samples[0]); + // Get distinct targets + $this->labels = $targets; + array_unique($this->labels); + foreach ($this->labels as $label) { + $samples = $this->getSamplesByLabel($label); + $this->p[$label] = count($samples) / $this->sampleCount; + $this->calculateStatistics($label, $samples); + } + } + + /** + * Calculates vital statistics for each label & feature. Stores these + * values in private array in order to avoid repeated calculation + * @param string $label + * @param array $samples + */ + private function calculateStatistics($label, $samples) + { + $this->std[$label] = array_fill(0, $this->featureCount, 0); + $this->mean[$label]= array_fill(0, $this->featureCount, 0); + $this->dataType[$label] = array_fill(0, $this->featureCount, self::CONTINUOS); + $this->discreteProb[$label] = array_fill(0, $this->featureCount, self::CONTINUOS); + for ($i=0; $i<$this->featureCount; $i++) { + // Get the values of nth column in the samples array + // Mean::arithmetic is called twice, can be optimized + $values = array_column($samples, $i); + $numValues = count($values); + // if the values contain non-numeric data, + // then it should be treated as nominal/categorical/discrete column + if ($values !== array_filter($values, 'is_numeric')) { + $this->dataType[$label][$i] = self::NOMINAL; + $this->discreteProb[$label][$i] = array_count_values($values); + $db = &$this->discreteProb[$label][$i]; + $db = array_map(function ($el) use ($numValues) { + return $el / $numValues; + }, $db); + } else { + $this->mean[$label][$i] = Mean::arithmetic($values); + // Add epsilon in order to avoid zero stdev + $this->std[$label][$i] = 1e-10 + StandardDeviation::population($values, false); + } + } + } + + /** + * Calculates the probability P(label|sample_n) + * + * @param array $sample + * @param int $feature + * @param string $label + */ + private function sampleProbability($sample, $feature, $label) + { + $value = $sample[$feature]; + if ($this->dataType[$label][$feature] == self::NOMINAL) { + if (! isset($this->discreteProb[$label][$feature][$value]) || + $this->discreteProb[$label][$feature][$value] == 0) { + return self::EPSILON; + } + return $this->discreteProb[$label][$feature][$value]; + } + $std = $this->std[$label][$feature] ; + $mean= $this->mean[$label][$feature]; + // Calculate the probability density by use of normal/Gaussian distribution + // Ref: https://en.wikipedia.org/wiki/Normal_distribution + // + // In order to avoid numerical errors because of small or zero values, + // some libraries adopt taking log of calculations such as + // scikit-learn did. + // (See : https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/naive_bayes.py) + $pdf = -0.5 * log(2.0 * pi() * $std * $std); + $pdf -= 0.5 * pow($value - $mean, 2) / ($std * $std); + return $pdf; + } + + /** + * Return samples belonging to specific label + * @param string $label + * @return array + */ + private function getSamplesByLabel($label) + { + $samples = array(); + for ($i=0; $i<$this->sampleCount; $i++) { + if ($this->targets[$i] == $label) { + $samples[] = $this->samples[$i]; + } + } + return $samples; + } /** * @param array $sample - * * @return mixed */ protected function predictSample(array $sample) { - $predictions = []; - foreach ($this->targets as $index => $label) { - $predictions[$label] = 0; - foreach ($sample as $token => $count) { - if (array_key_exists($token, $this->samples[$index])) { - $predictions[$label] += $count * $this->samples[$index][$token]; + $isArray = is_array($sample[0]); + $samples = $sample; + if (!$isArray) { + $samples = array($sample); + } + $samplePredictions = array(); + foreach ($samples as $sample) { + // Use NaiveBayes assumption for each label using: + // P(label|features) = P(label) * P(feature0|label) * P(feature1|label) .... P(featureN|label) + // Then compare probability for each class to determine which label is most likely + $predictions = array(); + foreach ($this->labels as $label) { + $p = $this->p[$label]; + for ($i=0; $i<$this->featureCount; $i++) { + $Plf = $this->sampleProbability($sample, $i, $label); + $p += $Plf; } + $predictions[$label] = $p; } + arsort($predictions, SORT_NUMERIC); + reset($predictions); + $samplePredictions[] = key($predictions); } - - arsort($predictions, SORT_NUMERIC); - reset($predictions); - - return key($predictions); + if (! $isArray) { + return $samplePredictions[0]; + } + return $samplePredictions; } } From d19ddb8507ef9833349f07dcd3bd6d4b5d5643ca Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 17 Jan 2017 16:26:43 +0100 Subject: [PATCH 140/328] Apply cs fixes for NaiveBayes --- src/Phpml/Classification/NaiveBayes.php | 73 +++++++++++++++++++------ 1 file changed, 56 insertions(+), 17 deletions(-) diff --git a/src/Phpml/Classification/NaiveBayes.php b/src/Phpml/Classification/NaiveBayes.php index 17b0fbee..1f897944 100644 --- a/src/Phpml/Classification/NaiveBayes.php +++ b/src/Phpml/Classification/NaiveBayes.php @@ -12,24 +12,62 @@ class NaiveBayes implements Classifier { use Trainable, Predictable; + const CONTINUOS = 1; const NOMINAL = 2; const EPSILON = 1e-10; - private $std = array(); - private $mean= array(); - private $discreteProb = array(); - private $dataType = array(); - private $p = array(); + + /** + * @var array + */ + private $std = []; + + /** + * @var array + */ + private $mean= []; + + /** + * @var array + */ + private $discreteProb = []; + + /** + * @var array + */ + private $dataType = []; + + /** + * @var array + */ + private $p = []; + + /** + * @var int + */ private $sampleCount = 0; + + /** + * @var int + */ private $featureCount = 0; - private $labels = array(); + + /** + * @var array + */ + private $labels = []; + + /** + * @param array $samples + * @param array $targets + */ public function train(array $samples, array $targets) { $this->samples = $samples; $this->targets = $targets; $this->sampleCount = count($samples); $this->featureCount = count($samples[0]); - // Get distinct targets + $this->labels = $targets; array_unique($this->labels); foreach ($this->labels as $label) { @@ -67,7 +105,7 @@ private function calculateStatistics($label, $samples) }, $db); } else { $this->mean[$label][$i] = Mean::arithmetic($values); - // Add epsilon in order to avoid zero stdev + // Add epsilon in order to avoid zero stdev $this->std[$label][$i] = 1e-10 + StandardDeviation::population($values, false); } } @@ -75,10 +113,11 @@ private function calculateStatistics($label, $samples) /** * Calculates the probability P(label|sample_n) - * + * * @param array $sample * @param int $feature * @param string $label + * @return float */ private function sampleProbability($sample, $feature, $label) { @@ -94,14 +133,14 @@ private function sampleProbability($sample, $feature, $label) $mean= $this->mean[$label][$feature]; // Calculate the probability density by use of normal/Gaussian distribution // Ref: https://en.wikipedia.org/wiki/Normal_distribution - // - // In order to avoid numerical errors because of small or zero values, - // some libraries adopt taking log of calculations such as - // scikit-learn did. - // (See : https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/naive_bayes.py) - $pdf = -0.5 * log(2.0 * pi() * $std * $std); - $pdf -= 0.5 * pow($value - $mean, 2) / ($std * $std); - return $pdf; + // + // In order to avoid numerical errors because of small or zero values, + // some libraries adopt taking log of calculations such as + // scikit-learn did. + // (See : https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/naive_bayes.py) + $pdf = -0.5 * log(2.0 * pi() * $std * $std); + $pdf -= 0.5 * pow($value - $mean, 2) / ($std * $std); + return $pdf; } /** From 95fc139170aa76f44b0ef3b554eefb31f0812a8c Mon Sep 17 00:00:00 2001 From: Mustafa Karabulut Date: Mon, 23 Jan 2017 10:24:50 +0200 Subject: [PATCH 141/328] Update Cluster.php (#32) --- src/Phpml/Clustering/KMeans/Cluster.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Phpml/Clustering/KMeans/Cluster.php b/src/Phpml/Clustering/KMeans/Cluster.php index 28fa8aff..5436ee99 100644 --- a/src/Phpml/Clustering/KMeans/Cluster.php +++ b/src/Phpml/Clustering/KMeans/Cluster.php @@ -136,4 +136,12 @@ public function count() { return count($this->points); } + + /** + * @param array $newCoordinates + */ + public function setCoordinates(array $newCoordinates) + { + $this->coordinates = $newCoordinates; + } } From 87396ebe5878410b3820c4f0818ff5d6b55528fc Mon Sep 17 00:00:00 2001 From: Mustafa Karabulut Date: Tue, 31 Jan 2017 21:27:15 +0200 Subject: [PATCH 142/328] DecisionTree and Fuzzy C Means classifiers (#35) * Fuzzy C-Means implementation * Update FuzzyCMeans * Rename FuzzyCMeans to FuzzyCMeans.php * Update NaiveBayes.php * Small fix applied to improve training performance array_unique is replaced with array_count_values+array_keys which is way faster * Revert "Small fix applied to improve training performance" This reverts commit c20253f16ac3e8c37d33ecaee28a87cc767e3b7f. * Revert "Revert "Small fix applied to improve training performance"" This reverts commit ea10e136c4c11b71609ccdcaf9999067e4be473e. * Revert "Small fix applied to improve training performance" This reverts commit c20253f16ac3e8c37d33ecaee28a87cc767e3b7f. * DecisionTree * FCM Test * FCM Test * DecisionTree Test --- src/Phpml/Classification/DecisionTree.php | 274 ++++++++++++++++++ .../DecisionTree/DecisionTreeLeaf.php | 106 +++++++ src/Phpml/Classification/NaiveBayes.php | 42 +-- src/Phpml/Clustering/FuzzyCMeans.php | 242 ++++++++++++++++ .../Phpml/Classification/DecisionTreeTest.php | 60 ++++ tests/Phpml/Clustering/FuzzyCMeansTest.php | 43 +++ 6 files changed, 740 insertions(+), 27 deletions(-) create mode 100644 src/Phpml/Classification/DecisionTree.php create mode 100644 src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php create mode 100644 src/Phpml/Clustering/FuzzyCMeans.php create mode 100644 tests/Phpml/Classification/DecisionTreeTest.php create mode 100644 tests/Phpml/Clustering/FuzzyCMeansTest.php diff --git a/src/Phpml/Classification/DecisionTree.php b/src/Phpml/Classification/DecisionTree.php new file mode 100644 index 00000000..033b22bf --- /dev/null +++ b/src/Phpml/Classification/DecisionTree.php @@ -0,0 +1,274 @@ +maxDepth = $maxDepth; + } + /** + * @param array $samples + * @param array $targets + */ + public function train(array $samples, array $targets) + { + $this->featureCount = count($samples[0]); + $this->columnTypes = $this->getColumnTypes($samples); + $this->samples = $samples; + $this->targets = $targets; + $this->labels = array_keys(array_count_values($targets)); + $this->tree = $this->getSplitLeaf(range(0, count($samples) - 1)); + } + + protected function getColumnTypes(array $samples) + { + $types = []; + for ($i=0; $i<$this->featureCount; $i++) { + $values = array_column($samples, $i); + $isCategorical = $this->isCategoricalColumn($values); + $types[] = $isCategorical ? self::NOMINAL : self::CONTINUOS; + } + return $types; + } + + /** + * @param null|array $records + * @return DecisionTreeLeaf + */ + protected function getSplitLeaf($records, $depth = 0) + { + $split = $this->getBestSplit($records); + $split->level = $depth; + if ($this->actualDepth < $depth) { + $this->actualDepth = $depth; + } + $leftRecords = []; + $rightRecords= []; + $remainingTargets = []; + $prevRecord = null; + $allSame = true; + foreach ($records as $recordNo) { + $record = $this->samples[$recordNo]; + if ($prevRecord && $prevRecord != $record) { + $allSame = false; + } + $prevRecord = $record; + if ($split->evaluate($record)) { + $leftRecords[] = $recordNo; + } else { + $rightRecords[]= $recordNo; + } + $target = $this->targets[$recordNo]; + if (! in_array($target, $remainingTargets)) { + $remainingTargets[] = $target; + } + } + + if (count($remainingTargets) == 1 || $allSame || $depth >= $this->maxDepth) { + $split->isTerminal = 1; + $classes = array_count_values($remainingTargets); + arsort($classes); + $split->classValue = key($classes); + } else { + if ($leftRecords) { + $split->leftLeaf = $this->getSplitLeaf($leftRecords, $depth + 1); + } + if ($rightRecords) { + $split->rightLeaf= $this->getSplitLeaf($rightRecords, $depth + 1); + } + } + return $split; + } + + /** + * @param array $records + * @return DecisionTreeLeaf[] + */ + protected function getBestSplit($records) + { + $targets = array_intersect_key($this->targets, array_flip($records)); + $samples = array_intersect_key($this->samples, array_flip($records)); + $samples = array_combine($records, $this->preprocess($samples)); + $bestGiniVal = 1; + $bestSplit = null; + for ($i=0; $i<$this->featureCount; $i++) { + $colValues = []; + $baseValue = null; + foreach ($samples as $index => $row) { + $colValues[$index] = $row[$i]; + if ($baseValue === null) { + $baseValue = $row[$i]; + } + } + $gini = $this->getGiniIndex($baseValue, $colValues, $targets); + if ($bestSplit == null || $bestGiniVal > $gini) { + $split = new DecisionTreeLeaf(); + $split->value = $baseValue; + $split->giniIndex = $gini; + $split->columnIndex = $i; + $split->records = $records; + $bestSplit = $split; + $bestGiniVal = $gini; + } + } + return $bestSplit; + } + + /** + * @param string $baseValue + * @param array $colValues + * @param array $targets + */ + public function getGiniIndex($baseValue, $colValues, $targets) + { + $countMatrix = []; + foreach ($this->labels as $label) { + $countMatrix[$label] = [0, 0]; + } + foreach ($colValues as $index => $value) { + $label = $targets[$index]; + $rowIndex = $value == $baseValue ? 0 : 1; + $countMatrix[$label][$rowIndex]++; + } + $giniParts = [0, 0]; + for ($i=0; $i<=1; $i++) { + $part = 0; + $sum = array_sum(array_column($countMatrix, $i)); + if ($sum > 0) { + foreach ($this->labels as $label) { + $part += pow($countMatrix[$label][$i] / floatval($sum), 2); + } + } + $giniParts[$i] = (1 - $part) * $sum; + } + return array_sum($giniParts) / count($colValues); + } + + /** + * @param array $samples + * @return array + */ + protected function preprocess(array $samples) + { + // Detect and convert continuous data column values into + // discrete values by using the median as a threshold value + $columns = array(); + for ($i=0; $i<$this->featureCount; $i++) { + $values = array_column($samples, $i); + if ($this->columnTypes[$i] == self::CONTINUOS) { + $median = Mean::median($values); + foreach ($values as &$value) { + if ($value <= $median) { + $value = "<= $median"; + } else { + $value = "> $median"; + } + } + } + $columns[] = $values; + } + // Below method is a strange yet very simple & efficient method + // to get the transpose of a 2D array + return array_map(null, ...$columns); + } + + /** + * @param array $columnValues + * @return bool + */ + protected function isCategoricalColumn(array $columnValues) + { + $count = count($columnValues); + // There are two main indicators that *may* show whether a + // column is composed of discrete set of values: + // 1- Column may contain string values + // 2- Number of unique values in the column is only a small fraction of + // all values in that column (Lower than or equal to %20 of all values) + $numericValues = array_filter($columnValues, 'is_numeric'); + if (count($numericValues) != $count) { + return true; + } + $distinctValues = array_count_values($columnValues); + if (count($distinctValues) <= $count / 5) { + return true; + } + return false; + } + + /** + * @return string + */ + public function getHtml() + { + return $this->tree->__toString(); + } + + /** + * @param array $sample + * @return mixed + */ + protected function predictSample(array $sample) + { + $node = $this->tree; + do { + if ($node->isTerminal) { + break; + } + if ($node->evaluate($sample)) { + $node = $node->leftLeaf; + } else { + $node = $node->rightLeaf; + } + } while ($node); + return $node->classValue; + } +} diff --git a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php new file mode 100644 index 00000000..220f8768 --- /dev/null +++ b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -0,0 +1,106 @@ +columnIndex]; + if (preg_match("/^([<>=]{1,2})\s*(.*)/", $this->value, $matches)) { + $op = $matches[1]; + $value= floatval($matches[2]); + $recordField = strval($recordField); + eval("\$result = $recordField $op $value;"); + return $result; + } + return $recordField == $this->value; + } + + public function __toString() + { + if ($this->isTerminal) { + $value = "$this->classValue"; + } else { + $value = $this->value; + $col = "col_$this->columnIndex"; + if (! preg_match("/^[<>=]{1,2}/", $value)) { + $value = "=$value"; + } + $value = "$col $value
Gini: ". number_format($this->giniIndex, 2); + } + $str = ""; + if ($this->leftLeaf || $this->rightLeaf) { + $str .=''; + if ($this->leftLeaf) { + $str .=""; + } else { + $str .=''; + } + $str .=''; + if ($this->rightLeaf) { + $str .=""; + } else { + $str .=''; + } + $str .= ''; + } + $str .= '
+ $value
| Yes
$this->leftLeaf
 No |
$this->rightLeaf
'; + return $str; + } +} diff --git a/src/Phpml/Classification/NaiveBayes.php b/src/Phpml/Classification/NaiveBayes.php index 1f897944..e86a91c5 100644 --- a/src/Phpml/Classification/NaiveBayes.php +++ b/src/Phpml/Classification/NaiveBayes.php @@ -68,8 +68,8 @@ public function train(array $samples, array $targets) $this->sampleCount = count($samples); $this->featureCount = count($samples[0]); - $this->labels = $targets; - array_unique($this->labels); + $labelCounts = array_count_values($targets); + $this->labels = array_keys($labelCounts); foreach ($this->labels as $label) { $samples = $this->getSamplesByLabel($label); $this->p[$label] = count($samples) / $this->sampleCount; @@ -165,32 +165,20 @@ private function getSamplesByLabel($label) */ protected function predictSample(array $sample) { - $isArray = is_array($sample[0]); - $samples = $sample; - if (!$isArray) { - $samples = array($sample); - } - $samplePredictions = array(); - foreach ($samples as $sample) { - // Use NaiveBayes assumption for each label using: - // P(label|features) = P(label) * P(feature0|label) * P(feature1|label) .... P(featureN|label) - // Then compare probability for each class to determine which label is most likely - $predictions = array(); - foreach ($this->labels as $label) { - $p = $this->p[$label]; - for ($i=0; $i<$this->featureCount; $i++) { - $Plf = $this->sampleProbability($sample, $i, $label); - $p += $Plf; - } - $predictions[$label] = $p; + // Use NaiveBayes assumption for each label using: + // P(label|features) = P(label) * P(feature0|label) * P(feature1|label) .... P(featureN|label) + // Then compare probability for each class to determine which label is most likely + $predictions = array(); + foreach ($this->labels as $label) { + $p = $this->p[$label]; + for ($i=0; $i<$this->featureCount; $i++) { + $Plf = $this->sampleProbability($sample, $i, $label); + $p += $Plf; } - arsort($predictions, SORT_NUMERIC); - reset($predictions); - $samplePredictions[] = key($predictions); - } - if (! $isArray) { - return $samplePredictions[0]; + $predictions[$label] = $p; } - return $samplePredictions; + arsort($predictions, SORT_NUMERIC); + reset($predictions); + return key($predictions); } } diff --git a/src/Phpml/Clustering/FuzzyCMeans.php b/src/Phpml/Clustering/FuzzyCMeans.php new file mode 100644 index 00000000..ed4fd9e3 --- /dev/null +++ b/src/Phpml/Clustering/FuzzyCMeans.php @@ -0,0 +1,242 @@ +clustersNumber = $clustersNumber; + $this->fuzziness = $fuzziness; + $this->epsilon = $epsilon; + $this->maxIterations = $maxIterations; + } + + protected function initClusters() + { + // Membership array is a matrix of cluster number by sample counts + // We initilize the membership array with random values + $dim = $this->space->getDimension(); + $this->generateRandomMembership($dim, $this->sampleCount); + $this->updateClusters(); + } + + /** + * @param int $rows + * @param int $cols + */ + protected function generateRandomMembership(int $rows, int $cols) + { + $this->membership = []; + for ($i=0; $i < $rows; $i++) { + $row = []; + $total = 0.0; + for ($k=0; $k < $cols; $k++) { + $val = rand(1, 5) / 10.0; + $row[] = $val; + $total += $val; + } + $this->membership[] = array_map(function ($val) use ($total) { + return $val / $total; + }, $row); + } + } + + protected function updateClusters() + { + $dim = $this->space->getDimension(); + if (! $this->clusters) { + $this->clusters = []; + for ($i=0; $i<$this->clustersNumber; $i++) { + $this->clusters[] = new Cluster($this->space, array_fill(0, $dim, 0.0)); + } + } + + for ($i=0; $i<$this->clustersNumber; $i++) { + $cluster = $this->clusters[$i]; + $center = $cluster->getCoordinates(); + for ($k=0; $k<$dim; $k++) { + $a = $this->getMembershipRowTotal($i, $k, true); + $b = $this->getMembershipRowTotal($i, $k, false); + $center[$k] = $a / $b; + } + $cluster->setCoordinates($center); + } + } + + protected function getMembershipRowTotal(int $row, int $col, bool $multiply) + { + $sum = 0.0; + for ($k = 0; $k < $this->sampleCount; $k++) { + $val = pow($this->membership[$row][$k], $this->fuzziness); + if ($multiply) { + $val *= $this->samples[$k][$col]; + } + $sum += $val; + } + return $sum; + } + + protected function updateMembershipMatrix() + { + for ($i = 0; $i < $this->clustersNumber; $i++) { + for ($k = 0; $k < $this->sampleCount; $k++) { + $distCalc = $this->getDistanceCalc($i, $k); + $this->membership[$i][$k] = 1.0 / $distCalc; + } + } + } + + /** + * + * @param int $row + * @param int $col + * @return float + */ + protected function getDistanceCalc(int $row, int $col) + { + $sum = 0.0; + $distance = new Euclidean(); + $dist1 = $distance->distance( + $this->clusters[$row]->getCoordinates(), + $this->samples[$col]); + for ($j = 0; $j < $this->clustersNumber; $j++) { + $dist2 = $distance->distance( + $this->clusters[$j]->getCoordinates(), + $this->samples[$col]); + $val = pow($dist1 / $dist2, 2.0 / ($this->fuzziness - 1)); + $sum += $val; + } + return $sum; + } + + /** + * The objective is to minimize the distance between all data points + * and all cluster centers. This method returns the summation of all + * these distances + */ + protected function getObjective() + { + $sum = 0.0; + $distance = new Euclidean(); + for ($i = 0; $i < $this->clustersNumber; $i++) { + $clust = $this->clusters[$i]->getCoordinates(); + for ($k = 0; $k < $this->sampleCount; $k++) { + $point = $this->samples[$k]; + $sum += $distance->distance($clust, $point); + } + } + return $sum; + } + + /** + * @return array + */ + public function getMembershipMatrix() + { + return $this->membership; + } + + /** + * @param array|Point[] $samples + * @return array + */ + public function cluster(array $samples) + { + // Initialize variables, clusters and membership matrix + $this->sampleCount = count($samples); + $this->samples =& $samples; + $this->space = new Space(count($samples[0])); + $this->initClusters(); + + // Our goal is minimizing the objective value while + // executing the clustering steps at a maximum number of iterations + $lastObjective = 0.0; + $difference = 0.0; + $iterations = 0; + do { + // Update the membership matrix and cluster centers, respectively + $this->updateMembershipMatrix(); + $this->updateClusters(); + + // Calculate the new value of the objective function + $objectiveVal = $this->getObjective(); + $difference = abs($lastObjective - $objectiveVal); + $lastObjective = $objectiveVal; + } while ($difference > $this->epsilon && $iterations++ <= $this->maxIterations); + + // Attach (hard cluster) each data point to the nearest cluster + for ($k=0; $k<$this->sampleCount; $k++) { + $column = array_column($this->membership, $k); + arsort($column); + reset($column); + $i = key($column); + $cluster = $this->clusters[$i]; + $cluster->attach(new Point($this->samples[$k])); + } + + // Return grouped samples + $grouped = []; + foreach ($this->clusters as $cluster) { + $grouped[] = $cluster->getPoints(); + } + return $grouped; + } +} diff --git a/tests/Phpml/Classification/DecisionTreeTest.php b/tests/Phpml/Classification/DecisionTreeTest.php new file mode 100644 index 00000000..c6f307db --- /dev/null +++ b/tests/Phpml/Classification/DecisionTreeTest.php @@ -0,0 +1,60 @@ +data; + $targets = array_column($data, 4); + array_walk($data, function (&$v) { + array_splice($v, 4, 1); + }); + } + return [$data, $targets]; + } + + public function testPredictSingleSample() + { + list($data, $targets) = $this->getData(); + $classifier = new DecisionTree(5); + $classifier->train($data, $targets); + $this->assertEquals('Dont_play', $classifier->predict(['sunny', 78, 72, 'false'])); + $this->assertEquals('Play', $classifier->predict(['overcast', 60, 60, 'false'])); + $this->assertEquals('Dont_play', $classifier->predict(['rain', 60, 60, 'true'])); + + return $classifier; + } + + public function testTreeDepth() + { + list($data, $targets) = $this->getData(); + $classifier = new DecisionTree(5); + $classifier->train($data, $targets); + $this->assertTrue(5 >= $classifier->actualDepth); + } +} diff --git a/tests/Phpml/Clustering/FuzzyCMeansTest.php b/tests/Phpml/Clustering/FuzzyCMeansTest.php new file mode 100644 index 00000000..16d4a976 --- /dev/null +++ b/tests/Phpml/Clustering/FuzzyCMeansTest.php @@ -0,0 +1,43 @@ +cluster($samples); + $this->assertCount(2, $clusters); + foreach ($samples as $index => $sample) { + if (in_array($sample, $clusters[0]) || in_array($sample, $clusters[1])) { + unset($samples[$index]); + } + } + $this->assertCount(0, $samples); + return $fcm; + } + + public function testMembershipMatrix() + { + $fcm = $this->testFCMSamplesClustering(); + $clusterCount = 2; + $sampleCount = 6; + $matrix = $fcm->getMembershipMatrix(); + $this->assertCount($clusterCount, $matrix); + foreach ($matrix as $row) { + $this->assertCount($sampleCount, $row); + } + // Transpose of the matrix + array_unshift($matrix, null); + $matrix = call_user_func_array('array_map', $matrix); + // All column totals should be equal to 1 (100% membership) + foreach ($matrix as $col) { + $this->assertEquals(1, array_sum($col)); + } + } +} \ No newline at end of file From c3686358b368aeac9ee61a33e0cc2c83d3873cf0 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 31 Jan 2017 20:33:08 +0100 Subject: [PATCH 143/328] Add rules for new cs-fixer --- .php_cs | 16 ++++++++++++++++ src/Phpml/Association/Apriori.php | 2 +- src/Phpml/Classification/DecisionTree.php | 9 ++++++--- .../DecisionTree/DecisionTreeLeaf.php | 1 + src/Phpml/Classification/NaiveBayes.php | 4 ++-- src/Phpml/Clustering/FuzzyCMeans.php | 1 + src/Phpml/Clustering/KMeans/Cluster.php | 6 +++--- src/Phpml/Clustering/KMeans/Space.php | 2 +- src/Phpml/Math/Matrix.php | 4 ++-- tests/Phpml/Association/AprioriTest.php | 2 +- tests/Phpml/Clustering/FuzzyCMeansTest.php | 5 +++-- tests/Phpml/Math/SetTest.php | 2 +- 12 files changed, 38 insertions(+), 16 deletions(-) create mode 100644 .php_cs diff --git a/.php_cs b/.php_cs new file mode 100644 index 00000000..417cafa3 --- /dev/null +++ b/.php_cs @@ -0,0 +1,16 @@ +setRules([ + '@PSR2' => true, + 'declare_strict_types' => true, + 'array_syntax' => ['syntax' => 'short'], + 'blank_line_after_opening_tag' => true, + 'single_blank_line_before_namespace' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__ . '/src') + ->in(__DIR__ . '/tests') + )->setRiskyAllowed(true) + ->setUsingCache(false); \ No newline at end of file diff --git a/src/Phpml/Association/Apriori.php b/src/Phpml/Association/Apriori.php index 48556917..362f25a6 100644 --- a/src/Phpml/Association/Apriori.php +++ b/src/Phpml/Association/Apriori.php @@ -160,7 +160,7 @@ private function powerSet(array $sample) : array $results = [[]]; foreach ($sample as $item) { foreach ($results as $combination) { - $results[] = array_merge(array($item), $combination); + $results[] = array_merge([$item], $combination); } } diff --git a/src/Phpml/Classification/DecisionTree.php b/src/Phpml/Classification/DecisionTree.php index 033b22bf..45b63298 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -19,20 +19,23 @@ class DecisionTree implements Classifier /** * @var array */ - private $samples = array(); + private $samples = []; /** * @var array */ private $columnTypes; + /** * @var array */ - private $labels = array(); + private $labels = []; + /** * @var int */ private $featureCount = 0; + /** * @var DecisionTreeLeaf */ @@ -201,7 +204,7 @@ protected function preprocess(array $samples) { // Detect and convert continuous data column values into // discrete values by using the median as a threshold value - $columns = array(); + $columns = []; for ($i=0; $i<$this->featureCount; $i++) { $values = array_column($samples, $i); if ($this->columnTypes[$i] == self::CONTINUOS) { diff --git a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php index 220f8768..d4289197 100644 --- a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -1,4 +1,5 @@ sampleCount; $i++) { if ($this->targets[$i] == $label) { $samples[] = $this->samples[$i]; @@ -168,7 +168,7 @@ protected function predictSample(array $sample) // Use NaiveBayes assumption for each label using: // P(label|features) = P(label) * P(feature0|label) * P(feature1|label) .... P(featureN|label) // Then compare probability for each class to determine which label is most likely - $predictions = array(); + $predictions = []; foreach ($this->labels as $label) { $p = $this->p[$label]; for ($i=0; $i<$this->featureCount; $i++) { diff --git a/src/Phpml/Clustering/FuzzyCMeans.php b/src/Phpml/Clustering/FuzzyCMeans.php index ed4fd9e3..424f2f15 100644 --- a/src/Phpml/Clustering/FuzzyCMeans.php +++ b/src/Phpml/Clustering/FuzzyCMeans.php @@ -1,4 +1,5 @@ parent::toArray(), 'points' => $this->getPoints(), - ); + ]; } /** @@ -143,5 +143,5 @@ public function count() public function setCoordinates(array $newCoordinates) { $this->coordinates = $newCoordinates; - } + } } diff --git a/src/Phpml/Clustering/KMeans/Space.php b/src/Phpml/Clustering/KMeans/Space.php index c51cc05c..5a4d5305 100644 --- a/src/Phpml/Clustering/KMeans/Space.php +++ b/src/Phpml/Clustering/KMeans/Space.php @@ -104,7 +104,7 @@ public function getBoundaries() } } - return array($min, $max); + return [$min, $max]; } /** diff --git a/src/Phpml/Math/Matrix.php b/src/Phpml/Math/Matrix.php index 4e70305f..6485a7db 100644 --- a/src/Phpml/Math/Matrix.php +++ b/src/Phpml/Math/Matrix.php @@ -212,7 +212,7 @@ public function multiply(Matrix $matrix) */ public function divideByScalar($value) { - $newMatrix = array(); + $newMatrix = []; for ($i = 0; $i < $this->rows; ++$i) { for ($j = 0; $j < $this->columns; ++$j) { $newMatrix[$i][$j] = $this->matrix[$i][$j] / $value; @@ -233,7 +233,7 @@ public function inverse() throw MatrixException::notSquareMatrix(); } - $newMatrix = array(); + $newMatrix = []; for ($i = 0; $i < $this->rows; ++$i) { for ($j = 0; $j < $this->columns; ++$j) { $minor = $this->crossOut($i, $j)->getDeterminant(); diff --git a/tests/Phpml/Association/AprioriTest.php b/tests/Phpml/Association/AprioriTest.php index 57ef5de5..45631086 100644 --- a/tests/Phpml/Association/AprioriTest.php +++ b/tests/Phpml/Association/AprioriTest.php @@ -176,7 +176,7 @@ public function testEquals() * * @return mixed */ - public function invoke(&$object, $method, array $params = array()) + public function invoke(&$object, $method, array $params = []) { $reflection = new \ReflectionClass(get_class($object)); $method = $reflection->getMethod($method); diff --git a/tests/Phpml/Clustering/FuzzyCMeansTest.php b/tests/Phpml/Clustering/FuzzyCMeansTest.php index 16d4a976..56d3c63c 100644 --- a/tests/Phpml/Clustering/FuzzyCMeansTest.php +++ b/tests/Phpml/Clustering/FuzzyCMeansTest.php @@ -1,4 +1,5 @@ cluster($samples); $this->assertCount(2, $clusters); foreach ($samples as $index => $sample) { @@ -40,4 +41,4 @@ public function testMembershipMatrix() $this->assertEquals(1, array_sum($col)); } } -} \ No newline at end of file +} diff --git a/tests/Phpml/Math/SetTest.php b/tests/Phpml/Math/SetTest.php index 5426764f..c6548c79 100644 --- a/tests/Phpml/Math/SetTest.php +++ b/tests/Phpml/Math/SetTest.php @@ -1,4 +1,4 @@ - Date: Wed, 1 Feb 2017 13:52:36 +0100 Subject: [PATCH 144/328] Note about updating docs in CONTRIBUTING.md (#39) --- CONTRIBUTING.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8084dc89..d9dcaff5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,6 +38,10 @@ This script run PHP Coding Standards Fixer with `--level=symfony` param. More about PHP-CS-Fixer: [http://cs.sensiolabs.org/](http://cs.sensiolabs.org/) +## Documentation + +Please update the documentation pages if necessary. You can find them in docs/. + --- Thank you very much again for your contribution! From c1b1a5d6ac368ad9b71240058596b356d8e71d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Monlla=C3=B3?= Date: Wed, 1 Feb 2017 19:06:38 +0100 Subject: [PATCH 145/328] Support for multiple training datasets (#38) * Multiple training data sets allowed * Tests with multiple training data sets * Updating docs according to #38 Documenting all models which predictions will be based on all training data provided. Some models already supported multiple training data sets. --- docs/machine-learning/association/apriori.md | 2 ++ .../classification/k-nearest-neighbors.md | 2 ++ .../classification/naive-bayes.md | 2 ++ docs/machine-learning/classification/svc.md | 2 ++ .../neural-network/backpropagation.md | 1 + .../regression/least-squares.md | 2 ++ docs/machine-learning/regression/svr.md | 2 ++ src/Phpml/Classification/DecisionTree.php | 13 ++++---- src/Phpml/Classification/NaiveBayes.php | 10 +++--- src/Phpml/Helper/Trainable.php | 8 ++--- src/Phpml/Regression/LeastSquares.php | 8 ++--- .../Phpml/Classification/DecisionTreeTest.php | 31 +++++++++++-------- tests/Phpml/Classification/NaiveBayesTest.php | 10 ++++++ 13 files changed, 61 insertions(+), 32 deletions(-) diff --git a/docs/machine-learning/association/apriori.md b/docs/machine-learning/association/apriori.md index 544406e0..e6685af8 100644 --- a/docs/machine-learning/association/apriori.md +++ b/docs/machine-learning/association/apriori.md @@ -27,6 +27,8 @@ $associator = new Apriori($support = 0.5, $confidence = 0.5); $associator->train($samples, $labels); ``` +You can train the associator using multiple data sets, predictions will be based on all the training data. + ### Predict To predict sample label use `predict` method. You can provide one sample or array of samples: diff --git a/docs/machine-learning/classification/k-nearest-neighbors.md b/docs/machine-learning/classification/k-nearest-neighbors.md index 6e70c611..a4eb96ca 100644 --- a/docs/machine-learning/classification/k-nearest-neighbors.md +++ b/docs/machine-learning/classification/k-nearest-neighbors.md @@ -24,6 +24,8 @@ $classifier = new KNearestNeighbors(); $classifier->train($samples, $labels); ``` +You can train the classifier using multiple data sets, predictions will be based on all the training data. + ## Predict To predict sample label use `predict` method. You can provide one sample or array of samples: diff --git a/docs/machine-learning/classification/naive-bayes.md b/docs/machine-learning/classification/naive-bayes.md index e990321f..410fd458 100644 --- a/docs/machine-learning/classification/naive-bayes.md +++ b/docs/machine-learning/classification/naive-bayes.md @@ -14,6 +14,8 @@ $classifier = new NaiveBayes(); $classifier->train($samples, $labels); ``` +You can train the classifier using multiple data sets, predictions will be based on all the training data. + ### Predict To predict sample label use `predict` method. You can provide one sample or array of samples: diff --git a/docs/machine-learning/classification/svc.md b/docs/machine-learning/classification/svc.md index d502dac2..62da5095 100644 --- a/docs/machine-learning/classification/svc.md +++ b/docs/machine-learning/classification/svc.md @@ -34,6 +34,8 @@ $classifier = new SVC(Kernel::LINEAR, $cost = 1000); $classifier->train($samples, $labels); ``` +You can train the classifier using multiple data sets, predictions will be based on all the training data. + ### Predict To predict sample label use `predict` method. You can provide one sample or array of samples: diff --git a/docs/machine-learning/neural-network/backpropagation.md b/docs/machine-learning/neural-network/backpropagation.md index 8c9b5609..05823517 100644 --- a/docs/machine-learning/neural-network/backpropagation.md +++ b/docs/machine-learning/neural-network/backpropagation.md @@ -27,3 +27,4 @@ $training->train( $maxIteraions = 30000 ); ``` +You can train the neural network using multiple data sets, predictions will be based on all the training data. diff --git a/docs/machine-learning/regression/least-squares.md b/docs/machine-learning/regression/least-squares.md index 4a00bcdb..84a32791 100644 --- a/docs/machine-learning/regression/least-squares.md +++ b/docs/machine-learning/regression/least-squares.md @@ -14,6 +14,8 @@ $regression = new LeastSquares(); $regression->train($samples, $targets); ``` +You can train the model using multiple data sets, predictions will be based on all the training data. + ### Predict To predict sample target value use `predict` method with sample to check (as `array`). Example: diff --git a/docs/machine-learning/regression/svr.md b/docs/machine-learning/regression/svr.md index ed2d10ff..ba6bd74d 100644 --- a/docs/machine-learning/regression/svr.md +++ b/docs/machine-learning/regression/svr.md @@ -34,6 +34,8 @@ $regression = new SVR(Kernel::LINEAR); $regression->train($samples, $targets); ``` +You can train the model using multiple data sets, predictions will be based on all the training data. + ### Predict To predict sample target value use `predict` method. You can provide one sample or array of samples: diff --git a/src/Phpml/Classification/DecisionTree.php b/src/Phpml/Classification/DecisionTree.php index 45b63298..1a39cbe9 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -64,12 +64,13 @@ public function __construct($maxDepth = 10) */ public function train(array $samples, array $targets) { - $this->featureCount = count($samples[0]); - $this->columnTypes = $this->getColumnTypes($samples); - $this->samples = $samples; - $this->targets = $targets; - $this->labels = array_keys(array_count_values($targets)); - $this->tree = $this->getSplitLeaf(range(0, count($samples) - 1)); + $this->samples = array_merge($this->samples, $samples); + $this->targets = array_merge($this->targets, $targets); + + $this->featureCount = count($this->samples[0]); + $this->columnTypes = $this->getColumnTypes($this->samples); + $this->labels = array_keys(array_count_values($this->targets)); + $this->tree = $this->getSplitLeaf(range(0, count($this->samples) - 1)); } protected function getColumnTypes(array $samples) diff --git a/src/Phpml/Classification/NaiveBayes.php b/src/Phpml/Classification/NaiveBayes.php index 2596ada0..af81b00a 100644 --- a/src/Phpml/Classification/NaiveBayes.php +++ b/src/Phpml/Classification/NaiveBayes.php @@ -63,12 +63,12 @@ class NaiveBayes implements Classifier */ public function train(array $samples, array $targets) { - $this->samples = $samples; - $this->targets = $targets; - $this->sampleCount = count($samples); - $this->featureCount = count($samples[0]); + $this->samples = array_merge($this->samples, $samples); + $this->targets = array_merge($this->targets, $targets); + $this->sampleCount = count($this->samples); + $this->featureCount = count($this->samples[0]); - $labelCounts = array_count_values($targets); + $labelCounts = array_count_values($this->targets); $this->labels = array_keys($labelCounts); foreach ($this->labels as $label) { $samples = $this->getSamplesByLabel($label); diff --git a/src/Phpml/Helper/Trainable.php b/src/Phpml/Helper/Trainable.php index e58a1da4..3d011ac4 100644 --- a/src/Phpml/Helper/Trainable.php +++ b/src/Phpml/Helper/Trainable.php @@ -9,12 +9,12 @@ trait Trainable /** * @var array */ - private $samples; + private $samples = []; /** * @var array */ - private $targets; + private $targets = []; /** * @param array $samples @@ -22,7 +22,7 @@ trait Trainable */ public function train(array $samples, array $targets) { - $this->samples = $samples; - $this->targets = $targets; + $this->samples = array_merge($this->samples, $samples); + $this->targets = array_merge($this->targets, $targets); } } diff --git a/src/Phpml/Regression/LeastSquares.php b/src/Phpml/Regression/LeastSquares.php index 19609fbe..1b664ed0 100644 --- a/src/Phpml/Regression/LeastSquares.php +++ b/src/Phpml/Regression/LeastSquares.php @@ -13,12 +13,12 @@ class LeastSquares implements Regression /** * @var array */ - private $samples; + private $samples = []; /** * @var array */ - private $targets; + private $targets = []; /** * @var float @@ -36,8 +36,8 @@ class LeastSquares implements Regression */ public function train(array $samples, array $targets) { - $this->samples = $samples; - $this->targets = $targets; + $this->samples = array_merge($this->samples, $samples); + $this->targets = array_merge($this->targets, $targets); $this->computeCoefficients(); } diff --git a/tests/Phpml/Classification/DecisionTreeTest.php b/tests/Phpml/Classification/DecisionTreeTest.php index c6f307db..25fb94c7 100644 --- a/tests/Phpml/Classification/DecisionTreeTest.php +++ b/tests/Phpml/Classification/DecisionTreeTest.php @@ -8,7 +8,7 @@ class DecisionTreeTest extends \PHPUnit_Framework_TestCase { - public $data = [ + private $data = [ ['sunny', 85, 85, 'false', 'Dont_play' ], ['sunny', 80, 90, 'true', 'Dont_play' ], ['overcast', 83, 78, 'false', 'Play' ], @@ -25,34 +25,39 @@ class DecisionTreeTest extends \PHPUnit_Framework_TestCase ['rain', 71, 80, 'true', 'Dont_play' ] ]; - public function getData() + private $extraData = [ + ['scorching', 90, 95, 'false', 'Dont_play'], + ['scorching', 100, 93, 'true', 'Dont_play'], + ]; + + private function getData($input) { - static $data = null, $targets = null; - if ($data == null) { - $data = $this->data; - $targets = array_column($data, 4); - array_walk($data, function (&$v) { - array_splice($v, 4, 1); - }); - } - return [$data, $targets]; + $targets = array_column($input, 4); + array_walk($input, function (&$v) { + array_splice($v, 4, 1); + }); + return [$input, $targets]; } public function testPredictSingleSample() { - list($data, $targets) = $this->getData(); + list($data, $targets) = $this->getData($this->data); $classifier = new DecisionTree(5); $classifier->train($data, $targets); $this->assertEquals('Dont_play', $classifier->predict(['sunny', 78, 72, 'false'])); $this->assertEquals('Play', $classifier->predict(['overcast', 60, 60, 'false'])); $this->assertEquals('Dont_play', $classifier->predict(['rain', 60, 60, 'true'])); + list($data, $targets) = $this->getData($this->extraData); + $classifier->train($data, $targets); + $this->assertEquals('Dont_play', $classifier->predict(['scorching', 95, 90, 'true'])); + $this->assertEquals('Play', $classifier->predict(['overcast', 60, 60, 'false'])); return $classifier; } public function testTreeDepth() { - list($data, $targets) = $this->getData(); + list($data, $targets) = $this->getData($this->data); $classifier = new DecisionTree(5); $classifier->train($data, $targets); $this->assertTrue(5 >= $classifier->actualDepth); diff --git a/tests/Phpml/Classification/NaiveBayesTest.php b/tests/Phpml/Classification/NaiveBayesTest.php index f2edb027..1a0aa1f7 100644 --- a/tests/Phpml/Classification/NaiveBayesTest.php +++ b/tests/Phpml/Classification/NaiveBayesTest.php @@ -34,5 +34,15 @@ public function testPredictArrayOfSamples() $predicted = $classifier->predict($testSamples); $this->assertEquals($testLabels, $predicted); + + // Feed an extra set of training data. + $samples = [[1, 1, 6]]; + $labels = ['d']; + $classifier->train($samples, $labels); + + $testSamples = [[1, 1, 6], [5, 1, 1]]; + $testLabels = ['d', 'a']; + $this->assertEquals($testLabels, $classifier->predict($testSamples)); + } } From 8f122fde90dd099ff9566c0cf9ae34d63b3d6b2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Monlla=C3=B3?= Date: Thu, 2 Feb 2017 09:03:09 +0100 Subject: [PATCH 146/328] Persistence class to save and restore models (#37) * Models manager with save/restore capabilities * Refactoring dataset exceptions * Persistency layer docs * New tests for serializable estimators * ModelManager static methods to instance methods --- docs/index.md | 2 + .../model-manager/persistency.md | 24 +++++++++ mkdocs.yml | 2 + src/Phpml/Dataset/CsvDataset.php | 8 +-- src/Phpml/Exception/DatasetException.php | 19 ------- src/Phpml/Exception/FileException.php | 39 ++++++++++++++ src/Phpml/Exception/SerializeException.php | 30 +++++++++++ src/Phpml/ModelManager.php | 52 +++++++++++++++++++ tests/Phpml/Association/AprioriTest.php | 19 +++++++ .../Phpml/Classification/DecisionTreeTest.php | 21 ++++++++ .../Classification/KNearestNeighborsTest.php | 24 +++++++++ tests/Phpml/Classification/NaiveBayesTest.php | 24 +++++++++ tests/Phpml/Classification/SVCTest.php | 23 ++++++++ tests/Phpml/Dataset/CsvDatasetTest.php | 2 +- tests/Phpml/ModelManagerTest.php | 47 +++++++++++++++++ tests/Phpml/Regression/LeastSquaresTest.php | 25 +++++++++ tests/Phpml/Regression/SVRTest.php | 24 +++++++++ 17 files changed, 361 insertions(+), 24 deletions(-) create mode 100644 docs/machine-learning/model-manager/persistency.md create mode 100644 src/Phpml/Exception/FileException.php create mode 100644 src/Phpml/Exception/SerializeException.php create mode 100644 src/Phpml/ModelManager.php create mode 100644 tests/Phpml/ModelManagerTest.php diff --git a/docs/index.md b/docs/index.md index 423877fc..156acb23 100644 --- a/docs/index.md +++ b/docs/index.md @@ -84,6 +84,8 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * [Iris](machine-learning/datasets/demo/iris/) * [Wine](machine-learning/datasets/demo/wine/) * [Glass](machine-learning/datasets/demo/glass/) +* Models management + * [Persistency](machine-learning/model-manager/persistency/) * Math * [Distance](math/distance/) * [Matrix](math/matrix/) diff --git a/docs/machine-learning/model-manager/persistency.md b/docs/machine-learning/model-manager/persistency.md new file mode 100644 index 00000000..626ae421 --- /dev/null +++ b/docs/machine-learning/model-manager/persistency.md @@ -0,0 +1,24 @@ +# Persistency + +You can save trained models for future use. Persistency across requests achieved by saving and restoring serialized estimators into files. + +### Example + +``` +use Phpml\Classification\KNearestNeighbors; +use Phpml\ModelManager; + +$samples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; +$labels = ['a', 'a', 'a', 'b', 'b', 'b']; + +$classifier = new KNearestNeighbors(); +$classifier->train($samples, $labels); + +$filepath = '/path/to/store/the/model'; +$modelManager = new ModelManager(); +$modelManager->saveToFile($classifier, $filepath); + +$restoredClassifier = $modelManager->restoreFromFile($filepath); +$restoredClassifier->predict([3, 2]); +// return 'b' +``` diff --git a/mkdocs.yml b/mkdocs.yml index b404e283..433cc3e9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -40,6 +40,8 @@ pages: - Iris: machine-learning/datasets/demo/iris.md - Wine: machine-learning/datasets/demo/wine.md - Glass: machine-learning/datasets/demo/glass.md + - Models management: + - Persistency: machine-learning/model-manager/persistency.md - Math: - Distance: math/distance.md - Matrix: math/matrix.md diff --git a/src/Phpml/Dataset/CsvDataset.php b/src/Phpml/Dataset/CsvDataset.php index ab9a2b76..dd722d4d 100644 --- a/src/Phpml/Dataset/CsvDataset.php +++ b/src/Phpml/Dataset/CsvDataset.php @@ -4,7 +4,7 @@ namespace Phpml\Dataset; -use Phpml\Exception\DatasetException; +use Phpml\Exception\FileException; class CsvDataset extends ArrayDataset { @@ -13,16 +13,16 @@ class CsvDataset extends ArrayDataset * @param int $features * @param bool $headingRow * - * @throws DatasetException + * @throws FileException */ public function __construct(string $filepath, int $features, bool $headingRow = true) { if (!file_exists($filepath)) { - throw DatasetException::missingFile(basename($filepath)); + throw FileException::missingFile(basename($filepath)); } if (false === $handle = fopen($filepath, 'rb')) { - throw DatasetException::cantOpenFile(basename($filepath)); + throw FileException::cantOpenFile(basename($filepath)); } if ($headingRow) { diff --git a/src/Phpml/Exception/DatasetException.php b/src/Phpml/Exception/DatasetException.php index 85f911f5..60920536 100644 --- a/src/Phpml/Exception/DatasetException.php +++ b/src/Phpml/Exception/DatasetException.php @@ -6,15 +6,6 @@ class DatasetException extends \Exception { - /** - * @param string $filepath - * - * @return DatasetException - */ - public static function missingFile(string $filepath) - { - return new self(sprintf('Dataset file "%s" missing.', $filepath)); - } /** * @param string $path @@ -25,14 +16,4 @@ public static function missingFolder(string $path) { return new self(sprintf('Dataset root folder "%s" missing.', $path)); } - - /** - * @param string $filepath - * - * @return DatasetException - */ - public static function cantOpenFile(string $filepath) - { - return new self(sprintf('Dataset file "%s" can\'t be open.', $filepath)); - } } diff --git a/src/Phpml/Exception/FileException.php b/src/Phpml/Exception/FileException.php new file mode 100644 index 00000000..558ae48a --- /dev/null +++ b/src/Phpml/Exception/FileException.php @@ -0,0 +1,39 @@ +invokeArgs($object, $params); } + + public function testSaveAndRestore() + { + $classifier = new Apriori(0.5, 0.5); + $classifier->train($this->sampleGreek, []); + + $testSamples = [['alpha', 'epsilon'], ['beta', 'theta']]; + $predicted = $classifier->predict($testSamples); + + $filename = 'apriori-test-'.rand(100, 999).'-'.uniqid(); + $filepath = tempnam(sys_get_temp_dir(), $filename); + $modelManager = new ModelManager(); + $modelManager->saveToFile($classifier, $filepath); + + $restoredClassifier = $modelManager->restoreFromFile($filepath); + $this->assertEquals($classifier, $restoredClassifier); + $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + } } diff --git a/tests/Phpml/Classification/DecisionTreeTest.php b/tests/Phpml/Classification/DecisionTreeTest.php index 25fb94c7..7ec25bad 100644 --- a/tests/Phpml/Classification/DecisionTreeTest.php +++ b/tests/Phpml/Classification/DecisionTreeTest.php @@ -5,6 +5,7 @@ namespace tests\Classification; use Phpml\Classification\DecisionTree; +use Phpml\ModelManager; class DecisionTreeTest extends \PHPUnit_Framework_TestCase { @@ -55,6 +56,26 @@ public function testPredictSingleSample() return $classifier; } + public function testSaveAndRestore() + { + list($data, $targets) = $this->getData($this->data); + $classifier = new DecisionTree(5); + $classifier->train($data, $targets); + + $testSamples = [['sunny', 78, 72, 'false'], ['overcast', 60, 60, 'false']]; + $predicted = $classifier->predict($testSamples); + + $filename = 'decision-tree-test-'.rand(100, 999).'-'.uniqid(); + $filepath = tempnam(sys_get_temp_dir(), $filename); + $modelManager = new ModelManager(); + $modelManager->saveToFile($classifier, $filepath); + + $restoredClassifier = $modelManager->restoreFromFile($filepath); + $this->assertEquals($classifier, $restoredClassifier); + $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + + } + public function testTreeDepth() { list($data, $targets) = $this->getData($this->data); diff --git a/tests/Phpml/Classification/KNearestNeighborsTest.php b/tests/Phpml/Classification/KNearestNeighborsTest.php index 7c5a75a2..9824e5d7 100644 --- a/tests/Phpml/Classification/KNearestNeighborsTest.php +++ b/tests/Phpml/Classification/KNearestNeighborsTest.php @@ -6,6 +6,7 @@ use Phpml\Classification\KNearestNeighbors; use Phpml\Math\Distance\Chebyshev; +use Phpml\ModelManager; class KNearestNeighborsTest extends \PHPUnit_Framework_TestCase { @@ -57,4 +58,27 @@ public function testPredictArrayOfSamplesUsingChebyshevDistanceMetric() $this->assertEquals($testLabels, $predicted); } + + public function testSaveAndRestore() + { + $trainSamples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; + $trainLabels = ['a', 'a', 'a', 'b', 'b', 'b']; + + $testSamples = [[3, 2], [5, 1], [4, 3], [4, -5], [2, 3], [1, 2], [1, 5], [3, 10]]; + $testLabels = ['b', 'b', 'b', 'b', 'a', 'a', 'a', 'a']; + + // Using non-default constructor parameters to check that their values are restored. + $classifier = new KNearestNeighbors(3, new Chebyshev()); + $classifier->train($trainSamples, $trainLabels); + $predicted = $classifier->predict($testSamples); + + $filename = 'knearest-neighbors-test-'.rand(100, 999).'-'.uniqid(); + $filepath = tempnam(sys_get_temp_dir(), $filename); + $modelManager = new ModelManager(); + $modelManager->saveToFile($classifier, $filepath); + + $restoredClassifier = $modelManager->restoreFromFile($filepath); + $this->assertEquals($classifier, $restoredClassifier); + $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + } } diff --git a/tests/Phpml/Classification/NaiveBayesTest.php b/tests/Phpml/Classification/NaiveBayesTest.php index 1a0aa1f7..1f66ec83 100644 --- a/tests/Phpml/Classification/NaiveBayesTest.php +++ b/tests/Phpml/Classification/NaiveBayesTest.php @@ -5,6 +5,7 @@ namespace tests\Classification; use Phpml\Classification\NaiveBayes; +use Phpml\ModelManager; class NaiveBayesTest extends \PHPUnit_Framework_TestCase { @@ -45,4 +46,27 @@ public function testPredictArrayOfSamples() $this->assertEquals($testLabels, $classifier->predict($testSamples)); } + + public function testSaveAndRestore() + { + $trainSamples = [[5, 1, 1], [1, 5, 1], [1, 1, 5]]; + $trainLabels = ['a', 'b', 'c']; + + $testSamples = [[3, 1, 1], [5, 1, 1], [4, 3, 8]]; + $testLabels = ['a', 'a', 'c']; + + $classifier = new NaiveBayes(); + $classifier->train($trainSamples, $trainLabels); + $predicted = $classifier->predict($testSamples); + + $filename = 'naive-bayes-test-'.rand(100, 999).'-'.uniqid(); + $filepath = tempnam(sys_get_temp_dir(), $filename); + $modelManager = new ModelManager(); + $modelManager->saveToFile($classifier, $filepath); + + $restoredClassifier = $modelManager->restoreFromFile($filepath); + $this->assertEquals($classifier, $restoredClassifier); + $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + } + } diff --git a/tests/Phpml/Classification/SVCTest.php b/tests/Phpml/Classification/SVCTest.php index 8b17b533..34111b40 100644 --- a/tests/Phpml/Classification/SVCTest.php +++ b/tests/Phpml/Classification/SVCTest.php @@ -6,6 +6,7 @@ use Phpml\Classification\SVC; use Phpml\SupportVectorMachine\Kernel; +use Phpml\ModelManager; class SVCTest extends \PHPUnit_Framework_TestCase { @@ -42,4 +43,26 @@ public function testPredictArrayOfSamplesWithLinearKernel() $this->assertEquals($testLabels, $predictions); } + + public function testSaveAndRestore() + { + $trainSamples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; + $trainLabels = ['a', 'a', 'a', 'b', 'b', 'b']; + + $testSamples = [[3, 2], [5, 1], [4, 3]]; + $testLabels = ['b', 'b', 'b']; + + $classifier = new SVC(Kernel::LINEAR, $cost = 1000); + $classifier->train($trainSamples, $trainLabels); + $predicted = $classifier->predict($testSamples); + + $filename = 'svc-test-'.rand(100, 999).'-'.uniqid(); + $filepath = tempnam(sys_get_temp_dir(), $filename); + $modelManager = new ModelManager(); + $modelManager->saveToFile($classifier, $filepath); + + $restoredClassifier = $modelManager->restoreFromFile($filepath); + $this->assertEquals($classifier, $restoredClassifier); + $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + } } diff --git a/tests/Phpml/Dataset/CsvDatasetTest.php b/tests/Phpml/Dataset/CsvDatasetTest.php index 44e745af..65c589ae 100644 --- a/tests/Phpml/Dataset/CsvDatasetTest.php +++ b/tests/Phpml/Dataset/CsvDatasetTest.php @@ -9,7 +9,7 @@ class CsvDatasetTest extends \PHPUnit_Framework_TestCase { /** - * @expectedException \Phpml\Exception\DatasetException + * @expectedException \Phpml\Exception\FileException */ public function testThrowExceptionOnMissingFile() { diff --git a/tests/Phpml/ModelManagerTest.php b/tests/Phpml/ModelManagerTest.php new file mode 100644 index 00000000..75044e9b --- /dev/null +++ b/tests/Phpml/ModelManagerTest.php @@ -0,0 +1,47 @@ +saveToFile($obj, $filepath); + + $restored = $modelManager->restoreFromFile($filepath); + $this->assertEquals($obj, $restored); + } + + /** + * @expectedException \Phpml\Exception\FileException + */ + public function testSaveToWrongFile() + { + $filepath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'unexisting'; + + $obj = new LeastSquares(); + $modelManager = new ModelManager(); + $modelManager->saveToFile($obj, $filepath); + } + + /** + * @expectedException \Phpml\Exception\FileException + */ + public function testRestoreWrongFile() + { + $filepath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'unexisting'; + $modelManager = new ModelManager(); + $modelManager->restoreFromFile($filepath); + } +} diff --git a/tests/Phpml/Regression/LeastSquaresTest.php b/tests/Phpml/Regression/LeastSquaresTest.php index c668b88d..2cd3885b 100644 --- a/tests/Phpml/Regression/LeastSquaresTest.php +++ b/tests/Phpml/Regression/LeastSquaresTest.php @@ -5,6 +5,7 @@ namespace tests\Regression; use Phpml\Regression\LeastSquares; +use Phpml\ModelManager; class LeastSquaresTest extends \PHPUnit_Framework_TestCase { @@ -65,4 +66,28 @@ public function testPredictMultiFeaturesSamples() $this->assertEquals(4094.82, $regression->predict([60000, 1996]), '', $delta); $this->assertEquals(5711.40, $regression->predict([60000, 2000]), '', $delta); } + + public function testSaveAndRestore() + { + //https://www.easycalculation.com/analytical/learn-least-square-regression.php + $samples = [[60], [61], [62], [63], [65]]; + $targets = [[3.1], [3.6], [3.8], [4], [4.1]]; + + $regression = new LeastSquares(); + $regression->train($samples, $targets); + + //http://www.stat.wmich.edu/s216/book/node127.html + $testSamples = [[9300], [10565], [15000]]; + $predicted = $regression->predict($testSamples); + + $filename = 'least-squares-test-'.rand(100, 999).'-'.uniqid(); + $filepath = tempnam(sys_get_temp_dir(), $filename); + $modelManager = new ModelManager(); + $modelManager->saveToFile($regression, $filepath); + + $restoredRegression = $modelManager->restoreFromFile($filepath); + $this->assertEquals($regression, $restoredRegression); + $this->assertEquals($predicted, $restoredRegression->predict($testSamples)); + } + } diff --git a/tests/Phpml/Regression/SVRTest.php b/tests/Phpml/Regression/SVRTest.php index 5f8bec9b..17bb66ea 100644 --- a/tests/Phpml/Regression/SVRTest.php +++ b/tests/Phpml/Regression/SVRTest.php @@ -6,6 +6,7 @@ use Phpml\Regression\SVR; use Phpml\SupportVectorMachine\Kernel; +use Phpml\ModelManager; class SVRTest extends \PHPUnit_Framework_TestCase { @@ -34,4 +35,27 @@ public function testPredictMultiFeaturesSamples() $this->assertEquals([4109.82, 4112.28], $regression->predict([[60000, 1996], [60000, 2000]]), '', $delta); } + + public function testSaveAndRestore() + { + + $samples = [[60], [61], [62], [63], [65]]; + $targets = [3.1, 3.6, 3.8, 4, 4.1]; + + $regression = new SVR(Kernel::LINEAR); + $regression->train($samples, $targets); + + $testSamples = [64]; + $predicted = $regression->predict($testSamples); + + $filename = 'svr-test'.rand(100, 999).'-'.uniqid(); + $filepath = tempnam(sys_get_temp_dir(), $filename); + $modelManager = new ModelManager(); + $modelManager->saveToFile($regression, $filepath); + + $restoredRegression = $modelManager->restoreFromFile($filepath); + $this->assertEquals($regression, $restoredRegression); + $this->assertEquals($predicted, $restoredRegression->predict($testSamples)); + } + } From 858d13b0faa593e60230bf09b9d51c8f2aff252c Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 3 Feb 2017 12:58:25 +0100 Subject: [PATCH 147/328] Update phpunit to 6.0 --- composer.json | 4 +- composer.lock | 323 ++++++++---------- src/Phpml/Exception/SerializeException.php | 1 - tests/Phpml/Association/AprioriTest.php | 3 +- .../Phpml/Classification/DecisionTreeTest.php | 4 +- .../Classification/KNearestNeighborsTest.php | 3 +- tests/Phpml/Classification/NaiveBayesTest.php | 5 +- tests/Phpml/Classification/SVCTest.php | 3 +- tests/Phpml/Clustering/DBSCANTest.php | 3 +- tests/Phpml/Clustering/FuzzyCMeansTest.php | 3 +- tests/Phpml/Clustering/KMeansTest.php | 3 +- .../Phpml/CrossValidation/RandomSplitTest.php | 3 +- .../StratifiedRandomSplitTest.php | 3 +- tests/Phpml/Dataset/ArrayDatasetTest.php | 3 +- tests/Phpml/Dataset/CsvDatasetTest.php | 3 +- tests/Phpml/Dataset/Demo/GlassDatasetTest.php | 3 +- tests/Phpml/Dataset/Demo/IrisDatasetTest.php | 3 +- tests/Phpml/Dataset/Demo/WineDatasetTest.php | 3 +- tests/Phpml/Dataset/FilesDatasetTest.php | 3 +- .../Phpml/FeatureExtraction/StopWordsTest.php | 3 +- .../TfIdfTransformerTest.php | 3 +- .../TokenCountVectorizerTest.php | 3 +- tests/Phpml/Math/Distance/ChebyshevTest.php | 3 +- tests/Phpml/Math/Distance/EuclideanTest.php | 3 +- tests/Phpml/Math/Distance/ManhattanTest.php | 3 +- tests/Phpml/Math/Distance/MinkowskiTest.php | 3 +- tests/Phpml/Math/Kernel/RBFTest.php | 3 +- tests/Phpml/Math/MatrixTest.php | 3 +- tests/Phpml/Math/ProductTest.php | 3 +- tests/Phpml/Math/SetTest.php | 7 +- .../Phpml/Math/Statistic/CorrelationTest.php | 3 +- tests/Phpml/Math/Statistic/MeanTest.php | 3 +- .../Math/Statistic/StandardDeviationTest.php | 3 +- tests/Phpml/Metric/AccuracyTest.php | 3 +- .../Phpml/Metric/ClassificationReportTest.php | 3 +- tests/Phpml/Metric/ConfusionMatrixTest.php | 3 +- tests/Phpml/ModelManagerTest.php | 4 +- .../ActivationFunction/BinaryStepTest.php | 3 +- .../ActivationFunction/GaussianTest.php | 3 +- .../HyperboliTangentTest.php | 3 +- .../ActivationFunction/SigmoidTest.php | 3 +- tests/Phpml/NeuralNetwork/LayerTest.php | 3 +- .../Network/LayeredNetworkTest.php | 3 +- .../Network/MultilayerPerceptronTest.php | 3 +- tests/Phpml/NeuralNetwork/Node/BiasTest.php | 3 +- tests/Phpml/NeuralNetwork/Node/InputTest.php | 3 +- .../NeuralNetwork/Node/Neuron/SynapseTest.php | 3 +- tests/Phpml/NeuralNetwork/Node/NeuronTest.php | 3 +- .../Training/BackpropagationTest.php | 3 +- tests/Phpml/PipelineTest.php | 3 +- tests/Phpml/Preprocessing/ImputerTest.php | 3 +- tests/Phpml/Preprocessing/NormalizerTest.php | 3 +- tests/Phpml/Regression/LeastSquaresTest.php | 4 +- tests/Phpml/Regression/SVRTest.php | 5 +- .../DataTransformerTest.php | 3 +- .../SupportVectorMachineTest.php | 3 +- .../Tokenization/WhitespaceTokenizerTest.php | 3 +- .../Phpml/Tokenization/WordTokenizerTest.php | 3 +- 58 files changed, 251 insertions(+), 253 deletions(-) diff --git a/composer.json b/composer.json index 97841547..88043ddf 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "type": "library", "description": "PHP-ML - Machine Learning library for PHP", "license": "MIT", - "keywords": ["machine learning","pattern recognition","neural network","computational learning theory","artificial intelligence"], + "keywords": ["machine learning","pattern recognition","neural network","computational learning theory","artificial intelligence","data science","feature extraction"], "homepage": "https://github.com/php-ai/php-ml", "authors": [ { @@ -20,7 +20,7 @@ "php": ">=7.0.0" }, "require-dev": { - "phpunit/phpunit": "^5.5" + "phpunit/phpunit": "^6.0" }, "config": { "bin-dir": "bin" diff --git a/composer.lock b/composer.lock index 27f18291..424fd17b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "471cccec358c6643fd2526258b91a0ba", - "content-hash": "be926d8a68fbc47e08c64340c062a392", + "content-hash": "626dba6a3c956ec66be5958c37001a67", "packages": [], "packages-dev": [ { @@ -60,20 +59,20 @@ "constructor", "instantiate" ], - "time": "2015-06-14 21:17:01" + "time": "2015-06-14T21:17:01+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.5.4", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "ea74994a3dc7f8d2f65a06009348f2d63c81e61f" + "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/ea74994a3dc7f8d2f65a06009348f2d63c81e61f", - "reference": "ea74994a3dc7f8d2f65a06009348f2d63c81e61f", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/5a5a9fc8025a08d8919be87d6884d5a92520cefe", + "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe", "shasum": "" }, "require": { @@ -102,7 +101,7 @@ "object", "object graph" ], - "time": "2016-09-16 13:37:59" + "time": "2017-01-26T22:05:40+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -156,20 +155,20 @@ "reflection", "static analysis" ], - "time": "2015-12-27 11:43:31" + "time": "2015-12-27T11:43:31+00:00" }, { "name": "phpdocumentor/reflection-docblock", - "version": "3.1.0", + "version": "3.1.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "9270140b940ff02e58ec577c237274e92cd40cdd" + "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9270140b940ff02e58ec577c237274e92cd40cdd", - "reference": "9270140b940ff02e58ec577c237274e92cd40cdd", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e", + "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e", "shasum": "" }, "require": { @@ -201,20 +200,20 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2016-06-10 09:48:41" + "time": "2016-09-30T07:12:33+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "0.2", + "version": "0.2.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443" + "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b39c7a5b194f9ed7bd0dd345c751007a41862443", - "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", + "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", "shasum": "" }, "require": { @@ -248,20 +247,20 @@ "email": "me@mikevanriel.com" } ], - "time": "2016-06-10 07:14:17" + "time": "2016-11-25T06:54:22+00:00" }, { "name": "phpspec/prophecy", - "version": "v1.6.1", + "version": "v1.6.2", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "58a8137754bc24b25740d4281399a4a3596058e0" + "reference": "6c52c2722f8460122f96f86346600e1077ce22cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/58a8137754bc24b25740d4281399a4a3596058e0", - "reference": "58a8137754bc24b25740d4281399a4a3596058e0", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/6c52c2722f8460122f96f86346600e1077ce22cb", + "reference": "6c52c2722f8460122f96f86346600e1077ce22cb", "shasum": "" }, "require": { @@ -269,10 +268,11 @@ "php": "^5.3|^7.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", "sebastian/comparator": "^1.1", - "sebastian/recursion-context": "^1.0" + "sebastian/recursion-context": "^1.0|^2.0" }, "require-dev": { - "phpspec/phpspec": "^2.0" + "phpspec/phpspec": "^2.0", + "phpunit/phpunit": "^4.8 || ^5.6.5" }, "type": "library", "extra": { @@ -310,44 +310,43 @@ "spy", "stub" ], - "time": "2016-06-07 08:13:47" + "time": "2016-11-21T14:58:47+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "4.0.1", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "5f3f7e736d6319d5f1fc402aff8b026da26709a3" + "reference": "e7d7a4acca58e45bdfd00221563d131cfb04ba96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5f3f7e736d6319d5f1fc402aff8b026da26709a3", - "reference": "5f3f7e736d6319d5f1fc402aff8b026da26709a3", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/e7d7a4acca58e45bdfd00221563d131cfb04ba96", + "reference": "e7d7a4acca58e45bdfd00221563d131cfb04ba96", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "phpunit/php-file-iterator": "~1.3", - "phpunit/php-text-template": "~1.2", + "php": "^7.0", + "phpunit/php-file-iterator": "^1.3", + "phpunit/php-text-template": "^1.2", "phpunit/php-token-stream": "^1.4.2", - "sebastian/code-unit-reverse-lookup": "~1.0", - "sebastian/environment": "^1.3.2 || ^2.0", - "sebastian/version": "~1.0|~2.0" + "sebastian/code-unit-reverse-lookup": "^1.0", + "sebastian/environment": "^2.0", + "sebastian/version": "^2.0" }, "require-dev": { - "ext-xdebug": ">=2.1.4", - "phpunit/phpunit": "^5.4" + "ext-xdebug": "^2.5", + "phpunit/phpunit": "^6.0" }, "suggest": { "ext-dom": "*", - "ext-xdebug": ">=2.4.0", "ext-xmlwriter": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0.x-dev" + "dev-master": "5.0.x-dev" } }, "autoload": { @@ -373,20 +372,20 @@ "testing", "xunit" ], - "time": "2016-07-26 14:39:29" + "time": "2017-02-02T10:35:41+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "1.4.1", + "version": "1.4.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", - "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", "shasum": "" }, "require": { @@ -420,7 +419,7 @@ "filesystem", "iterator" ], - "time": "2015-06-21 13:08:43" + "time": "2016-10-03T07:40:28+00:00" }, { "name": "phpunit/php-text-template", @@ -461,7 +460,7 @@ "keywords": [ "template" ], - "time": "2015-06-21 13:50:34" + "time": "2015-06-21T13:50:34+00:00" }, { "name": "phpunit/php-timer", @@ -505,20 +504,20 @@ "keywords": [ "timer" ], - "time": "2016-05-12 18:03:57" + "time": "2016-05-12T18:03:57+00:00" }, { "name": "phpunit/php-token-stream", - "version": "1.4.8", + "version": "1.4.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" + "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", - "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3b402f65a4cc90abf6e1104e388b896ce209631b", + "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b", "shasum": "" }, "require": { @@ -554,20 +553,20 @@ "keywords": [ "tokenizer" ], - "time": "2015-09-15 10:49:45" + "time": "2016-11-15T14:06:22+00:00" }, { "name": "phpunit/phpunit", - "version": "5.5.5", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a57126dc681b08289fef6ac96a48e30656f84350" + "reference": "93a57ef4b0754737ac73604c5f51abf675d925d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a57126dc681b08289fef6ac96a48e30656f84350", - "reference": "a57126dc681b08289fef6ac96a48e30656f84350", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/93a57ef4b0754737ac73604c5f51abf675d925d8", + "reference": "93a57ef4b0754737ac73604c5f51abf675d925d8", "shasum": "" }, "require": { @@ -576,34 +575,33 @@ "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", - "myclabs/deep-copy": "~1.3", - "php": "^5.6 || ^7.0", - "phpspec/prophecy": "^1.3.1", - "phpunit/php-code-coverage": "^4.0.1", - "phpunit/php-file-iterator": "~1.4", - "phpunit/php-text-template": "~1.2", + "myclabs/deep-copy": "^1.3", + "php": "^7.0", + "phpspec/prophecy": "^1.6.2", + "phpunit/php-code-coverage": "^5.0", + "phpunit/php-file-iterator": "^1.4", + "phpunit/php-text-template": "^1.2", "phpunit/php-timer": "^1.0.6", - "phpunit/phpunit-mock-objects": "^3.2", - "sebastian/comparator": "~1.1", - "sebastian/diff": "~1.2", - "sebastian/environment": "^1.3 || ^2.0", - "sebastian/exporter": "~1.2", - "sebastian/global-state": "~1.0", - "sebastian/object-enumerator": "~1.0", - "sebastian/resource-operations": "~1.0", - "sebastian/version": "~1.0|~2.0", - "symfony/yaml": "~2.1|~3.0" + "phpunit/phpunit-mock-objects": "^4.0", + "sebastian/comparator": "^1.2.4", + "sebastian/diff": "^1.2", + "sebastian/environment": "^2.0", + "sebastian/exporter": "^2.0", + "sebastian/global-state": "^1.1", + "sebastian/object-enumerator": "^2.0", + "sebastian/resource-operations": "^1.0", + "sebastian/version": "^2.0" }, "conflict": { - "phpdocumentor/reflection-docblock": "3.0.2" + "phpdocumentor/reflection-docblock": "3.0.2", + "phpunit/dbunit": "<3.0" }, "require-dev": { "ext-pdo": "*" }, "suggest": { - "ext-tidy": "*", "ext-xdebug": "*", - "phpunit/php-invoker": "~1.1" + "phpunit/php-invoker": "^1.1" }, "bin": [ "phpunit" @@ -611,7 +609,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.5.x-dev" + "dev-master": "6.0.x-dev" } }, "autoload": { @@ -637,33 +635,33 @@ "testing", "xunit" ], - "time": "2016-09-21 14:40:13" + "time": "2017-02-03T11:40:20+00:00" }, { "name": "phpunit/phpunit-mock-objects", - "version": "3.2.7", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "546898a2c0c356ef2891b39dd7d07f5d82c8ed0a" + "reference": "3819745c44f3aff9518fd655f320c4535d541af7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/546898a2c0c356ef2891b39dd7d07f5d82c8ed0a", - "reference": "546898a2c0c356ef2891b39dd7d07f5d82c8ed0a", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/3819745c44f3aff9518fd655f320c4535d541af7", + "reference": "3819745c44f3aff9518fd655f320c4535d541af7", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", - "php": "^5.6 || ^7.0", + "php": "^7.0", "phpunit/php-text-template": "^1.2", - "sebastian/exporter": "^1.2" + "sebastian/exporter": "^2.0" }, "conflict": { - "phpunit/phpunit": "<5.4.0" + "phpunit/phpunit": "<6.0" }, "require-dev": { - "phpunit/phpunit": "^5.4" + "phpunit/phpunit": "^6.0" }, "suggest": { "ext-soap": "*" @@ -671,7 +669,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2.x-dev" + "dev-master": "4.0.x-dev" } }, "autoload": { @@ -696,7 +694,7 @@ "mock", "xunit" ], - "time": "2016-09-06 16:07:45" + "time": "2017-02-02T10:36:38+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -741,26 +739,26 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2016-02-13 06:45:14" + "time": "2016-02-13T06:45:14+00:00" }, { "name": "sebastian/comparator", - "version": "1.2.0", + "version": "1.2.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", - "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", "shasum": "" }, "require": { "php": ">=5.3.3", "sebastian/diff": "~1.2", - "sebastian/exporter": "~1.2" + "sebastian/exporter": "~1.2 || ~2.0" }, "require-dev": { "phpunit/phpunit": "~4.4" @@ -805,7 +803,7 @@ "compare", "equality" ], - "time": "2015-07-26 15:48:44" + "time": "2017-01-29T09:50:25+00:00" }, { "name": "sebastian/diff", @@ -857,32 +855,32 @@ "keywords": [ "diff" ], - "time": "2015-12-08 07:14:41" + "time": "2015-12-08T07:14:41+00:00" }, { "name": "sebastian/environment", - "version": "1.3.8", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", - "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": "^5.6 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "^4.8 || ^5.0" + "phpunit/phpunit": "^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -907,25 +905,25 @@ "environment", "hhvm" ], - "time": "2016-08-18 05:49:44" + "time": "2016-11-26T07:53:53+00:00" }, { "name": "sebastian/exporter", - "version": "1.2.2", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", - "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", "shasum": "" }, "require": { "php": ">=5.3.3", - "sebastian/recursion-context": "~1.0" + "sebastian/recursion-context": "~2.0" }, "require-dev": { "ext-mbstring": "*", @@ -934,7 +932,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -974,7 +972,7 @@ "export", "exporter" ], - "time": "2016-06-17 09:04:28" + "time": "2016-11-19T08:54:04+00:00" }, { "name": "sebastian/global-state", @@ -1025,25 +1023,25 @@ "keywords": [ "global state" ], - "time": "2015-10-12 03:26:01" + "time": "2015-10-12T03:26:01+00:00" }, { "name": "sebastian/object-enumerator", - "version": "1.0.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "d4ca2fb70344987502567bc50081c03e6192fb26" + "reference": "96f8a3f257b69e8128ad74d3a7fd464bcbaa3b35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/d4ca2fb70344987502567bc50081c03e6192fb26", - "reference": "d4ca2fb70344987502567bc50081c03e6192fb26", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/96f8a3f257b69e8128ad74d3a7fd464bcbaa3b35", + "reference": "96f8a3f257b69e8128ad74d3a7fd464bcbaa3b35", "shasum": "" }, "require": { "php": ">=5.6", - "sebastian/recursion-context": "~1.0" + "sebastian/recursion-context": "~2.0" }, "require-dev": { "phpunit/phpunit": "~5" @@ -1051,7 +1049,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -1071,20 +1069,20 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2016-01-28 13:25:10" + "time": "2016-11-19T07:35:10+00:00" }, { "name": "sebastian/recursion-context", - "version": "1.0.2", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "913401df809e99e4f47b27cdd781f4a258d58791" + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791", - "reference": "913401df809e99e4f47b27cdd781f4a258d58791", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", "shasum": "" }, "require": { @@ -1096,7 +1094,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -1124,7 +1122,7 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2015-11-11 19:50:13" + "time": "2016-11-19T07:33:16+00:00" }, { "name": "sebastian/resource-operations", @@ -1166,20 +1164,20 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2015-07-28 20:34:47" + "time": "2015-07-28T20:34:47+00:00" }, { "name": "sebastian/version", - "version": "2.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5" + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5", - "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", "shasum": "" }, "require": { @@ -1209,73 +1207,24 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-02-04 12:56:52" - }, - { - "name": "symfony/yaml", - "version": "v3.1.4", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "f291ed25eb1435bddbe8a96caaef16469c2a092d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/f291ed25eb1435bddbe8a96caaef16469c2a092d", - "reference": "f291ed25eb1435bddbe8a96caaef16469c2a092d", - "shasum": "" - }, - "require": { - "php": ">=5.5.9" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Yaml Component", - "homepage": "https://symfony.com", - "time": "2016-09-02 02:12:52" + "time": "2016-10-03T07:35:21+00:00" }, { "name": "webmozart/assert", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "bb2d123231c095735130cc8f6d31385a44c7b308" + "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/bb2d123231c095735130cc8f6d31385a44c7b308", - "reference": "bb2d123231c095735130cc8f6d31385a44c7b308", + "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f", + "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f", "shasum": "" }, "require": { - "php": "^5.3.3|^7.0" + "php": "^5.3.3 || ^7.0" }, "require-dev": { "phpunit/phpunit": "^4.6", @@ -1284,7 +1233,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.3-dev" } }, "autoload": { @@ -1308,7 +1257,7 @@ "check", "validate" ], - "time": "2016-08-09 15:02:57" + "time": "2016-11-23T20:04:58+00:00" } ], "aliases": [], diff --git a/src/Phpml/Exception/SerializeException.php b/src/Phpml/Exception/SerializeException.php index 52620e98..70e68925 100644 --- a/src/Phpml/Exception/SerializeException.php +++ b/src/Phpml/Exception/SerializeException.php @@ -26,5 +26,4 @@ public static function cantSerialize(string $classname) { return new self(sprintf('Class "%s" can not be serialized.', $classname)); } - } diff --git a/tests/Phpml/Association/AprioriTest.php b/tests/Phpml/Association/AprioriTest.php index 60069b29..65d5ea5f 100644 --- a/tests/Phpml/Association/AprioriTest.php +++ b/tests/Phpml/Association/AprioriTest.php @@ -6,8 +6,9 @@ use Phpml\Association\Apriori; use Phpml\ModelManager; +use PHPUnit\Framework\TestCase; -class AprioriTest extends \PHPUnit_Framework_TestCase +class AprioriTest extends TestCase { private $sampleGreek = [ ['alpha', 'beta', 'epsilon'], diff --git a/tests/Phpml/Classification/DecisionTreeTest.php b/tests/Phpml/Classification/DecisionTreeTest.php index 7ec25bad..db2d8106 100644 --- a/tests/Phpml/Classification/DecisionTreeTest.php +++ b/tests/Phpml/Classification/DecisionTreeTest.php @@ -6,8 +6,9 @@ use Phpml\Classification\DecisionTree; use Phpml\ModelManager; +use PHPUnit\Framework\TestCase; -class DecisionTreeTest extends \PHPUnit_Framework_TestCase +class DecisionTreeTest extends TestCase { private $data = [ ['sunny', 85, 85, 'false', 'Dont_play' ], @@ -73,7 +74,6 @@ public function testSaveAndRestore() $restoredClassifier = $modelManager->restoreFromFile($filepath); $this->assertEquals($classifier, $restoredClassifier); $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); - } public function testTreeDepth() diff --git a/tests/Phpml/Classification/KNearestNeighborsTest.php b/tests/Phpml/Classification/KNearestNeighborsTest.php index 9824e5d7..ea9db77a 100644 --- a/tests/Phpml/Classification/KNearestNeighborsTest.php +++ b/tests/Phpml/Classification/KNearestNeighborsTest.php @@ -7,8 +7,9 @@ use Phpml\Classification\KNearestNeighbors; use Phpml\Math\Distance\Chebyshev; use Phpml\ModelManager; +use PHPUnit\Framework\TestCase; -class KNearestNeighborsTest extends \PHPUnit_Framework_TestCase +class KNearestNeighborsTest extends TestCase { public function testPredictSingleSampleWithDefaultK() { diff --git a/tests/Phpml/Classification/NaiveBayesTest.php b/tests/Phpml/Classification/NaiveBayesTest.php index 1f66ec83..9b2171af 100644 --- a/tests/Phpml/Classification/NaiveBayesTest.php +++ b/tests/Phpml/Classification/NaiveBayesTest.php @@ -6,8 +6,9 @@ use Phpml\Classification\NaiveBayes; use Phpml\ModelManager; +use PHPUnit\Framework\TestCase; -class NaiveBayesTest extends \PHPUnit_Framework_TestCase +class NaiveBayesTest extends TestCase { public function testPredictSingleSample() { @@ -44,7 +45,6 @@ public function testPredictArrayOfSamples() $testSamples = [[1, 1, 6], [5, 1, 1]]; $testLabels = ['d', 'a']; $this->assertEquals($testLabels, $classifier->predict($testSamples)); - } public function testSaveAndRestore() @@ -68,5 +68,4 @@ public function testSaveAndRestore() $this->assertEquals($classifier, $restoredClassifier); $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); } - } diff --git a/tests/Phpml/Classification/SVCTest.php b/tests/Phpml/Classification/SVCTest.php index 34111b40..89c27e3e 100644 --- a/tests/Phpml/Classification/SVCTest.php +++ b/tests/Phpml/Classification/SVCTest.php @@ -7,8 +7,9 @@ use Phpml\Classification\SVC; use Phpml\SupportVectorMachine\Kernel; use Phpml\ModelManager; +use PHPUnit\Framework\TestCase; -class SVCTest extends \PHPUnit_Framework_TestCase +class SVCTest extends TestCase { public function testPredictSingleSampleWithLinearKernel() { diff --git a/tests/Phpml/Clustering/DBSCANTest.php b/tests/Phpml/Clustering/DBSCANTest.php index b631508b..2d959acd 100644 --- a/tests/Phpml/Clustering/DBSCANTest.php +++ b/tests/Phpml/Clustering/DBSCANTest.php @@ -5,8 +5,9 @@ namespace tests\Clustering; use Phpml\Clustering\DBSCAN; +use PHPUnit\Framework\TestCase; -class DBSCANTest extends \PHPUnit_Framework_TestCase +class DBSCANTest extends TestCase { public function testDBSCANSamplesClustering() { diff --git a/tests/Phpml/Clustering/FuzzyCMeansTest.php b/tests/Phpml/Clustering/FuzzyCMeansTest.php index 56d3c63c..85285b27 100644 --- a/tests/Phpml/Clustering/FuzzyCMeansTest.php +++ b/tests/Phpml/Clustering/FuzzyCMeansTest.php @@ -5,8 +5,9 @@ namespace tests\Clustering; use Phpml\Clustering\FuzzyCMeans; +use PHPUnit\Framework\TestCase; -class FuzzyCMeansTest extends \PHPUnit_Framework_TestCase +class FuzzyCMeansTest extends TestCase { public function testFCMSamplesClustering() { diff --git a/tests/Phpml/Clustering/KMeansTest.php b/tests/Phpml/Clustering/KMeansTest.php index bd954bed..c25306b9 100644 --- a/tests/Phpml/Clustering/KMeansTest.php +++ b/tests/Phpml/Clustering/KMeansTest.php @@ -5,8 +5,9 @@ namespace tests\Clustering; use Phpml\Clustering\KMeans; +use PHPUnit\Framework\TestCase; -class KMeansTest extends \PHPUnit_Framework_TestCase +class KMeansTest extends TestCase { public function testKMeansSamplesClustering() { diff --git a/tests/Phpml/CrossValidation/RandomSplitTest.php b/tests/Phpml/CrossValidation/RandomSplitTest.php index d6f46816..b2717fd7 100644 --- a/tests/Phpml/CrossValidation/RandomSplitTest.php +++ b/tests/Phpml/CrossValidation/RandomSplitTest.php @@ -6,8 +6,9 @@ use Phpml\CrossValidation\RandomSplit; use Phpml\Dataset\ArrayDataset; +use PHPUnit\Framework\TestCase; -class RandomSplitTest extends \PHPUnit_Framework_TestCase +class RandomSplitTest extends TestCase { /** * @expectedException \Phpml\Exception\InvalidArgumentException diff --git a/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php b/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php index afe3d81a..ef07398f 100644 --- a/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php +++ b/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php @@ -6,8 +6,9 @@ use Phpml\CrossValidation\StratifiedRandomSplit; use Phpml\Dataset\ArrayDataset; +use PHPUnit\Framework\TestCase; -class StratifiedRandomSplitTest extends \PHPUnit_Framework_TestCase +class StratifiedRandomSplitTest extends TestCase { public function testDatasetStratifiedRandomSplitWithEvenDistribution() { diff --git a/tests/Phpml/Dataset/ArrayDatasetTest.php b/tests/Phpml/Dataset/ArrayDatasetTest.php index bd19bcbe..0b13b51d 100644 --- a/tests/Phpml/Dataset/ArrayDatasetTest.php +++ b/tests/Phpml/Dataset/ArrayDatasetTest.php @@ -5,8 +5,9 @@ namespace tests\Phpml\Dataset; use Phpml\Dataset\ArrayDataset; +use PHPUnit\Framework\TestCase; -class ArrayDatasetTest extends \PHPUnit_Framework_TestCase +class ArrayDatasetTest extends TestCase { /** * @expectedException \Phpml\Exception\InvalidArgumentException diff --git a/tests/Phpml/Dataset/CsvDatasetTest.php b/tests/Phpml/Dataset/CsvDatasetTest.php index 65c589ae..44a112b2 100644 --- a/tests/Phpml/Dataset/CsvDatasetTest.php +++ b/tests/Phpml/Dataset/CsvDatasetTest.php @@ -5,8 +5,9 @@ namespace tests\Phpml\Dataset; use Phpml\Dataset\CsvDataset; +use PHPUnit\Framework\TestCase; -class CsvDatasetTest extends \PHPUnit_Framework_TestCase +class CsvDatasetTest extends TestCase { /** * @expectedException \Phpml\Exception\FileException diff --git a/tests/Phpml/Dataset/Demo/GlassDatasetTest.php b/tests/Phpml/Dataset/Demo/GlassDatasetTest.php index 9755b5de..5e3f94ca 100644 --- a/tests/Phpml/Dataset/Demo/GlassDatasetTest.php +++ b/tests/Phpml/Dataset/Demo/GlassDatasetTest.php @@ -5,8 +5,9 @@ namespace tests\Phpml\Dataset\Demo; use Phpml\Dataset\Demo\GlassDataset; +use PHPUnit\Framework\TestCase; -class GlassDatasetTest extends \PHPUnit_Framework_TestCase +class GlassDatasetTest extends TestCase { public function testLoadingWineDataset() { diff --git a/tests/Phpml/Dataset/Demo/IrisDatasetTest.php b/tests/Phpml/Dataset/Demo/IrisDatasetTest.php index 3ee67d0f..18ad647a 100644 --- a/tests/Phpml/Dataset/Demo/IrisDatasetTest.php +++ b/tests/Phpml/Dataset/Demo/IrisDatasetTest.php @@ -5,8 +5,9 @@ namespace tests\Phpml\Dataset\Demo; use Phpml\Dataset\Demo\IrisDataset; +use PHPUnit\Framework\TestCase; -class IrisDatasetTest extends \PHPUnit_Framework_TestCase +class IrisDatasetTest extends TestCase { public function testLoadingIrisDataset() { diff --git a/tests/Phpml/Dataset/Demo/WineDatasetTest.php b/tests/Phpml/Dataset/Demo/WineDatasetTest.php index dbac4e03..a79ed8ce 100644 --- a/tests/Phpml/Dataset/Demo/WineDatasetTest.php +++ b/tests/Phpml/Dataset/Demo/WineDatasetTest.php @@ -5,8 +5,9 @@ namespace tests\Phpml\Dataset\Demo; use Phpml\Dataset\Demo\WineDataset; +use PHPUnit\Framework\TestCase; -class WineDatasetTest extends \PHPUnit_Framework_TestCase +class WineDatasetTest extends TestCase { public function testLoadingWineDataset() { diff --git a/tests/Phpml/Dataset/FilesDatasetTest.php b/tests/Phpml/Dataset/FilesDatasetTest.php index 49f0e206..d12477fa 100644 --- a/tests/Phpml/Dataset/FilesDatasetTest.php +++ b/tests/Phpml/Dataset/FilesDatasetTest.php @@ -5,8 +5,9 @@ namespace tests\Phpml\Dataset; use Phpml\Dataset\FilesDataset; +use PHPUnit\Framework\TestCase; -class FilesDatasetTest extends \PHPUnit_Framework_TestCase +class FilesDatasetTest extends TestCase { /** * @expectedException \Phpml\Exception\DatasetException diff --git a/tests/Phpml/FeatureExtraction/StopWordsTest.php b/tests/Phpml/FeatureExtraction/StopWordsTest.php index feed8925..dd0a1859 100644 --- a/tests/Phpml/FeatureExtraction/StopWordsTest.php +++ b/tests/Phpml/FeatureExtraction/StopWordsTest.php @@ -5,8 +5,9 @@ namespace tests\Phpml\FeatureExtraction; use Phpml\FeatureExtraction\StopWords; +use PHPUnit\Framework\TestCase; -class StopWordsTest extends \PHPUnit_Framework_TestCase +class StopWordsTest extends TestCase { public function testCustomStopWords() { diff --git a/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php b/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php index fd9bb1c3..90aa107b 100644 --- a/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php +++ b/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php @@ -5,8 +5,9 @@ namespace tests\Phpml\FeatureExtraction; use Phpml\FeatureExtraction\TfIdfTransformer; +use PHPUnit\Framework\TestCase; -class TfIdfTransformerTest extends \PHPUnit_Framework_TestCase +class TfIdfTransformerTest extends TestCase { public function testTfIdfTransformation() { diff --git a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php index 2a49b875..a25d5a5a 100644 --- a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php +++ b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php @@ -7,8 +7,9 @@ use Phpml\FeatureExtraction\StopWords; use Phpml\FeatureExtraction\TokenCountVectorizer; use Phpml\Tokenization\WhitespaceTokenizer; +use PHPUnit\Framework\TestCase; -class TokenCountVectorizerTest extends \PHPUnit_Framework_TestCase +class TokenCountVectorizerTest extends TestCase { public function testTransformationWithWhitespaceTokenizer() { diff --git a/tests/Phpml/Math/Distance/ChebyshevTest.php b/tests/Phpml/Math/Distance/ChebyshevTest.php index c35432d7..8806c747 100644 --- a/tests/Phpml/Math/Distance/ChebyshevTest.php +++ b/tests/Phpml/Math/Distance/ChebyshevTest.php @@ -5,8 +5,9 @@ namespace tests\Phpml\Metric; use Phpml\Math\Distance\Chebyshev; +use PHPUnit\Framework\TestCase; -class ChebyshevTest extends \PHPUnit_Framework_TestCase +class ChebyshevTest extends TestCase { /** * @var Chebyshev diff --git a/tests/Phpml/Math/Distance/EuclideanTest.php b/tests/Phpml/Math/Distance/EuclideanTest.php index b3a1f319..38cb6b75 100644 --- a/tests/Phpml/Math/Distance/EuclideanTest.php +++ b/tests/Phpml/Math/Distance/EuclideanTest.php @@ -5,8 +5,9 @@ namespace tests\Phpml\Metric; use Phpml\Math\Distance\Euclidean; +use PHPUnit\Framework\TestCase; -class EuclideanTest extends \PHPUnit_Framework_TestCase +class EuclideanTest extends TestCase { /** * @var Euclidean diff --git a/tests/Phpml/Math/Distance/ManhattanTest.php b/tests/Phpml/Math/Distance/ManhattanTest.php index 07c65109..0e22a155 100644 --- a/tests/Phpml/Math/Distance/ManhattanTest.php +++ b/tests/Phpml/Math/Distance/ManhattanTest.php @@ -5,8 +5,9 @@ namespace tests\Phpml\Metric; use Phpml\Math\Distance\Manhattan; +use PHPUnit\Framework\TestCase; -class ManhattanTest extends \PHPUnit_Framework_TestCase +class ManhattanTest extends TestCase { /** * @var Manhattan diff --git a/tests/Phpml/Math/Distance/MinkowskiTest.php b/tests/Phpml/Math/Distance/MinkowskiTest.php index 3e8afdb3..d8931d0d 100644 --- a/tests/Phpml/Math/Distance/MinkowskiTest.php +++ b/tests/Phpml/Math/Distance/MinkowskiTest.php @@ -5,8 +5,9 @@ namespace tests\Phpml\Metric; use Phpml\Math\Distance\Minkowski; +use PHPUnit\Framework\TestCase; -class MinkowskiTest extends \PHPUnit_Framework_TestCase +class MinkowskiTest extends TestCase { /** * @var Minkowski diff --git a/tests/Phpml/Math/Kernel/RBFTest.php b/tests/Phpml/Math/Kernel/RBFTest.php index 4eea13e1..80768274 100644 --- a/tests/Phpml/Math/Kernel/RBFTest.php +++ b/tests/Phpml/Math/Kernel/RBFTest.php @@ -5,8 +5,9 @@ namespace test\Phpml\Math\Kernel; use Phpml\Math\Kernel\RBF; +use PHPUnit\Framework\TestCase; -class RBFTest extends \PHPUnit_Framework_TestCase +class RBFTest extends TestCase { public function testComputeRBFKernelFunction() { diff --git a/tests/Phpml/Math/MatrixTest.php b/tests/Phpml/Math/MatrixTest.php index 442ef0ce..b4adc763 100644 --- a/tests/Phpml/Math/MatrixTest.php +++ b/tests/Phpml/Math/MatrixTest.php @@ -5,8 +5,9 @@ namespace tests\Phpml\Math; use Phpml\Math\Matrix; +use PHPUnit\Framework\TestCase; -class MatrixTest extends \PHPUnit_Framework_TestCase +class MatrixTest extends TestCase { /** * @expectedException \Phpml\Exception\InvalidArgumentException diff --git a/tests/Phpml/Math/ProductTest.php b/tests/Phpml/Math/ProductTest.php index 7fc525cc..50ced9ee 100644 --- a/tests/Phpml/Math/ProductTest.php +++ b/tests/Phpml/Math/ProductTest.php @@ -5,8 +5,9 @@ namespace tests\Phpml\Math; use Phpml\Math\Product; +use PHPUnit\Framework\TestCase; -class ProductTest extends \PHPUnit_Framework_TestCase +class ProductTest extends TestCase { public function testScalarProduct() { diff --git a/tests/Phpml/Math/SetTest.php b/tests/Phpml/Math/SetTest.php index c6548c79..101763c5 100644 --- a/tests/Phpml/Math/SetTest.php +++ b/tests/Phpml/Math/SetTest.php @@ -1,10 +1,13 @@ -assertEquals($regression, $restoredRegression); $this->assertEquals($predicted, $restoredRegression->predict($testSamples)); } - } diff --git a/tests/Phpml/Regression/SVRTest.php b/tests/Phpml/Regression/SVRTest.php index 17bb66ea..d3349737 100644 --- a/tests/Phpml/Regression/SVRTest.php +++ b/tests/Phpml/Regression/SVRTest.php @@ -7,8 +7,9 @@ use Phpml\Regression\SVR; use Phpml\SupportVectorMachine\Kernel; use Phpml\ModelManager; +use PHPUnit\Framework\TestCase; -class SVRTest extends \PHPUnit_Framework_TestCase +class SVRTest extends TestCase { public function testPredictSingleFeatureSamples() { @@ -38,7 +39,6 @@ public function testPredictMultiFeaturesSamples() public function testSaveAndRestore() { - $samples = [[60], [61], [62], [63], [65]]; $targets = [3.1, 3.6, 3.8, 4, 4.1]; @@ -57,5 +57,4 @@ public function testSaveAndRestore() $this->assertEquals($regression, $restoredRegression); $this->assertEquals($predicted, $restoredRegression->predict($testSamples)); } - } diff --git a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php b/tests/Phpml/SupportVectorMachine/DataTransformerTest.php index cb0ee526..33d52c0b 100644 --- a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php +++ b/tests/Phpml/SupportVectorMachine/DataTransformerTest.php @@ -5,8 +5,9 @@ namespace tests\SupportVectorMachine; use Phpml\SupportVectorMachine\DataTransformer; +use PHPUnit\Framework\TestCase; -class DataTransformerTest extends \PHPUnit_Framework_TestCase +class DataTransformerTest extends TestCase { public function testTransformDatasetToTrainingSet() { diff --git a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php index 4b4e03bd..ca9f299b 100644 --- a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php @@ -7,8 +7,9 @@ use Phpml\SupportVectorMachine\Kernel; use Phpml\SupportVectorMachine\SupportVectorMachine; use Phpml\SupportVectorMachine\Type; +use PHPUnit\Framework\TestCase; -class SupportVectorMachineTest extends \PHPUnit_Framework_TestCase +class SupportVectorMachineTest extends TestCase { public function testTrainCSVCModelWithLinearKernel() { diff --git a/tests/Phpml/Tokenization/WhitespaceTokenizerTest.php b/tests/Phpml/Tokenization/WhitespaceTokenizerTest.php index 8f2daf38..1133835c 100644 --- a/tests/Phpml/Tokenization/WhitespaceTokenizerTest.php +++ b/tests/Phpml/Tokenization/WhitespaceTokenizerTest.php @@ -5,8 +5,9 @@ namespace tests\Tokenization; use Phpml\Tokenization\WhitespaceTokenizer; +use PHPUnit\Framework\TestCase; -class WhitespaceTokenizerTest extends \PHPUnit_Framework_TestCase +class WhitespaceTokenizerTest extends TestCase { public function testTokenizationOnAscii() { diff --git a/tests/Phpml/Tokenization/WordTokenizerTest.php b/tests/Phpml/Tokenization/WordTokenizerTest.php index 959adfb9..3a6abba3 100644 --- a/tests/Phpml/Tokenization/WordTokenizerTest.php +++ b/tests/Phpml/Tokenization/WordTokenizerTest.php @@ -5,8 +5,9 @@ namespace tests\Tokenization; use Phpml\Tokenization\WordTokenizer; +use PHPUnit\Framework\TestCase; -class WordTokenizerTest extends \PHPUnit_Framework_TestCase +class WordTokenizerTest extends TestCase { public function testTokenizationOnAscii() { From b7c99835243bc5da9999a0b5fdef2dfecd605903 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 3 Feb 2017 17:48:15 +0100 Subject: [PATCH 148/328] Do not requre file to exist for model manager --- src/Phpml/ModelManager.php | 20 +++++++++++--------- tests/Phpml/ModelManagerTest.php | 22 +++++----------------- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/src/Phpml/ModelManager.php b/src/Phpml/ModelManager.php index 2ba8cc25..c03d0ed2 100644 --- a/src/Phpml/ModelManager.php +++ b/src/Phpml/ModelManager.php @@ -4,25 +4,26 @@ namespace Phpml; -use Phpml\Estimator; use Phpml\Exception\SerializeException; use Phpml\Exception\FileException; class ModelManager { /** - * @param Estimator $object - * @param string $filepath + * @param Estimator $estimator + * @param string $filepath + * @throws FileException + * @throws SerializeException */ - public function saveToFile(Estimator $object, string $filepath) + public function saveToFile(Estimator $estimator, string $filepath) { - if (!file_exists($filepath) || !is_writable(dirname($filepath))) { + if (!is_writable(dirname($filepath))) { throw FileException::cantSaveFile(basename($filepath)); } - $serialized = serialize($object); + $serialized = serialize($estimator); if (empty($serialized)) { - throw SerializeException::cantSerialize(get_type($object)); + throw SerializeException::cantSerialize(get_type($estimator)); } $result = file_put_contents($filepath, $serialized, LOCK_EX); @@ -33,10 +34,11 @@ public function saveToFile(Estimator $object, string $filepath) /** * @param string $filepath - * * @return Estimator + * @throws FileException + * @throws SerializeException */ - public function restoreFromFile(string $filepath) + public function restoreFromFile(string $filepath) : Estimator { if (!file_exists($filepath) || !is_readable($filepath)) { throw FileException::cantOpenFile(basename($filepath)); diff --git a/tests/Phpml/ModelManagerTest.php b/tests/Phpml/ModelManagerTest.php index e5d7f5e4..066aad14 100644 --- a/tests/Phpml/ModelManagerTest.php +++ b/tests/Phpml/ModelManagerTest.php @@ -12,27 +12,15 @@ class ModelManagerTest extends TestCase { public function testSaveAndRestore() { - $filename = 'test-save-to-file-'.rand(100, 999).'-'.uniqid(); - $filepath = tempnam(sys_get_temp_dir(), $filename); + $filename = uniqid(); + $filepath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $filename; - $obj = new LeastSquares(); + $estimator = new LeastSquares(); $modelManager = new ModelManager(); - $modelManager->saveToFile($obj, $filepath); + $modelManager->saveToFile($estimator, $filepath); $restored = $modelManager->restoreFromFile($filepath); - $this->assertEquals($obj, $restored); - } - - /** - * @expectedException \Phpml\Exception\FileException - */ - public function testSaveToWrongFile() - { - $filepath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'unexisting'; - - $obj = new LeastSquares(); - $modelManager = new ModelManager(); - $modelManager->saveToFile($obj, $filepath); + $this->assertEquals($estimator, $restored); } /** From 9536a363a23f14fd3e59c3d0bc50a4c1e616a893 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 3 Feb 2017 17:54:41 +0100 Subject: [PATCH 149/328] Prepare for next release --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 781d5fde..11b0807a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,15 @@ CHANGELOG This changelog references the relevant changes done in PHP-ML library. -* 0.2.2 (in plan/progress) +* 0.3.1 (in plan/progress) * feature [Regression] - SSE, SSTo, SSR - sum of the squared +* 0.3.0 (2017-02-04) + * feature [Persistency] - ModelManager - save and restore trained models by David Monllaó + * feature [Classification] - DecisionTree implementation by Mustafa Karabulut + * feature [Clustering] - Fuzzy C Means implementation by Mustafa Karabulut + * other small fixes and code styles refactors + * 0.2.1 (2016-11-20) * feature [Association] - Apriori algorithm implementation * bug [Metric] - division by zero From 72b25ffd42c36d33c92115d21c133c407dadb223 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sat, 4 Feb 2017 11:19:43 +0100 Subject: [PATCH 150/328] Add link to model manager in readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index bf33f905..c5843549 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,8 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * [Iris](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/demo/iris/) * [Wine](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/demo/wine/) * [Glass](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/demo/glass/) +* Models management + * [Persistency](http://php-ml.readthedocs.io/en/latest/machine-learning/model-manager/persistency/) * Math * [Distance](http://php-ml.readthedocs.io/en/latest/math/distance/) * [Matrix](http://php-ml.readthedocs.io/en/latest/math/matrix/) From 1d73503958b477a5d166e69f7df314db2660f537 Mon Sep 17 00:00:00 2001 From: Mustafa Karabulut Date: Tue, 7 Feb 2017 13:37:56 +0200 Subject: [PATCH 151/328] Ensemble Classifiers : Bagging and RandomForest (#36) * Fuzzy C-Means implementation * Update FuzzyCMeans * Rename FuzzyCMeans to FuzzyCMeans.php * Update NaiveBayes.php * Small fix applied to improve training performance array_unique is replaced with array_count_values+array_keys which is way faster * Revert "Small fix applied to improve training performance" This reverts commit c20253f16ac3e8c37d33ecaee28a87cc767e3b7f. * Revert "Revert "Small fix applied to improve training performance"" This reverts commit ea10e136c4c11b71609ccdcaf9999067e4be473e. * Revert "Small fix applied to improve training performance" This reverts commit c20253f16ac3e8c37d33ecaee28a87cc767e3b7f. * First DecisionTree implementation * Revert "First DecisionTree implementation" This reverts commit 4057a08679c26010c39040a48a3e6dad994a1a99. * DecisionTree * FCM Test * FCM Test * DecisionTree Test * Ensemble classifiers: Bagging and RandomForests * test * Fixes for conflicted files * Bagging and RandomForest ensemble algorithms * Changed unit test * Changed unit test * Changed unit test * Bagging and RandomForest ensemble algorithms * Baggging and RandomForest ensemble algorithms * Bagging and RandomForest ensemble algorithms RandomForest algorithm is improved with changes to original DecisionTree * Bagging and RandomForest ensemble algorithms * Slight fix about use of global Exception class * Fixed the error about wrong use of global Exception class * RandomForest code formatting --- src/Phpml/Classification/DecisionTree.php | 60 +++++- .../DecisionTree/DecisionTreeLeaf.php | 2 +- src/Phpml/Classification/Ensemble/Bagging.php | 198 ++++++++++++++++++ .../Classification/Ensemble/RandomForest.php | 89 ++++++++ .../Classification/Ensemble/BaggingTest.php | 127 +++++++++++ .../Ensemble/RandomForestTest.php | 38 ++++ 6 files changed, 507 insertions(+), 7 deletions(-) create mode 100644 src/Phpml/Classification/Ensemble/Bagging.php create mode 100644 src/Phpml/Classification/Ensemble/RandomForest.php create mode 100644 tests/Phpml/Classification/Ensemble/BaggingTest.php create mode 100644 tests/Phpml/Classification/Ensemble/RandomForestTest.php diff --git a/src/Phpml/Classification/DecisionTree.php b/src/Phpml/Classification/DecisionTree.php index 1a39cbe9..1a04802b 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -51,6 +51,11 @@ class DecisionTree implements Classifier */ public $actualDepth = 0; + /** + * @var int + */ + private $numUsableFeatures = 0; + /** * @param int $maxDepth */ @@ -144,15 +149,15 @@ protected function getBestSplit($records) $samples = array_combine($records, $this->preprocess($samples)); $bestGiniVal = 1; $bestSplit = null; - for ($i=0; $i<$this->featureCount; $i++) { + $features = $this->getSelectedFeatures(); + foreach ($features as $i) { $colValues = []; - $baseValue = null; foreach ($samples as $index => $row) { $colValues[$index] = $row[$i]; - if ($baseValue === null) { - $baseValue = $row[$i]; - } } + $counts = array_count_values($colValues); + arsort($counts); + $baseValue = key($counts); $gini = $this->getGiniIndex($baseValue, $colValues, $targets); if ($bestSplit == null || $bestGiniVal > $gini) { $split = new DecisionTreeLeaf(); @@ -167,6 +172,27 @@ protected function getBestSplit($records) return $bestSplit; } + /** + * @return array + */ + protected function getSelectedFeatures() + { + $allFeatures = range(0, $this->featureCount - 1); + if ($this->numUsableFeatures == 0) { + return $allFeatures; + } + + $numFeatures = $this->numUsableFeatures; + if ($numFeatures > $this->featureCount) { + $numFeatures = $this->featureCount; + } + shuffle($allFeatures); + $selectedFeatures = array_slice($allFeatures, 0, $numFeatures, false); + sort($selectedFeatures); + + return $selectedFeatures; + } + /** * @param string $baseValue * @param array $colValues @@ -248,6 +274,27 @@ protected function isCategoricalColumn(array $columnValues) return false; } + /** + * This method is used to set number of columns to be used + * when deciding a split at an internal node of the tree.
+ * If the value is given 0, then all features are used (default behaviour), + * otherwise the given value will be used as a maximum for number of columns + * randomly selected for each split operation. + * + * @param int $numFeatures + * @return $this + * @throws Exception + */ + public function setNumFeatures(int $numFeatures) + { + if ($numFeatures < 0) { + throw new \Exception("Selected column count should be greater or equal to zero"); + } + + $this->numUsableFeatures = $numFeatures; + return $this; + } + /** * @return string */ @@ -273,6 +320,7 @@ protected function predictSample(array $sample) $node = $node->rightLeaf; } } while ($node); - return $node->classValue; + + return $node ? $node->classValue : $this->labels[0]; } } diff --git a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php index d4289197..19938641 100644 --- a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -62,7 +62,7 @@ class DecisionTreeLeaf public function evaluate($record) { $recordField = $record[$this->columnIndex]; - if (preg_match("/^([<>=]{1,2})\s*(.*)/", $this->value, $matches)) { + if (is_string($this->value) && preg_match("/^([<>=]{1,2})\s*(.*)/", $this->value, $matches)) { $op = $matches[1]; $value= floatval($matches[2]); $recordField = strval($recordField); diff --git a/src/Phpml/Classification/Ensemble/Bagging.php b/src/Phpml/Classification/Ensemble/Bagging.php new file mode 100644 index 00000000..817869ef --- /dev/null +++ b/src/Phpml/Classification/Ensemble/Bagging.php @@ -0,0 +1,198 @@ + 20]; + + /** + * @var array + */ + protected $classifiers; + + /** + * @var float + */ + protected $subsetRatio = 0.5; + + /** + * @var array + */ + private $samples = []; + + /** + * Creates an ensemble classifier with given number of base classifiers
+ * Default number of base classifiers is 100. + * The more number of base classifiers, the better performance but at the cost of procesing time + * + * @param int $numClassifier + */ + public function __construct($numClassifier = 50) + { + $this->numClassifier = $numClassifier; + } + + /** + * This method determines the ratio of samples used to create the 'bootstrap' subset, + * e.g., random samples drawn from the original dataset with replacement (allow repeats), + * to train each base classifier. + * + * @param float $ratio + * @return $this + * @throws Exception + */ + public function setSubsetRatio(float $ratio) + { + if ($ratio < 0.1 || $ratio > 1.0) { + throw new \Exception("Subset ratio should be between 0.1 and 1.0"); + } + $this->subsetRatio = $ratio; + return $this; + } + + /** + * This method is used to set the base classifier. Default value is + * DecisionTree::class, but any class that implements the Classifier + * can be used.
+ * While giving the parameters of the classifier, the values should be + * given in the order they are in the constructor of the classifier and parameter + * names are neglected. + * + * @param string $classifier + * @param array $classifierOptions + * @return $this + */ + public function setClassifer(string $classifier, array $classifierOptions = []) + { + $this->classifier = $classifier; + $this->classifierOptions = $classifierOptions; + return $this; + } + + /** + * @param array $samples + * @param array $targets + */ + public function train(array $samples, array $targets) + { + $this->samples = array_merge($this->samples, $samples); + $this->targets = array_merge($this->targets, $targets); + $this->featureCount = count($samples[0]); + $this->numSamples = count($this->samples); + + // Init classifiers and train them with random sub-samples + $this->classifiers = $this->initClassifiers(); + $index = 0; + foreach ($this->classifiers as $classifier) { + list($samples, $targets) = $this->getRandomSubset($index); + $classifier->train($samples, $targets); + ++$index; + } + } + + /** + * @param int $index + * @return array + */ + protected function getRandomSubset($index) + { + $subsetLength = (int)ceil(sqrt($this->numSamples)); + $denom = $this->subsetRatio / 2; + $subsetLength = $this->numSamples / (1 / $denom); + $index = $index * $subsetLength % $this->numSamples; + $samples = []; + $targets = []; + for ($i=0; $i<$subsetLength * 2; $i++) { + $rand = rand($index, $this->numSamples - 1); + $samples[] = $this->samples[$rand]; + $targets[] = $this->targets[$rand]; + } + return [$samples, $targets]; + } + + /** + * @return array + */ + protected function initClassifiers() + { + $classifiers = []; + for ($i=0; $i<$this->numClassifier; $i++) { + $ref = new \ReflectionClass($this->classifier); + if ($this->classifierOptions) { + $obj = $ref->newInstanceArgs($this->classifierOptions); + } else { + $obj = $ref->newInstance(); + } + $classifiers[] = $this->initSingleClassifier($obj, $i); + } + return $classifiers; + } + + /** + * @param Classifier $classifier + * @param int $index + * @return Classifier + */ + protected function initSingleClassifier($classifier, $index) + { + return $classifier; + } + + /** + * @param array $sample + * @return mixed + */ + protected function predictSample(array $sample) + { + $predictions = []; + foreach ($this->classifiers as $classifier) { + /* @var $classifier Classifier */ + $predictions[] = $classifier->predict($sample); + } + + $counts = array_count_values($predictions); + arsort($counts); + reset($counts); + return key($counts); + } +} diff --git a/src/Phpml/Classification/Ensemble/RandomForest.php b/src/Phpml/Classification/Ensemble/RandomForest.php new file mode 100644 index 00000000..37df7aec --- /dev/null +++ b/src/Phpml/Classification/Ensemble/RandomForest.php @@ -0,0 +1,89 @@ +setSubsetRatio(1.0); + } + + /** + * This method is used to determine how much of the original columns (features) + * will be used to construct subsets to train base classifiers.
+ * + * Allowed values: 'sqrt', 'log' or any float number between 0.1 and 1.0
+ * + * If there are many features that diminishes classification performance, then + * small values should be preferred, otherwise, with low number of features, + * default value (0.7) will result in satisfactory performance. + * + * @param mixed $ratio string or float should be given + * @return $this + * @throws Exception + */ + public function setFeatureSubsetRatio($ratio) + { + if (is_float($ratio) && ($ratio < 0.1 || $ratio > 1.0)) { + throw new \Exception("When a float given, feature subset ratio should be between 0.1 and 1.0"); + } + if (is_string($ratio) && $ratio != 'sqrt' && $ratio != 'log') { + throw new \Exception("When a string given, feature subset ratio can only be 'sqrt' or 'log' "); + } + $this->featureSubsetRatio = $ratio; + return $this; + } + + /** + * RandomForest algorithm is usable *only* with DecisionTree + * + * @param string $classifier + * @param array $classifierOptions + * @return $this + */ + public function setClassifer(string $classifier, array $classifierOptions = []) + { + if ($classifier != DecisionTree::class) { + throw new \Exception("RandomForest can only use DecisionTree as base classifier"); + } + + return parent::setClassifer($classifier, $classifierOptions); + } + + /** + * @param DecisionTree $classifier + * @param int $index + * @return DecisionTree + */ + protected function initSingleClassifier($classifier, $index) + { + if (is_float($this->featureSubsetRatio)) { + $featureCount = (int)($this->featureSubsetRatio * $this->featureCount); + } elseif ($this->featureCount == 'sqrt') { + $featureCount = (int)sqrt($this->featureCount) + 1; + } else { + $featureCount = (int)log($this->featureCount, 2) + 1; + } + + if ($featureCount >= $this->featureCount) { + $featureCount = $this->featureCount; + } + + return $classifier->setNumFeatures($featureCount); + } +} diff --git a/tests/Phpml/Classification/Ensemble/BaggingTest.php b/tests/Phpml/Classification/Ensemble/BaggingTest.php new file mode 100644 index 00000000..e7dfcadf --- /dev/null +++ b/tests/Phpml/Classification/Ensemble/BaggingTest.php @@ -0,0 +1,127 @@ +getData($this->data); + $classifier = $this->getClassifier(); + // Testing with default options + $classifier->train($data, $targets); + $this->assertEquals('Dont_play', $classifier->predict(['sunny', 78, 72, 'false'])); + $this->assertEquals('Play', $classifier->predict(['overcast', 60, 60, 'false'])); + $this->assertEquals('Dont_play', $classifier->predict(['rain', 60, 60, 'true'])); + + list($data, $targets) = $this->getData($this->extraData); + $classifier->train($data, $targets); + $this->assertEquals('Dont_play', $classifier->predict(['scorching', 95, 90, 'true'])); + $this->assertEquals('Play', $classifier->predict(['overcast', 60, 60, 'false'])); + + return $classifier; + } + + public function testSaveAndRestore() + { + list($data, $targets) = $this->getData($this->data); + $classifier = $this->getClassifier(5); + $classifier->train($data, $targets); + + $testSamples = [['sunny', 78, 72, 'false'], ['overcast', 60, 60, 'false']]; + $predicted = $classifier->predict($testSamples); + + $filename = 'bagging-test-'.rand(100, 999).'-'.uniqid(); + $filepath = tempnam(sys_get_temp_dir(), $filename); + $modelManager = new ModelManager(); + $modelManager->saveToFile($classifier, $filepath); + + $restoredClassifier = $modelManager->restoreFromFile($filepath); + $this->assertEquals($classifier, $restoredClassifier); + $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + } + + public function testBaseClassifiers() + { + list($data, $targets) = $this->getData($this->data); + $baseClassifiers = $this->getAvailableBaseClassifiers(); + + foreach ($baseClassifiers as $base => $params) { + $classifier = $this->getClassifier(); + $classifier->setClassifer($base, $params); + $classifier->train($data, $targets); + + $baseClassifier = new $base(...array_values($params)); + $baseClassifier->train($data, $targets); + $testData = [['sunny', 78, 72, 'false'], ['overcast', 60, 60, 'false'], ['rain', 60, 60, 'true']]; + foreach ($testData as $test) { + $result = $classifier->predict($test); + $baseResult = $classifier->predict($test); + $this->assertEquals($result, $baseResult); + } + } + } + + protected function getClassifier($numBaseClassifiers = 50) + { + $classifier = new Bagging($numBaseClassifiers); + $classifier->setSubsetRatio(1.0); + $classifier->setClassifer(DecisionTree::class, ['depth' => 10]); + return $classifier; + } + + protected function getAvailableBaseClassifiers() + { + return [ + DecisionTree::class => ['depth' => 5], + NaiveBayes::class => [] + ]; + } + + private function getData($input) + { + // Populating input data to a size large enough + // for base classifiers that they can work with a subset of it + $populated = []; + for ($i=0; $i<20; $i++) { + $populated = array_merge($populated, $input); + } + shuffle($populated); + $targets = array_column($populated, 4); + array_walk($populated, function (&$v) { + array_splice($v, 4, 1); + }); + return [$populated, $targets]; + } +} diff --git a/tests/Phpml/Classification/Ensemble/RandomForestTest.php b/tests/Phpml/Classification/Ensemble/RandomForestTest.php new file mode 100644 index 00000000..d32507c6 --- /dev/null +++ b/tests/Phpml/Classification/Ensemble/RandomForestTest.php @@ -0,0 +1,38 @@ +setFeatureSubsetRatio('log'); + return $classifier; + } + + protected function getAvailableBaseClassifiers() + { + return [ DecisionTree::class => ['depth' => 5] ]; + } + + public function testOtherBaseClassifier() + { + try { + $classifier = new RandomForest(); + $classifier->setClassifer(NaiveBayes::class); + $this->assertEquals(0, 1); + } catch (\Exception $ex) { + $this->assertEquals(1, 1); + } + } +} From 0a58a71d776000fe79b54027438e04d5ec83d991 Mon Sep 17 00:00:00 2001 From: Mustafa Karabulut Date: Thu, 9 Feb 2017 12:30:38 +0300 Subject: [PATCH 152/328] Euclidean optimization (#42) * Euclidean optimization * Euclidean with foreach --- src/Phpml/Math/Distance/Euclidean.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Phpml/Math/Distance/Euclidean.php b/src/Phpml/Math/Distance/Euclidean.php index ceaf510f..ad60e871 100644 --- a/src/Phpml/Math/Distance/Euclidean.php +++ b/src/Phpml/Math/Distance/Euclidean.php @@ -24,10 +24,9 @@ public function distance(array $a, array $b): float } $distance = 0; - $count = count($a); - for ($i = 0; $i < $count; ++$i) { - $distance += pow($a[$i] - $b[$i], 2); + foreach ($a as $i => $val) { + $distance += ($val - $b[$i]) ** 2; } return sqrt((float) $distance); From 240a22788f31ef9fc90c8dd27f5952bb4a5de882 Mon Sep 17 00:00:00 2001 From: Mustafa Karabulut Date: Fri, 10 Feb 2017 14:01:58 +0300 Subject: [PATCH 153/328] Added new algorithms to the list (#44) --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index c5843549..62a906d4 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,10 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * [SVC](http://php-ml.readthedocs.io/en/latest/machine-learning/classification/svc/) * [k-Nearest Neighbors](http://php-ml.readthedocs.io/en/latest/machine-learning/classification/k-nearest-neighbors/) * [Naive Bayes](http://php-ml.readthedocs.io/en/latest/machine-learning/classification/naive-bayes/) + * Decision Tree (CART) + * Ensemble Algorithms + * Bagging (Bootstrap Aggregating) + * Random Forest * Regression * [Least Squares](http://php-ml.readthedocs.io/en/latest/machine-learning/regression/least-squares/) * [SVR](http://php-ml.readthedocs.io/en/latest/machine-learning/regression/svr/) From a33d5fe9c81ad3479fd9aff56dbc78fa08a1a302 Mon Sep 17 00:00:00 2001 From: Mustafa Karabulut Date: Mon, 13 Feb 2017 23:23:18 +0300 Subject: [PATCH 154/328] RandomForest::getFeatureImportances() method (#47) * RandomForest::getFeatureImportances() method * CsvDataset update for column names --- src/Phpml/Classification/DecisionTree.php | 121 +++++++++++++++++- .../DecisionTree/DecisionTreeLeaf.php | 64 ++++++++- src/Phpml/Classification/Ensemble/Bagging.php | 16 +-- .../Classification/Ensemble/RandomForest.php | 76 ++++++++++- src/Phpml/Dataset/CsvDataset.php | 18 ++- 5 files changed, 273 insertions(+), 22 deletions(-) diff --git a/src/Phpml/Classification/DecisionTree.php b/src/Phpml/Classification/DecisionTree.php index 1a04802b..6a860ebe 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -56,6 +56,17 @@ class DecisionTree implements Classifier */ private $numUsableFeatures = 0; + /** + * @var array + */ + private $featureImportances = null; + + /** + * + * @var array + */ + private $columnNames = null; + /** * @param int $maxDepth */ @@ -76,6 +87,21 @@ public function train(array $samples, array $targets) $this->columnTypes = $this->getColumnTypes($this->samples); $this->labels = array_keys(array_count_values($this->targets)); $this->tree = $this->getSplitLeaf(range(0, count($this->samples) - 1)); + + // Each time the tree is trained, feature importances are reset so that + // we will have to compute it again depending on the new data + $this->featureImportances = null; + + // If column names are given or computed before, then there is no + // need to init it and accidentally remove the previous given names + if ($this->columnNames === null) { + $this->columnNames = range(0, $this->featureCount - 1); + } elseif (count($this->columnNames) > $this->featureCount) { + $this->columnNames = array_slice($this->columnNames, 0, $this->featureCount); + } elseif (count($this->columnNames) < $this->featureCount) { + $this->columnNames = array_merge($this->columnNames, + range(count($this->columnNames), $this->featureCount - 1)); + } } protected function getColumnTypes(array $samples) @@ -164,6 +190,7 @@ protected function getBestSplit($records) $split->value = $baseValue; $split->giniIndex = $gini; $split->columnIndex = $i; + $split->isContinuous = $this->columnTypes[$i] == self::CONTINUOS; $split->records = $records; $bestSplit = $split; $bestGiniVal = $gini; @@ -292,6 +319,25 @@ public function setNumFeatures(int $numFeatures) } $this->numUsableFeatures = $numFeatures; + + return $this; + } + + /** + * A string array to represent columns. Useful when HTML output or + * column importances are desired to be inspected. + * + * @param array $names + * @return $this + */ + public function setColumnNames(array $names) + { + if ($this->featureCount != 0 && count($names) != $this->featureCount) { + throw new \Exception("Length of the given array should be equal to feature count ($this->featureCount)"); + } + + $this->columnNames = $names; + return $this; } @@ -300,7 +346,80 @@ public function setNumFeatures(int $numFeatures) */ public function getHtml() { - return $this->tree->__toString(); + return $this->tree->getHTML($this->columnNames); + } + + /** + * This will return an array including an importance value for + * each column in the given dataset. The importance values are + * normalized and their total makes 1.
+ * + * @param array $labels + * @return array + */ + public function getFeatureImportances() + { + if ($this->featureImportances !== null) { + return $this->featureImportances; + } + + $sampleCount = count($this->samples); + $this->featureImportances = []; + foreach ($this->columnNames as $column => $columnName) { + $nodes = $this->getSplitNodesByColumn($column, $this->tree); + + $importance = 0; + foreach ($nodes as $node) { + $importance += $node->getNodeImpurityDecrease($sampleCount); + } + + $this->featureImportances[$columnName] = $importance; + } + + // Normalize & sort the importances + $total = array_sum($this->featureImportances); + if ($total > 0) { + foreach ($this->featureImportances as &$importance) { + $importance /= $total; + } + arsort($this->featureImportances); + } + + return $this->featureImportances; + } + + /** + * Collects and returns an array of internal nodes that use the given + * column as a split criteron + * + * @param int $column + * @param DecisionTreeLeaf + * @param array $collected + * + * @return array + */ + protected function getSplitNodesByColumn($column, DecisionTreeLeaf $node) + { + if (!$node || $node->isTerminal) { + return []; + } + + $nodes = []; + if ($node->columnIndex == $column) { + $nodes[] = $node; + } + + $lNodes = []; + $rNodes = []; + if ($node->leftLeaf) { + $lNodes = $this->getSplitNodesByColumn($column, $node->leftLeaf); + } + if ($node->rightLeaf) { + $rNodes = $this->getSplitNodesByColumn($column, $node->rightLeaf); + } + $nodes = array_merge($nodes, $lNodes, $rNodes); + + return $nodes; } /** diff --git a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php index 19938641..e30fc109 100644 --- a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -6,7 +6,6 @@ class DecisionTreeLeaf { - const OPERATOR_EQ = '='; /** * @var string */ @@ -45,6 +44,11 @@ class DecisionTreeLeaf */ public $isTerminal = false; + /** + * @var bool + */ + public $isContinuous = false; + /** * @var float */ @@ -62,7 +66,7 @@ class DecisionTreeLeaf public function evaluate($record) { $recordField = $record[$this->columnIndex]; - if (is_string($this->value) && preg_match("/^([<>=]{1,2})\s*(.*)/", $this->value, $matches)) { + if ($this->isContinuous && preg_match("/^([<>=]{1,2})\s*(.*)/", strval($this->value), $matches)) { $op = $matches[1]; $value= floatval($matches[2]); $recordField = strval($recordField); @@ -72,13 +76,51 @@ public function evaluate($record) return $recordField == $this->value; } - public function __toString() + /** + * Returns Mean Decrease Impurity (MDI) in the node. + * For terminal nodes, this value is equal to 0 + * + * @return float + */ + public function getNodeImpurityDecrease(int $parentRecordCount) + { + if ($this->isTerminal) { + return 0.0; + } + + $nodeSampleCount = (float)count($this->records); + $iT = $this->giniIndex; + + if ($this->leftLeaf) { + $pL = count($this->leftLeaf->records)/$nodeSampleCount; + $iT -= $pL * $this->leftLeaf->giniIndex; + } + + if ($this->rightLeaf) { + $pR = count($this->rightLeaf->records)/$nodeSampleCount; + $iT -= $pR * $this->rightLeaf->giniIndex; + } + + return $iT * $nodeSampleCount / $parentRecordCount; + } + + /** + * Returns HTML representation of the node including children nodes + * + * @param $columnNames + * @return string + */ + public function getHTML($columnNames = null) { if ($this->isTerminal) { $value = "$this->classValue"; } else { $value = $this->value; - $col = "col_$this->columnIndex"; + if ($columnNames !== null) { + $col = $columnNames[$this->columnIndex]; + } else { + $col = "col_$this->columnIndex"; + } if (! preg_match("/^[<>=]{1,2}/", $value)) { $value = "=$value"; } @@ -89,13 +131,13 @@ public function __toString() if ($this->leftLeaf || $this->rightLeaf) { $str .=''; if ($this->leftLeaf) { - $str .="| Yes
$this->leftLeaf"; + $str .="| Yes
" . $this->leftLeaf->getHTML($columnNames) . ""; } else { $str .=''; } $str .=' '; if ($this->rightLeaf) { - $str .="No |
$this->rightLeaf"; + $str .="No |
" . $this->rightLeaf->getHTML($columnNames) . ""; } else { $str .=''; } @@ -104,4 +146,14 @@ public function __toString() $str .= ''; return $str; } + + /** + * HTML representation of the tree without column names + * + * @return string + */ + public function __toString() + { + return $this->getHTML(); + } } diff --git a/src/Phpml/Classification/Ensemble/Bagging.php b/src/Phpml/Classification/Ensemble/Bagging.php index 817869ef..d579b248 100644 --- a/src/Phpml/Classification/Ensemble/Bagging.php +++ b/src/Phpml/Classification/Ensemble/Bagging.php @@ -53,7 +53,7 @@ class Bagging implements Classifier /** * @var float */ - protected $subsetRatio = 0.5; + protected $subsetRatio = 0.7; /** * @var array @@ -120,7 +120,7 @@ public function train(array $samples, array $targets) $this->featureCount = count($samples[0]); $this->numSamples = count($this->samples); - // Init classifiers and train them with random sub-samples + // Init classifiers and train them with bootstrap samples $this->classifiers = $this->initClassifiers(); $index = 0; foreach ($this->classifiers as $classifier) { @@ -134,16 +134,14 @@ public function train(array $samples, array $targets) * @param int $index * @return array */ - protected function getRandomSubset($index) + protected function getRandomSubset(int $index) { - $subsetLength = (int)ceil(sqrt($this->numSamples)); - $denom = $this->subsetRatio / 2; - $subsetLength = $this->numSamples / (1 / $denom); - $index = $index * $subsetLength % $this->numSamples; $samples = []; $targets = []; - for ($i=0; $i<$subsetLength * 2; $i++) { - $rand = rand($index, $this->numSamples - 1); + srand($index); + $bootstrapSize = $this->subsetRatio * $this->numSamples; + for ($i=0; $i < $bootstrapSize; $i++) { + $rand = rand(0, $this->numSamples - 1); $samples[] = $this->samples[$rand]; $targets[] = $this->targets[$rand]; } diff --git a/src/Phpml/Classification/Ensemble/RandomForest.php b/src/Phpml/Classification/Ensemble/RandomForest.php index 37df7aec..025badff 100644 --- a/src/Phpml/Classification/Ensemble/RandomForest.php +++ b/src/Phpml/Classification/Ensemble/RandomForest.php @@ -16,6 +16,18 @@ class RandomForest extends Bagging */ protected $featureSubsetRatio = 'log'; + /** + * @var array + */ + protected $columnNames = null; + + /** + * Initializes RandomForest with the given number of trees. More trees + * may increase the prediction performance while it will also substantially + * increase the processing time and the required memory + * + * @param type $numClassifier + */ public function __construct($numClassifier = 50) { parent::__construct($numClassifier); @@ -24,14 +36,13 @@ public function __construct($numClassifier = 50) } /** - * This method is used to determine how much of the original columns (features) + * This method is used to determine how many of the original columns (features) * will be used to construct subsets to train base classifiers.
* * Allowed values: 'sqrt', 'log' or any float number between 0.1 and 1.0
* - * If there are many features that diminishes classification performance, then - * small values should be preferred, otherwise, with low number of features, - * default value (0.7) will result in satisfactory performance. + * Default value for the ratio is 'log' which results in log(numFeatures, 2) + 1 + * features to be taken into consideration while selecting subspace of features * * @param mixed $ratio string or float should be given * @return $this @@ -65,6 +76,55 @@ public function setClassifer(string $classifier, array $classifierOptions = []) return parent::setClassifer($classifier, $classifierOptions); } + /** + * This will return an array including an importance value for + * each column in the given dataset. Importance values for a column + * is the average importance of that column in all trees in the forest + * + * @return array + */ + public function getFeatureImportances() + { + // Traverse each tree and sum importance of the columns + $sum = []; + foreach ($this->classifiers as $tree) { + /* @var $tree DecisionTree */ + $importances = $tree->getFeatureImportances(); + + foreach ($importances as $column => $importance) { + if (array_key_exists($column, $sum)) { + $sum[$column] += $importance; + } else { + $sum[$column] = $importance; + } + } + } + + // Normalize & sort the importance values + $total = array_sum($sum); + foreach ($sum as &$importance) { + $importance /= $total; + } + + arsort($sum); + + return $sum; + } + + /** + * A string array to represent the columns is given. They are useful + * when trying to print some information about the trees such as feature importances + * + * @param array $names + * @return $this + */ + public function setColumnNames(array $names) + { + $this->columnNames = $names; + + return $this; + } + /** * @param DecisionTree $classifier * @param int $index @@ -84,6 +144,12 @@ protected function initSingleClassifier($classifier, $index) $featureCount = $this->featureCount; } - return $classifier->setNumFeatures($featureCount); + if ($this->columnNames === null) { + $this->columnNames = range(0, $this->featureCount - 1); + } + + return $classifier + ->setColumnNames($this->columnNames) + ->setNumFeatures($featureCount); } } diff --git a/src/Phpml/Dataset/CsvDataset.php b/src/Phpml/Dataset/CsvDataset.php index dd722d4d..483b1af8 100644 --- a/src/Phpml/Dataset/CsvDataset.php +++ b/src/Phpml/Dataset/CsvDataset.php @@ -8,6 +8,11 @@ class CsvDataset extends ArrayDataset { + /** + * @var array + */ + protected $columnNames; + /** * @param string $filepath * @param int $features @@ -26,7 +31,10 @@ public function __construct(string $filepath, int $features, bool $headingRow = } if ($headingRow) { - fgets($handle); + $data = fgetcsv($handle, 1000, ','); + $this->columnNames = array_slice($data, 0, $features); + } else { + $this->columnNames = range(0, $features - 1); } while (($data = fgetcsv($handle, 1000, ',')) !== false) { @@ -35,4 +43,12 @@ public function __construct(string $filepath, int $features, bool $headingRow = } fclose($handle); } + + /** + * @return array + */ + public function getColumnNames() + { + return $this->columnNames; + } } From f0a7984f39f552363a03b6a2bb62b8b5ae65e218 Mon Sep 17 00:00:00 2001 From: Povilas Susinskas Date: Wed, 15 Feb 2017 11:09:16 +0200 Subject: [PATCH 155/328] Check if matrix is singular doing inverse (#49) * Check if matrix is singular doing inverse * add return bool type --- src/Phpml/Exception/MatrixException.php | 8 ++++++++ src/Phpml/Math/Matrix.php | 12 ++++++++++++ tests/Phpml/Math/MatrixTest.php | 14 ++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/src/Phpml/Exception/MatrixException.php b/src/Phpml/Exception/MatrixException.php index 9aaa43a5..28158048 100644 --- a/src/Phpml/Exception/MatrixException.php +++ b/src/Phpml/Exception/MatrixException.php @@ -21,4 +21,12 @@ public static function columnOutOfRange() { return new self('Column out of range'); } + + /** + * @return MatrixException + */ + public static function singularMatrix() + { + return new self('Matrix is singular'); + } } diff --git a/src/Phpml/Math/Matrix.php b/src/Phpml/Math/Matrix.php index 6485a7db..39f5c5ab 100644 --- a/src/Phpml/Math/Matrix.php +++ b/src/Phpml/Math/Matrix.php @@ -233,6 +233,10 @@ public function inverse() throw MatrixException::notSquareMatrix(); } + if ($this->isSingular()) { + throw MatrixException::singularMatrix(); + } + $newMatrix = []; for ($i = 0; $i < $this->rows; ++$i) { for ($j = 0; $j < $this->columns; ++$j) { @@ -271,4 +275,12 @@ public function crossOut(int $row, int $column) return new self($newMatrix, false); } + + /** + * @return bool + */ + public function isSingular() : bool + { + return 0 == $this->getDeterminant(); + } } diff --git a/tests/Phpml/Math/MatrixTest.php b/tests/Phpml/Math/MatrixTest.php index b4adc763..48a6fe94 100644 --- a/tests/Phpml/Math/MatrixTest.php +++ b/tests/Phpml/Math/MatrixTest.php @@ -141,6 +141,20 @@ public function testThrowExceptionWhenInverseIfArrayIsNotSquare() $matrix->inverse(); } + /** + * @expectedException \Phpml\Exception\MatrixException + */ + public function testThrowExceptionWhenInverseIfMatrixIsSingular() + { + $matrix = new Matrix([ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + ]); + + $matrix->inverse(); + } + public function testInverseMatrix() { //http://ncalculators.com/matrix/inverse-matrix.htm From cf222bcce4fa9c50ca4f603f5983292598637776 Mon Sep 17 00:00:00 2001 From: Mustafa Karabulut Date: Fri, 17 Feb 2017 01:23:55 +0300 Subject: [PATCH 156/328] Linear classifiers: Perceptron, Adaline, DecisionStump (#50) * Linear classifiers * Code formatting to PSR-2 * Added basic test cases for linear classifiers --- src/Phpml/Classification/DecisionTree.php | 55 +++++- src/Phpml/Classification/Linear/Adaline.php | 148 +++++++++++++++ .../Classification/Linear/DecisionStump.php | 56 ++++++ .../Classification/Linear/Perceptron.php | 174 ++++++++++++++++++ src/Phpml/Preprocessing/Normalizer.php | 55 +++++- .../Classification/Linear/AdalineTest.php | 55 ++++++ .../Linear/DecisionStumpTest.php | 59 ++++++ .../Classification/Linear/PerceptronTest.php | 55 ++++++ tests/Phpml/Preprocessing/NormalizerTest.php | 28 +++ 9 files changed, 676 insertions(+), 9 deletions(-) create mode 100644 src/Phpml/Classification/Linear/Adaline.php create mode 100644 src/Phpml/Classification/Linear/DecisionStump.php create mode 100644 src/Phpml/Classification/Linear/Perceptron.php create mode 100644 tests/Phpml/Classification/Linear/AdalineTest.php create mode 100644 tests/Phpml/Classification/Linear/DecisionStumpTest.php create mode 100644 tests/Phpml/Classification/Linear/PerceptronTest.php diff --git a/src/Phpml/Classification/DecisionTree.php b/src/Phpml/Classification/DecisionTree.php index 6a860ebe..c73f8706 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -56,6 +56,11 @@ class DecisionTree implements Classifier */ private $numUsableFeatures = 0; + /** + * @var array + */ + private $selectedFeatures; + /** * @var array */ @@ -126,33 +131,45 @@ protected function getSplitLeaf($records, $depth = 0) if ($this->actualDepth < $depth) { $this->actualDepth = $depth; } + + // Traverse all records to see if all records belong to the same class, + // otherwise group the records so that we can classify the leaf + // in case maximum depth is reached $leftRecords = []; $rightRecords= []; $remainingTargets = []; $prevRecord = null; $allSame = true; + foreach ($records as $recordNo) { + // Check if the previous record is the same with the current one $record = $this->samples[$recordNo]; if ($prevRecord && $prevRecord != $record) { $allSame = false; } $prevRecord = $record; + + // According to the split criteron, this record will + // belong to either left or the right side in the next split if ($split->evaluate($record)) { $leftRecords[] = $recordNo; } else { $rightRecords[]= $recordNo; } + + // Group remaining targets $target = $this->targets[$recordNo]; - if (! in_array($target, $remainingTargets)) { - $remainingTargets[] = $target; + if (! array_key_exists($target, $remainingTargets)) { + $remainingTargets[$target] = 1; + } else { + $remainingTargets[$target]++; } } if (count($remainingTargets) == 1 || $allSame || $depth >= $this->maxDepth) { $split->isTerminal = 1; - $classes = array_count_values($remainingTargets); - arsort($classes); - $split->classValue = key($classes); + arsort($remainingTargets); + $split->classValue = key($remainingTargets); } else { if ($leftRecords) { $split->leftLeaf = $this->getSplitLeaf($leftRecords, $depth + 1); @@ -200,15 +217,31 @@ protected function getBestSplit($records) } /** + * Returns available features/columns to the tree for the decision making + * process.
+ * + * If a number is given with setNumFeatures() method, then a random selection + * of features up to this number is returned.
+ * + * If some features are manually selected by use of setSelectedFeatures(), + * then only these features are returned
+ * + * If any of above methods were not called beforehand, then all features + * are returned by default. + * * @return array */ protected function getSelectedFeatures() { $allFeatures = range(0, $this->featureCount - 1); - if ($this->numUsableFeatures == 0) { + if ($this->numUsableFeatures == 0 && ! $this->selectedFeatures) { return $allFeatures; } + if ($this->selectedFeatures) { + return $this->selectedFeatures; + } + $numFeatures = $this->numUsableFeatures; if ($numFeatures > $this->featureCount) { $numFeatures = $this->featureCount; @@ -323,6 +356,16 @@ public function setNumFeatures(int $numFeatures) return $this; } + /** + * Used to set predefined features to consider while deciding which column to use for a split, + * + * @param array $features + */ + protected function setSelectedFeatures(array $selectedFeatures) + { + $this->selectedFeatures = $selectedFeatures; + } + /** * A string array to represent columns. Useful when HTML output or * column importances are desired to be inspected. diff --git a/src/Phpml/Classification/Linear/Adaline.php b/src/Phpml/Classification/Linear/Adaline.php new file mode 100644 index 00000000..94283d97 --- /dev/null +++ b/src/Phpml/Classification/Linear/Adaline.php @@ -0,0 +1,148 @@ + + * + * Learning rate should be a float value between 0.0(exclusive) and 1.0 (inclusive)
+ * Maximum number of iterations can be an integer value greater than 0
+ * If normalizeInputs is set to true, then every input given to the algorithm will be standardized + * by use of standard deviation and mean calculation + * + * @param int $learningRate + * @param int $maxIterations + */ + public function __construct(float $learningRate = 0.001, int $maxIterations = 1000, + bool $normalizeInputs = true, int $trainingType = self::BATCH_TRAINING) + { + if ($normalizeInputs) { + $this->normalizer = new Normalizer(Normalizer::NORM_STD); + } + + if (! in_array($trainingType, [self::BATCH_TRAINING, self::ONLINE_TRAINING])) { + throw new \Exception("Adaline can only be trained with batch and online/stochastic gradient descent algorithm"); + } + $this->trainingType = $trainingType; + + parent::__construct($learningRate, $maxIterations); + } + + /** + * @param array $samples + * @param array $targets + */ + public function train(array $samples, array $targets) + { + if ($this->normalizer) { + $this->normalizer->transform($samples); + } + + parent::train($samples, $targets); + } + + /** + * Adapts the weights with respect to given samples and targets + * by use of gradient descent learning rule + */ + protected function runTraining() + { + // If online training is chosen, then the parent runTraining method + // will be executed with the 'output' method as the error function + if ($this->trainingType == self::ONLINE_TRAINING) { + return parent::runTraining(); + } + + // Batch learning is executed: + $currIter = 0; + while ($this->maxIterations > $currIter++) { + $outputs = array_map([$this, 'output'], $this->samples); + $updates = array_map([$this, 'gradient'], $this->targets, $outputs); + $sum = array_sum($updates); + + // Updates all weights at once + for ($i=0; $i <= $this->featureCount; $i++) { + if ($i == 0) { + $this->weights[0] += $this->learningRate * $sum; + } else { + $col = array_column($this->samples, $i - 1); + $error = 0; + foreach ($col as $index => $val) { + $error += $val * $updates[$index]; + } + + $this->weights[$i] += $this->learningRate * $error; + } + } + } + } + + /** + * Returns the direction of gradient given the desired and actual outputs + * + * @param int $desired + * @param int $output + * @return int + */ + protected function gradient($desired, $output) + { + return $desired - $output; + } + + /** + * @param array $sample + * @return mixed + */ + public function predictSample(array $sample) + { + if ($this->normalizer) { + $samples = [$sample]; + $this->normalizer->transform($samples); + $sample = $samples[0]; + } + + return parent::predictSample($sample); + } +} diff --git a/src/Phpml/Classification/Linear/DecisionStump.php b/src/Phpml/Classification/Linear/DecisionStump.php new file mode 100644 index 00000000..18d44497 --- /dev/null +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -0,0 +1,56 @@ + + * + * If columnIndex is given, then the stump tries to produce a decision node + * on this column, otherwise in cases given the value of -1, the stump itself + * decides which column to take for the decision (Default DecisionTree behaviour) + * + * @param int $columnIndex + */ + public function __construct(int $columnIndex = -1) + { + $this->columnIndex = $columnIndex; + + parent::__construct(1); + } + + /** + * @param array $samples + * @param array $targets + */ + public function train(array $samples, array $targets) + { + // Check if a column index was given + if ($this->columnIndex >= 0 && $this->columnIndex > count($samples[0]) - 1) { + $this->columnIndex = -1; + } + + if ($this->columnIndex >= 0) { + $this->setSelectedFeatures([$this->columnIndex]); + } + + parent::train($samples, $targets); + } +} diff --git a/src/Phpml/Classification/Linear/Perceptron.php b/src/Phpml/Classification/Linear/Perceptron.php new file mode 100644 index 00000000..963638e6 --- /dev/null +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -0,0 +1,174 @@ + + * + * Learning rate should be a float value between 0.0(exclusive) and 1.0(inclusive)
+ * Maximum number of iterations can be an integer value greater than 0 + * @param int $learningRate + * @param int $maxIterations + */ + public function __construct(float $learningRate = 0.001, int $maxIterations = 1000) + { + if ($learningRate <= 0.0 || $learningRate > 1.0) { + throw new \Exception("Learning rate should be a float value between 0.0(exclusive) and 1.0(inclusive)"); + } + + if ($maxIterations <= 0) { + throw new \Exception("Maximum number of iterations should be an integer greater than 0"); + } + + $this->learningRate = $learningRate; + $this->maxIterations = $maxIterations; + } + + /** + * @param array $samples + * @param array $targets + */ + public function train(array $samples, array $targets) + { + $this->labels = array_keys(array_count_values($targets)); + if (count($this->labels) > 2) { + throw new \Exception("Perceptron is for only binary (two-class) classification"); + } + + // Set all target values to either -1 or 1 + $this->labels = [1 => $this->labels[0], -1 => $this->labels[1]]; + foreach ($targets as $target) { + $this->targets[] = $target == $this->labels[1] ? 1 : -1; + } + + // Set samples and feature count vars + $this->samples = array_merge($this->samples, $samples); + $this->featureCount = count($this->samples[0]); + + // Init weights with random values + $this->weights = array_fill(0, $this->featureCount + 1, 0); + foreach ($this->weights as &$weight) { + $weight = rand() / (float) getrandmax(); + } + // Do training + $this->runTraining(); + } + + /** + * Adapts the weights with respect to given samples and targets + * by use of perceptron learning rule + */ + protected function runTraining() + { + $currIter = 0; + while ($this->maxIterations > $currIter++) { + foreach ($this->samples as $index => $sample) { + $target = $this->targets[$index]; + $prediction = $this->{static::$errorFunction}($sample); + $update = $target - $prediction; + // Update bias + $this->weights[0] += $update * $this->learningRate; // Bias + // Update other weights + for ($i=1; $i <= $this->featureCount; $i++) { + $this->weights[$i] += $update * $sample[$i - 1] * $this->learningRate; + } + } + } + } + + /** + * Calculates net output of the network as a float value for the given input + * + * @param array $sample + * @return int + */ + protected function output(array $sample) + { + $sum = 0; + foreach ($this->weights as $index => $w) { + if ($index == 0) { + $sum += $w; + } else { + $sum += $w * $sample[$index - 1]; + } + } + + return $sum; + } + + /** + * Returns the class value (either -1 or 1) for the given input + * + * @param array $sample + * @return int + */ + protected function outputClass(array $sample) + { + return $this->output($sample) > 0 ? 1 : -1; + } + + /** + * @param array $sample + * @return mixed + */ + protected function predictSample(array $sample) + { + $predictedClass = $this->outputClass($sample); + + return $this->labels[ $predictedClass ]; + } +} diff --git a/src/Phpml/Preprocessing/Normalizer.php b/src/Phpml/Preprocessing/Normalizer.php index 5cff6e84..42a8f1c2 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -5,17 +5,35 @@ namespace Phpml\Preprocessing; use Phpml\Exception\NormalizerException; +use Phpml\Math\Statistic\StandardDeviation; +use Phpml\Math\Statistic\Mean; class Normalizer implements Preprocessor { const NORM_L1 = 1; const NORM_L2 = 2; + const NORM_STD= 3; /** * @var int */ private $norm; + /** + * @var bool + */ + private $fitted = false; + + /** + * @var array + */ + private $std; + + /** + * @var array + */ + private $mean; + /** * @param int $norm * @@ -23,7 +41,7 @@ class Normalizer implements Preprocessor */ public function __construct(int $norm = self::NORM_L2) { - if (!in_array($norm, [self::NORM_L1, self::NORM_L2])) { + if (!in_array($norm, [self::NORM_L1, self::NORM_L2, self::NORM_STD])) { throw NormalizerException::unknownNorm(); } @@ -35,7 +53,20 @@ public function __construct(int $norm = self::NORM_L2) */ public function fit(array $samples) { - // intentionally not implemented + if ($this->fitted) { + return; + } + + if ($this->norm == self::NORM_STD) { + $features = range(0, count($samples[0]) - 1); + foreach ($features as $i) { + $values = array_column($samples, $i); + $this->std[$i] = StandardDeviation::population($values); + $this->mean[$i] = Mean::arithmetic($values); + } + } + + $this->fitted = true; } /** @@ -43,7 +74,15 @@ public function fit(array $samples) */ public function transform(array &$samples) { - $method = sprintf('normalizeL%s', $this->norm); + $methods = [ + self::NORM_L1 => 'normalizeL1', + self::NORM_L2 => 'normalizeL2', + self::NORM_STD=> 'normalizeSTD' + ]; + $method = $methods[$this->norm]; + + $this->fit($samples); + foreach ($samples as &$sample) { $this->$method($sample); } @@ -88,4 +127,14 @@ private function normalizeL2(array &$sample) } } } + + /** + * @param array $sample + */ + private function normalizeSTD(array &$sample) + { + foreach ($sample as $i => $val) { + $sample[$i] = ($sample[$i] - $this->mean[$i]) / $this->std[$i]; + } + } } diff --git a/tests/Phpml/Classification/Linear/AdalineTest.php b/tests/Phpml/Classification/Linear/AdalineTest.php new file mode 100644 index 00000000..7ea63ab2 --- /dev/null +++ b/tests/Phpml/Classification/Linear/AdalineTest.php @@ -0,0 +1,55 @@ +train($samples, $targets); + $this->assertEquals(0, $classifier->predict([0.1, 0.2])); + $this->assertEquals(0, $classifier->predict([0.1, 0.99])); + $this->assertEquals(1, $classifier->predict([1.1, 0.8])); + + // OR problem + $samples = [[0, 0], [1, 0], [0, 1], [1, 1]]; + $targets = [0, 1, 1, 1]; + $classifier = new Adaline(); + $classifier->train($samples, $targets); + $this->assertEquals(0, $classifier->predict([0.1, 0.2])); + $this->assertEquals(1, $classifier->predict([0.1, 0.99])); + $this->assertEquals(1, $classifier->predict([1.1, 0.8])); + + return $classifier; + } + + public function testSaveAndRestore() + { + // Instantinate new Percetron trained for OR problem + $samples = [[0, 0], [1, 0], [0, 1], [1, 1]]; + $targets = [0, 1, 1, 1]; + $classifier = new Adaline(); + $classifier->train($samples, $targets); + $testSamples = [[0, 1], [1, 1], [0.2, 0.1]]; + $predicted = $classifier->predict($testSamples); + + $filename = 'adaline-test-'.rand(100, 999).'-'.uniqid(); + $filepath = tempnam(sys_get_temp_dir(), $filename); + $modelManager = new ModelManager(); + $modelManager->saveToFile($classifier, $filepath); + + $restoredClassifier = $modelManager->restoreFromFile($filepath); + $this->assertEquals($classifier, $restoredClassifier); + $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + } +} diff --git a/tests/Phpml/Classification/Linear/DecisionStumpTest.php b/tests/Phpml/Classification/Linear/DecisionStumpTest.php new file mode 100644 index 00000000..f83e0953 --- /dev/null +++ b/tests/Phpml/Classification/Linear/DecisionStumpTest.php @@ -0,0 +1,59 @@ +train($samples, $targets); + $this->assertEquals(0, $classifier->predict([0.1, 0.2])); + $this->assertEquals(0, $classifier->predict([1.1, 0.2])); + $this->assertEquals(1, $classifier->predict([0.1, 0.99])); + $this->assertEquals(1, $classifier->predict([1.1, 0.8])); + + // Then: vertical test + $samples = [[0, 0], [1, 0], [0, 1], [1, 1]]; + $targets = [0, 1, 0, 1]; + $classifier = new DecisionStump(); + $classifier->train($samples, $targets); + $this->assertEquals(0, $classifier->predict([0.1, 0.2])); + $this->assertEquals(0, $classifier->predict([0.1, 1.1])); + $this->assertEquals(1, $classifier->predict([1.0, 0.99])); + $this->assertEquals(1, $classifier->predict([1.1, 0.1])); + + return $classifier; + } + + public function testSaveAndRestore() + { + // Instantinate new Percetron trained for OR problem + $samples = [[0, 0], [1, 0], [0, 1], [1, 1]]; + $targets = [0, 1, 1, 1]; + $classifier = new DecisionStump(); + $classifier->train($samples, $targets); + $testSamples = [[0, 1], [1, 1], [0.2, 0.1]]; + $predicted = $classifier->predict($testSamples); + + $filename = 'dstump-test-'.rand(100, 999).'-'.uniqid(); + $filepath = tempnam(sys_get_temp_dir(), $filename); + $modelManager = new ModelManager(); + $modelManager->saveToFile($classifier, $filepath); + + $restoredClassifier = $modelManager->restoreFromFile($filepath); + $this->assertEquals($classifier, $restoredClassifier); + $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + } +} diff --git a/tests/Phpml/Classification/Linear/PerceptronTest.php b/tests/Phpml/Classification/Linear/PerceptronTest.php new file mode 100644 index 00000000..bf1b3847 --- /dev/null +++ b/tests/Phpml/Classification/Linear/PerceptronTest.php @@ -0,0 +1,55 @@ +train($samples, $targets); + $this->assertEquals(0, $classifier->predict([0.1, 0.2])); + $this->assertEquals(0, $classifier->predict([0.1, 0.99])); + $this->assertEquals(1, $classifier->predict([1.1, 0.8])); + + // OR problem + $samples = [[0, 0], [0.1, 0.2], [1, 0], [0, 1], [1, 1]]; + $targets = [0, 0, 1, 1, 1]; + $classifier = new Perceptron(0.001, 5000); + $classifier->train($samples, $targets); + $this->assertEquals(0, $classifier->predict([0, 0])); + $this->assertEquals(1, $classifier->predict([0.1, 0.99])); + $this->assertEquals(1, $classifier->predict([1.1, 0.8])); + + return $classifier; + } + + public function testSaveAndRestore() + { + // Instantinate new Percetron trained for OR problem + $samples = [[0, 0], [1, 0], [0, 1], [1, 1]]; + $targets = [0, 1, 1, 1]; + $classifier = new Perceptron(); + $classifier->train($samples, $targets); + $testSamples = [[0, 1], [1, 1], [0.2, 0.1]]; + $predicted = $classifier->predict($testSamples); + + $filename = 'perceptron-test-'.rand(100, 999).'-'.uniqid(); + $filepath = tempnam(sys_get_temp_dir(), $filename); + $modelManager = new ModelManager(); + $modelManager->saveToFile($classifier, $filepath); + + $restoredClassifier = $modelManager->restoreFromFile($filepath); + $this->assertEquals($classifier, $restoredClassifier); + $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + } +} diff --git a/tests/Phpml/Preprocessing/NormalizerTest.php b/tests/Phpml/Preprocessing/NormalizerTest.php index 99ebf4e7..07d121cc 100644 --- a/tests/Phpml/Preprocessing/NormalizerTest.php +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -100,4 +100,32 @@ public function testL1NormWithZeroSumCondition() $this->assertEquals($normalized, $samples, '', $delta = 0.01); } + + public function testStandardNorm() + { + // Generate 10 random vectors of length 3 + $samples = []; + srand(time()); + for ($i=0; $i<10; $i++) { + $sample = array_fill(0, 3, 0); + for ($k=0; $k<3; $k++) { + $sample[$k] = rand(1, 100); + } + $samples[] = $sample; + } + + // Use standard normalization + $normalizer = new Normalizer(Normalizer::NORM_STD); + $normalizer->transform($samples); + + // Values in the vector should be some value between -3 and +3 + $this->assertCount(10, $samples); + foreach ($samples as $sample) { + $errors = array_filter($sample, + function ($element) { + return $element < -3 || $element > 3; + }); + $this->assertCount(0, $errors); + } + } } From 4daa0a222a40ec82d518f279aaa73403f0ed6890 Mon Sep 17 00:00:00 2001 From: Mustafa Karabulut Date: Tue, 21 Feb 2017 12:38:18 +0300 Subject: [PATCH 157/328] AdaBoost algorithm along with some improvements (#51) --- src/Phpml/Classification/DecisionTree.php | 30 ++- .../DecisionTree/DecisionTreeLeaf.php | 18 +- .../Classification/Ensemble/AdaBoost.php | 190 ++++++++++++++++++ src/Phpml/Classification/Linear/Adaline.php | 72 +++---- .../Classification/Linear/DecisionStump.php | 127 +++++++++++- .../Classification/Linear/Perceptron.php | 23 ++- .../Classification/Ensemble/AdaBoostTest.php | 64 ++++++ 7 files changed, 463 insertions(+), 61 deletions(-) create mode 100644 src/Phpml/Classification/Ensemble/AdaBoost.php create mode 100644 tests/Phpml/Classification/Ensemble/AdaBoostTest.php diff --git a/src/Phpml/Classification/DecisionTree.php b/src/Phpml/Classification/DecisionTree.php index c73f8706..231d766a 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -24,7 +24,7 @@ class DecisionTree implements Classifier /** * @var array */ - private $columnTypes; + protected $columnTypes; /** * @var array @@ -39,12 +39,12 @@ class DecisionTree implements Classifier /** * @var DecisionTreeLeaf */ - private $tree = null; + protected $tree = null; /** * @var int */ - private $maxDepth; + protected $maxDepth; /** * @var int @@ -79,6 +79,7 @@ public function __construct($maxDepth = 10) { $this->maxDepth = $maxDepth; } + /** * @param array $samples * @param array $targets @@ -209,6 +210,17 @@ protected function getBestSplit($records) $split->columnIndex = $i; $split->isContinuous = $this->columnTypes[$i] == self::CONTINUOS; $split->records = $records; + + // If a numeric column is to be selected, then + // the original numeric value and the selected operator + // will also be saved into the leaf for future access + if ($this->columnTypes[$i] == self::CONTINUOS) { + $matches = []; + preg_match("/^([<>=]{1,2})\s*(.*)/", strval($split->value), $matches); + $split->operator = $matches[1]; + $split->numericValue = floatval($matches[2]); + } + $bestSplit = $split; $bestGiniVal = $gini; } @@ -318,15 +330,21 @@ protected function preprocess(array $samples) protected function isCategoricalColumn(array $columnValues) { $count = count($columnValues); + // There are two main indicators that *may* show whether a // column is composed of discrete set of values: - // 1- Column may contain string values + // 1- Column may contain string values and not float values // 2- Number of unique values in the column is only a small fraction of // all values in that column (Lower than or equal to %20 of all values) $numericValues = array_filter($columnValues, 'is_numeric'); + $floatValues = array_filter($columnValues, 'is_float'); + if ($floatValues) { + return false; + } if (count($numericValues) != $count) { return true; } + $distinctValues = array_count_values($columnValues); if (count($distinctValues) <= $count / 5) { return true; @@ -357,9 +375,9 @@ public function setNumFeatures(int $numFeatures) } /** - * Used to set predefined features to consider while deciding which column to use for a split, + * Used to set predefined features to consider while deciding which column to use for a split * - * @param array $features + * @param array $selectedFeatures */ protected function setSelectedFeatures(array $selectedFeatures) { diff --git a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php index e30fc109..bbb31751 100644 --- a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -11,6 +11,16 @@ class DecisionTreeLeaf */ public $value; + /** + * @var float + */ + public $numericValue; + + /** + * @var string + */ + public $operator; + /** * @var int */ @@ -66,13 +76,15 @@ class DecisionTreeLeaf public function evaluate($record) { $recordField = $record[$this->columnIndex]; - if ($this->isContinuous && preg_match("/^([<>=]{1,2})\s*(.*)/", strval($this->value), $matches)) { - $op = $matches[1]; - $value= floatval($matches[2]); + + if ($this->isContinuous) { + $op = $this->operator; + $value= $this->numericValue; $recordField = strval($recordField); eval("\$result = $recordField $op $value;"); return $result; } + return $recordField == $this->value; } diff --git a/src/Phpml/Classification/Ensemble/AdaBoost.php b/src/Phpml/Classification/Ensemble/AdaBoost.php new file mode 100644 index 00000000..70440a69 --- /dev/null +++ b/src/Phpml/Classification/Ensemble/AdaBoost.php @@ -0,0 +1,190 @@ +maxIterations = $maxIterations; + } + + /** + * @param array $samples + * @param array $targets + */ + public function train(array $samples, array $targets) + { + // Initialize usual variables + $this->labels = array_keys(array_count_values($targets)); + if (count($this->labels) != 2) { + throw new \Exception("AdaBoost is a binary classifier and can only classify between two classes"); + } + + // Set all target values to either -1 or 1 + $this->labels = [1 => $this->labels[0], -1 => $this->labels[1]]; + foreach ($targets as $target) { + $this->targets[] = $target == $this->labels[1] ? 1 : -1; + } + + $this->samples = array_merge($this->samples, $samples); + $this->featureCount = count($samples[0]); + $this->sampleCount = count($this->samples); + + // Initialize AdaBoost parameters + $this->weights = array_fill(0, $this->sampleCount, 1.0 / $this->sampleCount); + $this->classifiers = []; + $this->alpha = []; + + // Execute the algorithm for a maximum number of iterations + $currIter = 0; + while ($this->maxIterations > $currIter++) { + // Determine the best 'weak' classifier based on current weights + // and update alpha & weight values at each iteration + list($classifier, $errorRate) = $this->getBestClassifier(); + $alpha = $this->calculateAlpha($errorRate); + $this->updateWeights($classifier, $alpha); + + $this->classifiers[] = $classifier; + $this->alpha[] = $alpha; + } + } + + /** + * Returns the classifier with the lowest error rate with the + * consideration of current sample weights + * + * @return Classifier + */ + protected function getBestClassifier() + { + // This method works only for "DecisionStump" classifier, for now. + // As a future task, it will be generalized enough to work with other + // classifiers as well + $minErrorRate = 1.0; + $bestClassifier = null; + for ($i=0; $i < $this->featureCount; $i++) { + $stump = new DecisionStump($i); + $stump->setSampleWeights($this->weights); + $stump->train($this->samples, $this->targets); + + $errorRate = $stump->getTrainingErrorRate(); + if ($errorRate < $minErrorRate) { + $bestClassifier = $stump; + $minErrorRate = $errorRate; + } + } + + return [$bestClassifier, $minErrorRate]; + } + + /** + * Calculates alpha of a classifier + * + * @param float $errorRate + * @return float + */ + protected function calculateAlpha(float $errorRate) + { + if ($errorRate == 0) { + $errorRate = 1e-10; + } + return 0.5 * log((1 - $errorRate) / $errorRate); + } + + /** + * Updates the sample weights + * + * @param DecisionStump $classifier + * @param float $alpha + */ + protected function updateWeights(DecisionStump $classifier, float $alpha) + { + $sumOfWeights = array_sum($this->weights); + $weightsT1 = []; + foreach ($this->weights as $index => $weight) { + $desired = $this->targets[$index]; + $output = $classifier->predict($this->samples[$index]); + + $weight *= exp(-$alpha * $desired * $output) / $sumOfWeights; + + $weightsT1[] = $weight; + } + + $this->weights = $weightsT1; + } + + /** + * @param array $sample + * @return mixed + */ + public function predictSample(array $sample) + { + $sum = 0; + foreach ($this->alpha as $index => $alpha) { + $h = $this->classifiers[$index]->predict($sample); + $sum += $h * $alpha; + } + + return $this->labels[ $sum > 0 ? 1 : -1]; + } +} diff --git a/src/Phpml/Classification/Linear/Adaline.php b/src/Phpml/Classification/Linear/Adaline.php index 94283d97..aeff95e2 100644 --- a/src/Phpml/Classification/Linear/Adaline.php +++ b/src/Phpml/Classification/Linear/Adaline.php @@ -8,7 +8,6 @@ use Phpml\Helper\Trainable; use Phpml\Classification\Classifier; use Phpml\Classification\Linear\Perceptron; -use Phpml\Preprocessing\Normalizer; class Adaline extends Perceptron { @@ -38,11 +37,6 @@ class Adaline extends Perceptron */ protected $trainingType; - /** - * @var Normalizer - */ - private $normalizer; - /** * Initalize an Adaline (ADAptive LInear NEuron) classifier with given learning rate and maximum * number of iterations used while training the classifier
@@ -58,29 +52,13 @@ class Adaline extends Perceptron public function __construct(float $learningRate = 0.001, int $maxIterations = 1000, bool $normalizeInputs = true, int $trainingType = self::BATCH_TRAINING) { - if ($normalizeInputs) { - $this->normalizer = new Normalizer(Normalizer::NORM_STD); - } - if (! in_array($trainingType, [self::BATCH_TRAINING, self::ONLINE_TRAINING])) { throw new \Exception("Adaline can only be trained with batch and online/stochastic gradient descent algorithm"); } - $this->trainingType = $trainingType; - parent::__construct($learningRate, $maxIterations); - } - - /** - * @param array $samples - * @param array $targets - */ - public function train(array $samples, array $targets) - { - if ($this->normalizer) { - $this->normalizer->transform($samples); - } + $this->trainingType = $trainingType; - parent::train($samples, $targets); + parent::__construct($learningRate, $maxIterations, $normalizeInputs); } /** @@ -100,22 +78,8 @@ protected function runTraining() while ($this->maxIterations > $currIter++) { $outputs = array_map([$this, 'output'], $this->samples); $updates = array_map([$this, 'gradient'], $this->targets, $outputs); - $sum = array_sum($updates); - - // Updates all weights at once - for ($i=0; $i <= $this->featureCount; $i++) { - if ($i == 0) { - $this->weights[0] += $this->learningRate * $sum; - } else { - $col = array_column($this->samples, $i - 1); - $error = 0; - foreach ($col as $index => $val) { - $error += $val * $updates[$index]; - } - - $this->weights[$i] += $this->learningRate * $error; - } - } + + $this->updateWeights($updates); } } @@ -132,17 +96,27 @@ protected function gradient($desired, $output) } /** - * @param array $sample - * @return mixed + * Updates the weights of the network given the direction of the + * gradient for each sample + * + * @param array $updates */ - public function predictSample(array $sample) + protected function updateWeights(array $updates) { - if ($this->normalizer) { - $samples = [$sample]; - $this->normalizer->transform($samples); - $sample = $samples[0]; - } + // Updates all weights at once + for ($i=0; $i <= $this->featureCount; $i++) { + if ($i == 0) { + $this->weights[0] += $this->learningRate * array_sum($updates); + } else { + $col = array_column($this->samples, $i - 1); + + $error = 0; + foreach ($col as $index => $val) { + $error += $val * $updates[$index]; + } - return parent::predictSample($sample); + $this->weights[$i] += $this->learningRate * $error; + } + } } } diff --git a/src/Phpml/Classification/Linear/DecisionStump.php b/src/Phpml/Classification/Linear/DecisionStump.php index 18d44497..1220d48d 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -8,6 +8,7 @@ use Phpml\Helper\Trainable; use Phpml\Classification\Classifier; use Phpml\Classification\DecisionTree; +use Phpml\Classification\DecisionTree\DecisionTreeLeaf; class DecisionStump extends DecisionTree { @@ -19,6 +20,22 @@ class DecisionStump extends DecisionTree protected $columnIndex; + /** + * Sample weights : If used the optimization on the decision value + * will take these weights into account. If not given, all samples + * will be weighed with the same value of 1 + * + * @var array + */ + protected $weights = null; + + /** + * Lowest error rate obtained while training/optimizing the model + * + * @var float + */ + protected $trainingErrorRate; + /** * A DecisionStump classifier is a one-level deep DecisionTree. It is generally * used with ensemble algorithms as in the weak classifier role.
@@ -42,8 +59,7 @@ public function __construct(int $columnIndex = -1) */ public function train(array $samples, array $targets) { - // Check if a column index was given - if ($this->columnIndex >= 0 && $this->columnIndex > count($samples[0]) - 1) { + if ($this->columnIndex > count($samples[0]) - 1) { $this->columnIndex = -1; } @@ -51,6 +67,113 @@ public function train(array $samples, array $targets) $this->setSelectedFeatures([$this->columnIndex]); } + if ($this->weights) { + $numWeights = count($this->weights); + if ($numWeights != count($samples)) { + throw new \Exception("Number of sample weights does not match with number of samples"); + } + } else { + $this->weights = array_fill(0, count($samples), 1); + } + parent::train($samples, $targets); + + $this->columnIndex = $this->tree->columnIndex; + + // For numerical values, try to optimize the value by finding a different threshold value + if ($this->columnTypes[$this->columnIndex] == self::CONTINUOS) { + $this->optimizeDecision($samples, $targets); + } + } + + /** + * Used to set sample weights. + * + * @param array $weights + */ + public function setSampleWeights(array $weights) + { + $this->weights = $weights; + } + + /** + * Returns the training error rate, the proportion of wrong predictions + * over the total number of samples + * + * @return float + */ + public function getTrainingErrorRate() + { + return $this->trainingErrorRate; + } + + /** + * Tries to optimize the threshold by probing a range of different values + * between the minimum and maximum values in the selected column + * + * @param array $samples + * @param array $targets + */ + protected function optimizeDecision(array $samples, array $targets) + { + $values = array_column($samples, $this->columnIndex); + $minValue = min($values); + $maxValue = max($values); + $stepSize = ($maxValue - $minValue) / 100.0; + + $leftLabel = $this->tree->leftLeaf->classValue; + $rightLabel= $this->tree->rightLeaf->classValue; + + $bestOperator = $this->tree->operator; + $bestThreshold = $this->tree->numericValue; + $bestErrorRate = $this->calculateErrorRate( + $bestThreshold, $bestOperator, $values, $targets, $leftLabel, $rightLabel); + + foreach (['<=', '>'] as $operator) { + for ($step = $minValue; $step <= $maxValue; $step+= $stepSize) { + $threshold = (float)$step; + $errorRate = $this->calculateErrorRate( + $threshold, $operator, $values, $targets, $leftLabel, $rightLabel); + + if ($errorRate < $bestErrorRate) { + $bestErrorRate = $errorRate; + $bestThreshold = $threshold; + $bestOperator = $operator; + } + }// for + } + + // Update the tree node value + $this->tree->numericValue = $bestThreshold; + $this->tree->operator = $bestOperator; + $this->tree->value = "$bestOperator $bestThreshold"; + $this->trainingErrorRate = $bestErrorRate; + } + + /** + * Calculates the ratio of wrong predictions based on the new threshold + * value given as the parameter + * + * @param float $threshold + * @param string $operator + * @param array $values + * @param array $targets + * @param mixed $leftLabel + * @param mixed $rightLabel + */ + protected function calculateErrorRate(float $threshold, string $operator, array $values, array $targets, $leftLabel, $rightLabel) + { + $total = (float) array_sum($this->weights); + $wrong = 0; + + foreach ($values as $index => $value) { + eval("\$predicted = \$value $operator \$threshold ? \$leftLabel : \$rightLabel;"); + + if ($predicted != $targets[$index]) { + $wrong += $this->weights[$index]; + } + } + + return $wrong / $total; } } diff --git a/src/Phpml/Classification/Linear/Perceptron.php b/src/Phpml/Classification/Linear/Perceptron.php index 963638e6..78a204a1 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -7,6 +7,7 @@ use Phpml\Helper\Predictable; use Phpml\Helper\Trainable; use Phpml\Classification\Classifier; +use Phpml\Preprocessing\Normalizer; class Perceptron implements Classifier { @@ -55,6 +56,11 @@ class Perceptron implements Classifier */ protected $maxIterations; + /** + * @var Normalizer + */ + protected $normalizer; + /** * Initalize a perceptron classifier with given learning rate and maximum * number of iterations used while training the perceptron
@@ -64,7 +70,8 @@ class Perceptron implements Classifier * @param int $learningRate * @param int $maxIterations */ - public function __construct(float $learningRate = 0.001, int $maxIterations = 1000) + public function __construct(float $learningRate = 0.001, int $maxIterations = 1000, + bool $normalizeInputs = true) { if ($learningRate <= 0.0 || $learningRate > 1.0) { throw new \Exception("Learning rate should be a float value between 0.0(exclusive) and 1.0(inclusive)"); @@ -74,6 +81,10 @@ public function __construct(float $learningRate = 0.001, int $maxIterations = 10 throw new \Exception("Maximum number of iterations should be an integer greater than 0"); } + if ($normalizeInputs) { + $this->normalizer = new Normalizer(Normalizer::NORM_STD); + } + $this->learningRate = $learningRate; $this->maxIterations = $maxIterations; } @@ -89,6 +100,10 @@ public function train(array $samples, array $targets) throw new \Exception("Perceptron is for only binary (two-class) classification"); } + if ($this->normalizer) { + $this->normalizer->transform($samples); + } + // Set all target values to either -1 or 1 $this->labels = [1 => $this->labels[0], -1 => $this->labels[1]]; foreach ($targets as $target) { @@ -167,6 +182,12 @@ protected function outputClass(array $sample) */ protected function predictSample(array $sample) { + if ($this->normalizer) { + $samples = [$sample]; + $this->normalizer->transform($samples); + $sample = $samples[0]; + } + $predictedClass = $this->outputClass($sample); return $this->labels[ $predictedClass ]; diff --git a/tests/Phpml/Classification/Ensemble/AdaBoostTest.php b/tests/Phpml/Classification/Ensemble/AdaBoostTest.php new file mode 100644 index 00000000..c9e4d86a --- /dev/null +++ b/tests/Phpml/Classification/Ensemble/AdaBoostTest.php @@ -0,0 +1,64 @@ +train($samples, $targets); + $this->assertEquals(0, $classifier->predict([0.1, 0.2])); + $this->assertEquals(0, $classifier->predict([0.1, 0.99])); + $this->assertEquals(1, $classifier->predict([1.1, 0.8])); + + // OR problem + $samples = [[0, 0], [0.1, 0.2], [0.2, 0.1], [1, 0], [0, 1], [1, 1]]; + $targets = [0, 0, 0, 1, 1, 1]; + $classifier = new AdaBoost(); + $classifier->train($samples, $targets); + $this->assertEquals(0, $classifier->predict([0.1, 0.2])); + $this->assertEquals(1, $classifier->predict([0.1, 0.99])); + $this->assertEquals(1, $classifier->predict([1.1, 0.8])); + + // XOR problem + $samples = [[0.1, 0.2], [1., 1.], [0.9, 0.8], [0., 1.], [1., 0.], [0.2, 0.8]]; + $targets = [0, 0, 0, 1, 1, 1]; + $classifier = new AdaBoost(5); + $classifier->train($samples, $targets); + $this->assertEquals(0, $classifier->predict([0.1, 0.1])); + $this->assertEquals(1, $classifier->predict([0, 0.999])); + $this->assertEquals(0, $classifier->predict([1.1, 0.8])); + + return $classifier; + } + + public function testSaveAndRestore() + { + // Instantinate new Percetron trained for OR problem + $samples = [[0, 0], [1, 0], [0, 1], [1, 1]]; + $targets = [0, 1, 1, 1]; + $classifier = new AdaBoost(); + $classifier->train($samples, $targets); + $testSamples = [[0, 1], [1, 1], [0.2, 0.1]]; + $predicted = $classifier->predict($testSamples); + + $filename = 'adaboost-test-'.rand(100, 999).'-'.uniqid(); + $filepath = tempnam(sys_get_temp_dir(), $filename); + $modelManager = new ModelManager(); + $modelManager->saveToFile($classifier, $filepath); + + $restoredClassifier = $modelManager->restoreFromFile($filepath); + $this->assertEquals($classifier, $restoredClassifier); + $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + } +} From e8c6005aec062abb60381ed033c9e2279234ea85 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 23 Feb 2017 20:59:30 +0100 Subject: [PATCH 158/328] Update changelog and cs fixes --- CHANGELOG.md | 9 +++++++-- README.md | 5 +++++ src/Phpml/Exception/MatrixException.php | 2 +- tests/Phpml/Math/MatrixTest.php | 4 ++-- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11b0807a..7829691a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,13 @@ CHANGELOG This changelog references the relevant changes done in PHP-ML library. -* 0.3.1 (in plan/progress) - * feature [Regression] - SSE, SSTo, SSR - sum of the squared +* 0.4.0 (2017-02-23) + * feature [Classification] - Ensemble Classifiers : Bagging and RandomForest by Mustafa Karabulut + * feature [Classification] - RandomForest::getFeatureImportances() method by Mustafa Karabulut + * feature [Classification] - Linear classifiers: Perceptron, Adaline, DecisionStump by Mustafa Karabulut + * feature [Classification] - AdaBoost algorithm by Mustafa Karabulut + * bug [Math] - Check if matrix is singular doing inverse by Povilas Susinskas + * optimization - Euclidean optimization by Mustafa Karabulut * 0.3.0 (2017-02-04) * feature [Persistency] - ModelManager - save and restore trained models by David Monllaó diff --git a/README.md b/README.md index 62a906d4..c362a2c1 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,11 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * Ensemble Algorithms * Bagging (Bootstrap Aggregating) * Random Forest + * AdaBoost + * Linear + * Adaline + * Decision Stump + * Perceptron * Regression * [Least Squares](http://php-ml.readthedocs.io/en/latest/machine-learning/regression/least-squares/) * [SVR](http://php-ml.readthedocs.io/en/latest/machine-learning/regression/svr/) diff --git a/src/Phpml/Exception/MatrixException.php b/src/Phpml/Exception/MatrixException.php index 28158048..1b016597 100644 --- a/src/Phpml/Exception/MatrixException.php +++ b/src/Phpml/Exception/MatrixException.php @@ -27,6 +27,6 @@ public static function columnOutOfRange() */ public static function singularMatrix() { - return new self('Matrix is singular'); + return new self('Matrix is singular'); } } diff --git a/tests/Phpml/Math/MatrixTest.php b/tests/Phpml/Math/MatrixTest.php index 48a6fe94..0b466122 100644 --- a/tests/Phpml/Math/MatrixTest.php +++ b/tests/Phpml/Math/MatrixTest.php @@ -146,13 +146,13 @@ public function testThrowExceptionWhenInverseIfArrayIsNotSquare() */ public function testThrowExceptionWhenInverseIfMatrixIsSingular() { - $matrix = new Matrix([ + $matrix = new Matrix([ [0, 0, 0], [0, 0, 0], [0, 0, 0], ]); - $matrix->inverse(); + $matrix->inverse(); } public function testInverseMatrix() From c028a73985434058171f0c95ca47b7375cf89634 Mon Sep 17 00:00:00 2001 From: Mustafa Karabulut Date: Tue, 28 Feb 2017 23:45:18 +0300 Subject: [PATCH 159/328] AdaBoost improvements (#53) * AdaBoost improvements * AdaBoost improvements & test case resolved * Some coding style fixes --- src/Phpml/Classification/DecisionTree.php | 11 +- .../Classification/Ensemble/AdaBoost.php | 119 +++++++-- src/Phpml/Classification/Linear/Adaline.php | 6 + .../Classification/Linear/DecisionStump.php | 245 +++++++++++++----- .../Classification/Linear/Perceptron.php | 71 ++++- .../Classification/WeightedClassifier.php | 20 ++ .../Classification/Linear/PerceptronTest.php | 12 +- 7 files changed, 385 insertions(+), 99 deletions(-) create mode 100644 src/Phpml/Classification/WeightedClassifier.php diff --git a/src/Phpml/Classification/DecisionTree.php b/src/Phpml/Classification/DecisionTree.php index 231d766a..b2b4db34 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -110,12 +110,13 @@ public function train(array $samples, array $targets) } } - protected function getColumnTypes(array $samples) + public static function getColumnTypes(array $samples) { $types = []; - for ($i=0; $i<$this->featureCount; $i++) { + $featureCount = count($samples[0]); + for ($i=0; $i < $featureCount; $i++) { $values = array_column($samples, $i); - $isCategorical = $this->isCategoricalColumn($values); + $isCategorical = self::isCategoricalColumn($values); $types[] = $isCategorical ? self::NOMINAL : self::CONTINUOS; } return $types; @@ -327,13 +328,13 @@ protected function preprocess(array $samples) * @param array $columnValues * @return bool */ - protected function isCategoricalColumn(array $columnValues) + protected static function isCategoricalColumn(array $columnValues) { $count = count($columnValues); // There are two main indicators that *may* show whether a // column is composed of discrete set of values: - // 1- Column may contain string values and not float values + // 1- Column may contain string values and non-float values // 2- Number of unique values in the column is only a small fraction of // all values in that column (Lower than or equal to %20 of all values) $numericValues = array_filter($columnValues, 'is_numeric'); diff --git a/src/Phpml/Classification/Ensemble/AdaBoost.php b/src/Phpml/Classification/Ensemble/AdaBoost.php index 70440a69..3d1e4187 100644 --- a/src/Phpml/Classification/Ensemble/AdaBoost.php +++ b/src/Phpml/Classification/Ensemble/AdaBoost.php @@ -5,6 +5,9 @@ namespace Phpml\Classification\Ensemble; use Phpml\Classification\Linear\DecisionStump; +use Phpml\Classification\WeightedClassifier; +use Phpml\Math\Statistic\Mean; +use Phpml\Math\Statistic\StandardDeviation; use Phpml\Classification\Classifier; use Phpml\Helper\Predictable; use Phpml\Helper\Trainable; @@ -44,7 +47,7 @@ class AdaBoost implements Classifier protected $weights = []; /** - * Base classifiers + * List of selected 'weak' classifiers * * @var array */ @@ -57,17 +60,39 @@ class AdaBoost implements Classifier */ protected $alpha = []; + /** + * @var string + */ + protected $baseClassifier = DecisionStump::class; + + /** + * @var array + */ + protected $classifierOptions = []; + /** * ADAptive BOOSTing (AdaBoost) is an ensemble algorithm to * improve classification performance of 'weak' classifiers such as * DecisionStump (default base classifier of AdaBoost). * */ - public function __construct(int $maxIterations = 30) + public function __construct(int $maxIterations = 50) { $this->maxIterations = $maxIterations; } + /** + * Sets the base classifier that will be used for boosting (default = DecisionStump) + * + * @param string $baseClassifier + * @param array $classifierOptions + */ + public function setBaseClassifier(string $baseClassifier = DecisionStump::class, array $classifierOptions = []) + { + $this->baseClassifier = $baseClassifier; + $this->classifierOptions = $classifierOptions; + } + /** * @param array $samples * @param array $targets @@ -77,7 +102,7 @@ public function train(array $samples, array $targets) // Initialize usual variables $this->labels = array_keys(array_count_values($targets)); if (count($this->labels) != 2) { - throw new \Exception("AdaBoost is a binary classifier and can only classify between two classes"); + throw new \Exception("AdaBoost is a binary classifier and can classify between two classes only"); } // Set all target values to either -1 or 1 @@ -98,9 +123,12 @@ public function train(array $samples, array $targets) // Execute the algorithm for a maximum number of iterations $currIter = 0; while ($this->maxIterations > $currIter++) { + // Determine the best 'weak' classifier based on current weights - // and update alpha & weight values at each iteration - list($classifier, $errorRate) = $this->getBestClassifier(); + $classifier = $this->getBestClassifier(); + $errorRate = $this->evaluateClassifier($classifier); + + // Update alpha & weight values at each iteration $alpha = $this->calculateAlpha($errorRate); $this->updateWeights($classifier, $alpha); @@ -117,24 +145,71 @@ public function train(array $samples, array $targets) */ protected function getBestClassifier() { - // This method works only for "DecisionStump" classifier, for now. - // As a future task, it will be generalized enough to work with other - // classifiers as well - $minErrorRate = 1.0; - $bestClassifier = null; - for ($i=0; $i < $this->featureCount; $i++) { - $stump = new DecisionStump($i); - $stump->setSampleWeights($this->weights); - $stump->train($this->samples, $this->targets); - - $errorRate = $stump->getTrainingErrorRate(); - if ($errorRate < $minErrorRate) { - $bestClassifier = $stump; - $minErrorRate = $errorRate; + $ref = new \ReflectionClass($this->baseClassifier); + if ($this->classifierOptions) { + $classifier = $ref->newInstanceArgs($this->classifierOptions); + } else { + $classifier = $ref->newInstance(); + } + + if (is_subclass_of($classifier, WeightedClassifier::class)) { + $classifier->setSampleWeights($this->weights); + $classifier->train($this->samples, $this->targets); + } else { + list($samples, $targets) = $this->resample(); + $classifier->train($samples, $targets); + } + + return $classifier; + } + + /** + * Resamples the dataset in accordance with the weights and + * returns the new dataset + * + * @return array + */ + protected function resample() + { + $weights = $this->weights; + $std = StandardDeviation::population($weights); + $mean= Mean::arithmetic($weights); + $min = min($weights); + $minZ= (int)round(($min - $mean) / $std); + + $samples = []; + $targets = []; + foreach ($weights as $index => $weight) { + $z = (int)round(($weight - $mean) / $std) - $minZ + 1; + for ($i=0; $i < $z; $i++) { + if (rand(0, 1) == 0) { + continue; + } + $samples[] = $this->samples[$index]; + $targets[] = $this->targets[$index]; + } + } + + return [$samples, $targets]; + } + + /** + * Evaluates the classifier and returns the classification error rate + * + * @param Classifier $classifier + */ + protected function evaluateClassifier(Classifier $classifier) + { + $total = (float) array_sum($this->weights); + $wrong = 0; + foreach ($this->samples as $index => $sample) { + $predicted = $classifier->predict($sample); + if ($predicted != $this->targets[$index]) { + $wrong += $this->weights[$index]; } } - return [$bestClassifier, $minErrorRate]; + return $wrong / $total; } /** @@ -154,10 +229,10 @@ protected function calculateAlpha(float $errorRate) /** * Updates the sample weights * - * @param DecisionStump $classifier + * @param Classifier $classifier * @param float $alpha */ - protected function updateWeights(DecisionStump $classifier, float $alpha) + protected function updateWeights(Classifier $classifier, float $alpha) { $sumOfWeights = array_sum($this->weights); $weightsT1 = []; diff --git a/src/Phpml/Classification/Linear/Adaline.php b/src/Phpml/Classification/Linear/Adaline.php index aeff95e2..13674f15 100644 --- a/src/Phpml/Classification/Linear/Adaline.php +++ b/src/Phpml/Classification/Linear/Adaline.php @@ -76,10 +76,16 @@ protected function runTraining() // Batch learning is executed: $currIter = 0; while ($this->maxIterations > $currIter++) { + $weights = $this->weights; + $outputs = array_map([$this, 'output'], $this->samples); $updates = array_map([$this, 'gradient'], $this->targets, $outputs); $this->updateWeights($updates); + + if ($this->earlyStop($weights)) { + break; + } } } diff --git a/src/Phpml/Classification/Linear/DecisionStump.php b/src/Phpml/Classification/Linear/DecisionStump.php index 1220d48d..1605a20c 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -6,18 +6,19 @@ use Phpml\Helper\Predictable; use Phpml\Helper\Trainable; -use Phpml\Classification\Classifier; +use Phpml\Classification\WeightedClassifier; use Phpml\Classification\DecisionTree; -use Phpml\Classification\DecisionTree\DecisionTreeLeaf; -class DecisionStump extends DecisionTree +class DecisionStump extends WeightedClassifier { use Trainable, Predictable; + const AUTO_SELECT = -1; + /** * @var int */ - protected $columnIndex; + protected $givenColumnIndex; /** @@ -36,6 +37,31 @@ class DecisionStump extends DecisionTree */ protected $trainingErrorRate; + /** + * @var int + */ + protected $column; + + /** + * @var mixed + */ + protected $value; + + /** + * @var string + */ + protected $operator; + + /** + * @var array + */ + protected $columnTypes; + + /** + * @var float + */ + protected $numSplitCount = 10.0; + /** * A DecisionStump classifier is a one-level deep DecisionTree. It is generally * used with ensemble algorithms as in the weak classifier role.
@@ -46,11 +72,9 @@ class DecisionStump extends DecisionTree * * @param int $columnIndex */ - public function __construct(int $columnIndex = -1) + public function __construct(int $columnIndex = self::AUTO_SELECT) { - $this->columnIndex = $columnIndex; - - parent::__construct(1); + $this->givenColumnIndex = $columnIndex; } /** @@ -59,95 +83,167 @@ public function __construct(int $columnIndex = -1) */ public function train(array $samples, array $targets) { - if ($this->columnIndex > count($samples[0]) - 1) { - $this->columnIndex = -1; + $this->samples = array_merge($this->samples, $samples); + $this->targets = array_merge($this->targets, $targets); + + // DecisionStump is capable of classifying between two classes only + $labels = array_count_values($this->targets); + $this->labels = array_keys($labels); + if (count($this->labels) != 2) { + throw new \Exception("DecisionStump can classify between two classes only:" . implode(',', $this->labels)); } - if ($this->columnIndex >= 0) { - $this->setSelectedFeatures([$this->columnIndex]); + // If a column index is given, it should be among the existing columns + if ($this->givenColumnIndex > count($samples[0]) - 1) { + $this->givenColumnIndex = self::AUTO_SELECT; } + // Check the size of the weights given. + // If none given, then assign 1 as a weight to each sample if ($this->weights) { $numWeights = count($this->weights); - if ($numWeights != count($samples)) { + if ($numWeights != count($this->samples)) { throw new \Exception("Number of sample weights does not match with number of samples"); } } else { $this->weights = array_fill(0, count($samples), 1); } - parent::train($samples, $targets); + // Determine type of each column as either "continuous" or "nominal" + $this->columnTypes = DecisionTree::getColumnTypes($this->samples); - $this->columnIndex = $this->tree->columnIndex; + // Try to find the best split in the columns of the dataset + // by calculating error rate for each split point in each column + $columns = range(0, count($samples[0]) - 1); + if ($this->givenColumnIndex != self::AUTO_SELECT) { + $columns = [$this->givenColumnIndex]; + } - // For numerical values, try to optimize the value by finding a different threshold value - if ($this->columnTypes[$this->columnIndex] == self::CONTINUOS) { - $this->optimizeDecision($samples, $targets); + $bestSplit = [ + 'value' => 0, 'operator' => '', + 'column' => 0, 'trainingErrorRate' => 1.0]; + foreach ($columns as $col) { + if ($this->columnTypes[$col] == DecisionTree::CONTINUOS) { + $split = $this->getBestNumericalSplit($col); + } else { + $split = $this->getBestNominalSplit($col); + } + + if ($split['trainingErrorRate'] < $bestSplit['trainingErrorRate']) { + $bestSplit = $split; + } } - } - /** - * Used to set sample weights. - * - * @param array $weights - */ - public function setSampleWeights(array $weights) - { - $this->weights = $weights; + // Assign determined best values to the stump + foreach ($bestSplit as $name => $value) { + $this->{$name} = $value; + } } /** - * Returns the training error rate, the proportion of wrong predictions - * over the total number of samples + * While finding best split point for a numerical valued column, + * DecisionStump looks for equally distanced values between minimum and maximum + * values in the column. Given $count value determines how many split + * points to be probed. The more split counts, the better performance but + * worse processing time (Default value is 10.0) * - * @return float + * @param float $count */ - public function getTrainingErrorRate() + public function setNumericalSplitCount(float $count) { - return $this->trainingErrorRate; + $this->numSplitCount = $count; } /** - * Tries to optimize the threshold by probing a range of different values - * between the minimum and maximum values in the selected column + * Determines best split point for the given column * - * @param array $samples - * @param array $targets + * @param int $col + * + * @return array */ - protected function optimizeDecision(array $samples, array $targets) + protected function getBestNumericalSplit(int $col) { - $values = array_column($samples, $this->columnIndex); + $values = array_column($this->samples, $col); $minValue = min($values); $maxValue = max($values); - $stepSize = ($maxValue - $minValue) / 100.0; + $stepSize = ($maxValue - $minValue) / $this->numSplitCount; - $leftLabel = $this->tree->leftLeaf->classValue; - $rightLabel= $this->tree->rightLeaf->classValue; - - $bestOperator = $this->tree->operator; - $bestThreshold = $this->tree->numericValue; - $bestErrorRate = $this->calculateErrorRate( - $bestThreshold, $bestOperator, $values, $targets, $leftLabel, $rightLabel); + $split = null; foreach (['<=', '>'] as $operator) { + // Before trying all possible split points, let's first try + // the average value for the cut point + $threshold = array_sum($values) / (float) count($values); + $errorRate = $this->calculateErrorRate($threshold, $operator, $values); + if ($split == null || $errorRate < $split['trainingErrorRate']) { + $split = ['value' => $threshold, 'operator' => $operator, + 'column' => $col, 'trainingErrorRate' => $errorRate]; + } + + // Try other possible points one by one for ($step = $minValue; $step <= $maxValue; $step+= $stepSize) { $threshold = (float)$step; - $errorRate = $this->calculateErrorRate( - $threshold, $operator, $values, $targets, $leftLabel, $rightLabel); + $errorRate = $this->calculateErrorRate($threshold, $operator, $values); + if ($errorRate < $split['trainingErrorRate']) { + $split = ['value' => $threshold, 'operator' => $operator, + 'column' => $col, 'trainingErrorRate' => $errorRate]; + } + }// for + } + + return $split; + } + + /** + * + * @param int $col + * + * @return array + */ + protected function getBestNominalSplit(int $col) + { + $values = array_column($this->samples, $col); + $valueCounts = array_count_values($values); + $distinctVals= array_keys($valueCounts); + + $split = null; + + foreach (['=', '!='] as $operator) { + foreach ($distinctVals as $val) { + $errorRate = $this->calculateErrorRate($val, $operator, $values); - if ($errorRate < $bestErrorRate) { - $bestErrorRate = $errorRate; - $bestThreshold = $threshold; - $bestOperator = $operator; + if ($split == null || $split['trainingErrorRate'] < $errorRate) { + $split = ['value' => $val, 'operator' => $operator, + 'column' => $col, 'trainingErrorRate' => $errorRate]; } }// for } - // Update the tree node value - $this->tree->numericValue = $bestThreshold; - $this->tree->operator = $bestOperator; - $this->tree->value = "$bestOperator $bestThreshold"; - $this->trainingErrorRate = $bestErrorRate; + return $split; + } + + + /** + * + * @param type $leftValue + * @param type $operator + * @param type $rightValue + * + * @return boolean + */ + protected function evaluate($leftValue, $operator, $rightValue) + { + switch ($operator) { + case '>': return $leftValue > $rightValue; + case '>=': return $leftValue >= $rightValue; + case '<': return $leftValue < $rightValue; + case '<=': return $leftValue <= $rightValue; + case '=': return $leftValue == $rightValue; + case '!=': + case '<>': return $leftValue != $rightValue; + } + + return false; } /** @@ -157,23 +253,42 @@ protected function optimizeDecision(array $samples, array $targets) * @param float $threshold * @param string $operator * @param array $values - * @param array $targets - * @param mixed $leftLabel - * @param mixed $rightLabel */ - protected function calculateErrorRate(float $threshold, string $operator, array $values, array $targets, $leftLabel, $rightLabel) + protected function calculateErrorRate(float $threshold, string $operator, array $values) { $total = (float) array_sum($this->weights); - $wrong = 0; - + $wrong = 0.0; + $leftLabel = $this->labels[0]; + $rightLabel= $this->labels[1]; foreach ($values as $index => $value) { - eval("\$predicted = \$value $operator \$threshold ? \$leftLabel : \$rightLabel;"); + if ($this->evaluate($threshold, $operator, $value)) { + $predicted = $leftLabel; + } else { + $predicted = $rightLabel; + } - if ($predicted != $targets[$index]) { + if ($predicted != $this->targets[$index]) { $wrong += $this->weights[$index]; } } return $wrong / $total; } + + /** + * @param array $sample + * @return mixed + */ + protected function predictSample(array $sample) + { + if ($this->evaluate($this->value, $this->operator, $sample[$this->column])) { + return $this->labels[0]; + } + return $this->labels[1]; + } + + public function __toString() + { + return "$this->column $this->operator $this->value"; + } } diff --git a/src/Phpml/Classification/Linear/Perceptron.php b/src/Phpml/Classification/Linear/Perceptron.php index 78a204a1..bc31da17 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -61,6 +61,14 @@ class Perceptron implements Classifier */ protected $normalizer; + /** + * Minimum amount of change in the weights between iterations + * that needs to be obtained to continue the training + * + * @var float + */ + protected $threshold = 1e-5; + /** * Initalize a perceptron classifier with given learning rate and maximum * number of iterations used while training the perceptron
@@ -89,6 +97,20 @@ public function __construct(float $learningRate = 0.001, int $maxIterations = 10 $this->maxIterations = $maxIterations; } + /** + * Sets minimum value for the change in the weights + * between iterations to continue the iterations.
+ * + * If the weight change is less than given value then the + * algorithm will stop training + * + * @param float $threshold + */ + public function setChangeThreshold(float $threshold = 1e-5) + { + $this->threshold = $threshold; + } + /** * @param array $samples * @param array $targets @@ -97,7 +119,7 @@ public function train(array $samples, array $targets) { $this->labels = array_keys(array_count_values($targets)); if (count($this->labels) > 2) { - throw new \Exception("Perceptron is for only binary (two-class) classification"); + throw new \Exception("Perceptron is for binary (two-class) classification only"); } if ($this->normalizer) { @@ -130,11 +152,20 @@ public function train(array $samples, array $targets) protected function runTraining() { $currIter = 0; + $bestWeights = null; + $bestScore = count($this->samples); + $bestWeightIter = 0; + while ($this->maxIterations > $currIter++) { + $weights = $this->weights; + $misClassified = 0; foreach ($this->samples as $index => $sample) { $target = $this->targets[$index]; $prediction = $this->{static::$errorFunction}($sample); $update = $target - $prediction; + if ($target != $prediction) { + $misClassified++; + } // Update bias $this->weights[0] += $update * $this->learningRate; // Bias // Update other weights @@ -142,7 +173,45 @@ protected function runTraining() $this->weights[$i] += $update * $sample[$i - 1] * $this->learningRate; } } + + // Save the best weights in the "pocket" so that + // any future weights worse than this will be disregarded + if ($bestWeights == null || $misClassified <= $bestScore) { + $bestWeights = $weights; + $bestScore = $misClassified; + $bestWeightIter = $currIter; + } + + // Check for early stop + if ($this->earlyStop($weights)) { + break; + } + } + + // The weights in the pocket are better than or equal to the last state + // so, we use these weights + $this->weights = $bestWeights; + } + + /** + * @param array $oldWeights + * + * @return boolean + */ + protected function earlyStop($oldWeights) + { + // Check for early stop: No change larger than 1e-5 + $diff = array_map( + function ($w1, $w2) { + return abs($w1 - $w2) > 1e-5 ? 1 : 0; + }, + $oldWeights, $this->weights); + + if (array_sum($diff) == 0) { + return true; } + + return false; } /** diff --git a/src/Phpml/Classification/WeightedClassifier.php b/src/Phpml/Classification/WeightedClassifier.php new file mode 100644 index 00000000..36a294ec --- /dev/null +++ b/src/Phpml/Classification/WeightedClassifier.php @@ -0,0 +1,20 @@ +weights = $weights; + } +} diff --git a/tests/Phpml/Classification/Linear/PerceptronTest.php b/tests/Phpml/Classification/Linear/PerceptronTest.php index bf1b3847..64954f7b 100644 --- a/tests/Phpml/Classification/Linear/PerceptronTest.php +++ b/tests/Phpml/Classification/Linear/PerceptronTest.php @@ -13,20 +13,20 @@ class PerceptronTest extends TestCase public function testPredictSingleSample() { // AND problem - $samples = [[0, 0], [1, 0], [0, 1], [1, 1], [0.9, 0.8]]; + $samples = [[0, 0], [1, 0], [0, 1], [1, 1], [0.6, 0.6]]; $targets = [0, 0, 0, 1, 1]; $classifier = new Perceptron(0.001, 5000); $classifier->train($samples, $targets); $this->assertEquals(0, $classifier->predict([0.1, 0.2])); - $this->assertEquals(0, $classifier->predict([0.1, 0.99])); + $this->assertEquals(0, $classifier->predict([0, 1])); $this->assertEquals(1, $classifier->predict([1.1, 0.8])); // OR problem - $samples = [[0, 0], [0.1, 0.2], [1, 0], [0, 1], [1, 1]]; - $targets = [0, 0, 1, 1, 1]; - $classifier = new Perceptron(0.001, 5000); + $samples = [[0.1, 0.1], [0.4, 0.], [0., 0.3], [1, 0], [0, 1], [1, 1]]; + $targets = [0, 0, 0, 1, 1, 1]; + $classifier = new Perceptron(0.001, 5000, false); $classifier->train($samples, $targets); - $this->assertEquals(0, $classifier->predict([0, 0])); + $this->assertEquals(0, $classifier->predict([0., 0.])); $this->assertEquals(1, $classifier->predict([0.1, 0.99])); $this->assertEquals(1, $classifier->predict([1.1, 0.8])); From 63c63dfba2b8b16599369ea8528585f1af6d5b70 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 1 Mar 2017 10:16:15 +0100 Subject: [PATCH 160/328] Add no_unused_imports rule to cs-fixer --- .php_cs | 1 + src/Phpml/Classification/Ensemble/Bagging.php | 2 -- src/Phpml/Classification/Ensemble/RandomForest.php | 2 -- src/Phpml/Classification/Linear/Adaline.php | 3 --- src/Phpml/Classification/Linear/Perceptron.php | 1 - src/Phpml/Classification/WeightedClassifier.php | 6 +++--- tests/Phpml/Classification/Ensemble/BaggingTest.php | 1 - tests/Phpml/Classification/Ensemble/RandomForestTest.php | 3 --- 8 files changed, 4 insertions(+), 15 deletions(-) diff --git a/.php_cs b/.php_cs index 417cafa3..5a2dd57e 100644 --- a/.php_cs +++ b/.php_cs @@ -7,6 +7,7 @@ return PhpCsFixer\Config::create() 'array_syntax' => ['syntax' => 'short'], 'blank_line_after_opening_tag' => true, 'single_blank_line_before_namespace' => true, + 'no_unused_imports' => true ]) ->setFinder( PhpCsFixer\Finder::create() diff --git a/src/Phpml/Classification/Ensemble/Bagging.php b/src/Phpml/Classification/Ensemble/Bagging.php index d579b248..1bb20273 100644 --- a/src/Phpml/Classification/Ensemble/Bagging.php +++ b/src/Phpml/Classification/Ensemble/Bagging.php @@ -6,10 +6,8 @@ use Phpml\Helper\Predictable; use Phpml\Helper\Trainable; -use Phpml\Math\Statistic\Mean; use Phpml\Classification\Classifier; use Phpml\Classification\DecisionTree; -use Phpml\Classification\NaiveBayes; class Bagging implements Classifier { diff --git a/src/Phpml/Classification/Ensemble/RandomForest.php b/src/Phpml/Classification/Ensemble/RandomForest.php index 025badff..273eb21a 100644 --- a/src/Phpml/Classification/Ensemble/RandomForest.php +++ b/src/Phpml/Classification/Ensemble/RandomForest.php @@ -4,9 +4,7 @@ namespace Phpml\Classification\Ensemble; -use Phpml\Classification\Ensemble\Bagging; use Phpml\Classification\DecisionTree; -use Phpml\Classification\NaiveBayes; use Phpml\Classification\Classifier; class RandomForest extends Bagging diff --git a/src/Phpml/Classification/Linear/Adaline.php b/src/Phpml/Classification/Linear/Adaline.php index 13674f15..194451a8 100644 --- a/src/Phpml/Classification/Linear/Adaline.php +++ b/src/Phpml/Classification/Linear/Adaline.php @@ -4,10 +4,7 @@ namespace Phpml\Classification\Linear; -use Phpml\Helper\Predictable; -use Phpml\Helper\Trainable; use Phpml\Classification\Classifier; -use Phpml\Classification\Linear\Perceptron; class Adaline extends Perceptron { diff --git a/src/Phpml/Classification/Linear/Perceptron.php b/src/Phpml/Classification/Linear/Perceptron.php index bc31da17..e2c684dd 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -5,7 +5,6 @@ namespace Phpml\Classification\Linear; use Phpml\Helper\Predictable; -use Phpml\Helper\Trainable; use Phpml\Classification\Classifier; use Phpml\Preprocessing\Normalizer; diff --git a/src/Phpml/Classification/WeightedClassifier.php b/src/Phpml/Classification/WeightedClassifier.php index 36a294ec..c0ec045e 100644 --- a/src/Phpml/Classification/WeightedClassifier.php +++ b/src/Phpml/Classification/WeightedClassifier.php @@ -1,8 +1,8 @@ - Date: Sun, 5 Mar 2017 11:43:19 +0300 Subject: [PATCH 161/328] One-v-Rest Classification technique applied to linear classifiers (#54) * One-v-Rest Classification technique applied to linear classifiers * Fix for Apriori * Fixes for One-v-Rest * One-v-Rest test cases --- src/Phpml/Classification/DecisionTree.php | 5 - .../Classification/Linear/DecisionStump.php | 135 +++++++++++++----- .../Classification/Linear/Perceptron.php | 55 +++++-- src/Phpml/Helper/OneVsRest.php | 126 ++++++++++++++++ src/Phpml/Math/Statistic/Gaussian.php | 60 ++++++++ .../Classification/Linear/AdalineTest.php | 15 ++ .../Linear/DecisionStumpTest.php | 20 ++- .../Classification/Linear/PerceptronTest.php | 15 ++ tests/Phpml/Math/Statistic/GaussianTest.php | 28 ++++ 9 files changed, 409 insertions(+), 50 deletions(-) create mode 100644 src/Phpml/Helper/OneVsRest.php create mode 100644 src/Phpml/Math/Statistic/Gaussian.php create mode 100644 tests/Phpml/Math/Statistic/GaussianTest.php diff --git a/src/Phpml/Classification/DecisionTree.php b/src/Phpml/Classification/DecisionTree.php index b2b4db34..0a70d2fb 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -16,11 +16,6 @@ class DecisionTree implements Classifier const CONTINUOS = 1; const NOMINAL = 2; - /** - * @var array - */ - private $samples = []; - /** * @var array */ diff --git a/src/Phpml/Classification/Linear/DecisionStump.php b/src/Phpml/Classification/Linear/DecisionStump.php index 1605a20c..de86fe98 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -5,13 +5,13 @@ namespace Phpml\Classification\Linear; use Phpml\Helper\Predictable; -use Phpml\Helper\Trainable; +use Phpml\Helper\OneVsRest; use Phpml\Classification\WeightedClassifier; use Phpml\Classification\DecisionTree; class DecisionStump extends WeightedClassifier { - use Trainable, Predictable; + use Predictable, OneVsRest; const AUTO_SELECT = -1; @@ -20,6 +20,10 @@ class DecisionStump extends WeightedClassifier */ protected $givenColumnIndex; + /** + * @var array + */ + protected $binaryLabels; /** * Sample weights : If used the optimization on the decision value @@ -57,10 +61,22 @@ class DecisionStump extends WeightedClassifier */ protected $columnTypes; + /** + * @var int + */ + protected $featureCount; + /** * @var float */ - protected $numSplitCount = 10.0; + protected $numSplitCount = 100.0; + + /** + * Distribution of samples in the leaves + * + * @var array + */ + protected $prob; /** * A DecisionStump classifier is a one-level deep DecisionTree. It is generally @@ -81,20 +97,15 @@ public function __construct(int $columnIndex = self::AUTO_SELECT) * @param array $samples * @param array $targets */ - public function train(array $samples, array $targets) + protected function trainBinary(array $samples, array $targets) { $this->samples = array_merge($this->samples, $samples); $this->targets = array_merge($this->targets, $targets); - - // DecisionStump is capable of classifying between two classes only - $labels = array_count_values($this->targets); - $this->labels = array_keys($labels); - if (count($this->labels) != 2) { - throw new \Exception("DecisionStump can classify between two classes only:" . implode(',', $this->labels)); - } + $this->binaryLabels = array_keys(array_count_values($this->targets)); + $this->featureCount = count($this->samples[0]); // If a column index is given, it should be among the existing columns - if ($this->givenColumnIndex > count($samples[0]) - 1) { + if ($this->givenColumnIndex > count($this->samples[0]) - 1) { $this->givenColumnIndex = self::AUTO_SELECT; } @@ -106,7 +117,7 @@ public function train(array $samples, array $targets) throw new \Exception("Number of sample weights does not match with number of samples"); } } else { - $this->weights = array_fill(0, count($samples), 1); + $this->weights = array_fill(0, count($this->samples), 1); } // Determine type of each column as either "continuous" or "nominal" @@ -114,14 +125,15 @@ public function train(array $samples, array $targets) // Try to find the best split in the columns of the dataset // by calculating error rate for each split point in each column - $columns = range(0, count($samples[0]) - 1); + $columns = range(0, count($this->samples[0]) - 1); if ($this->givenColumnIndex != self::AUTO_SELECT) { $columns = [$this->givenColumnIndex]; } $bestSplit = [ 'value' => 0, 'operator' => '', - 'column' => 0, 'trainingErrorRate' => 1.0]; + 'prob' => [], 'column' => 0, + 'trainingErrorRate' => 1.0]; foreach ($columns as $col) { if ($this->columnTypes[$col] == DecisionTree::CONTINUOS) { $split = $this->getBestNumericalSplit($col); @@ -164,6 +176,10 @@ public function setNumericalSplitCount(float $count) protected function getBestNumericalSplit(int $col) { $values = array_column($this->samples, $col); + // Trying all possible points may be accomplished in two general ways: + // 1- Try all values in the $samples array ($values) + // 2- Artificially split the range of values into several parts and try them + // We choose the second one because it is faster in larger datasets $minValue = min($values); $maxValue = max($values); $stepSize = ($maxValue - $minValue) / $this->numSplitCount; @@ -174,19 +190,21 @@ protected function getBestNumericalSplit(int $col) // Before trying all possible split points, let's first try // the average value for the cut point $threshold = array_sum($values) / (float) count($values); - $errorRate = $this->calculateErrorRate($threshold, $operator, $values); + list($errorRate, $prob) = $this->calculateErrorRate($threshold, $operator, $values); if ($split == null || $errorRate < $split['trainingErrorRate']) { $split = ['value' => $threshold, 'operator' => $operator, - 'column' => $col, 'trainingErrorRate' => $errorRate]; + 'prob' => $prob, 'column' => $col, + 'trainingErrorRate' => $errorRate]; } // Try other possible points one by one for ($step = $minValue; $step <= $maxValue; $step+= $stepSize) { $threshold = (float)$step; - $errorRate = $this->calculateErrorRate($threshold, $operator, $values); + list($errorRate, $prob) = $this->calculateErrorRate($threshold, $operator, $values); if ($errorRate < $split['trainingErrorRate']) { $split = ['value' => $threshold, 'operator' => $operator, - 'column' => $col, 'trainingErrorRate' => $errorRate]; + 'prob' => $prob, 'column' => $col, + 'trainingErrorRate' => $errorRate]; } }// for } @@ -210,11 +228,12 @@ protected function getBestNominalSplit(int $col) foreach (['=', '!='] as $operator) { foreach ($distinctVals as $val) { - $errorRate = $this->calculateErrorRate($val, $operator, $values); + list($errorRate, $prob) = $this->calculateErrorRate($val, $operator, $values); if ($split == null || $split['trainingErrorRate'] < $errorRate) { $split = ['value' => $val, 'operator' => $operator, - 'column' => $col, 'trainingErrorRate' => $errorRate]; + 'prob' => $prob, 'column' => $col, + 'trainingErrorRate' => $errorRate]; } }// for } @@ -238,9 +257,9 @@ protected function evaluate($leftValue, $operator, $rightValue) case '>=': return $leftValue >= $rightValue; case '<': return $leftValue < $rightValue; case '<=': return $leftValue <= $rightValue; - case '=': return $leftValue == $rightValue; + case '=': return $leftValue === $rightValue; case '!=': - case '<>': return $leftValue != $rightValue; + case '<>': return $leftValue !== $rightValue; } return false; @@ -253,42 +272,90 @@ protected function evaluate($leftValue, $operator, $rightValue) * @param float $threshold * @param string $operator * @param array $values + * + * @return array */ protected function calculateErrorRate(float $threshold, string $operator, array $values) { - $total = (float) array_sum($this->weights); $wrong = 0.0; - $leftLabel = $this->labels[0]; - $rightLabel= $this->labels[1]; + $prob = []; + $leftLabel = $this->binaryLabels[0]; + $rightLabel= $this->binaryLabels[1]; + foreach ($values as $index => $value) { - if ($this->evaluate($threshold, $operator, $value)) { + if ($this->evaluate($value, $operator, $threshold)) { $predicted = $leftLabel; } else { $predicted = $rightLabel; } - if ($predicted != $this->targets[$index]) { + $target = $this->targets[$index]; + if (strval($predicted) != strval($this->targets[$index])) { $wrong += $this->weights[$index]; } + + if (! isset($prob[$predicted][$target])) { + $prob[$predicted][$target] = 0; + } + $prob[$predicted][$target]++; } - return $wrong / $total; + // Calculate probabilities: Proportion of labels in each leaf + $dist = array_combine($this->binaryLabels, array_fill(0, 2, 0.0)); + foreach ($prob as $leaf => $counts) { + $leafTotal = (float)array_sum($prob[$leaf]); + foreach ($counts as $label => $count) { + if (strval($leaf) == strval($label)) { + $dist[$leaf] = $count / $leafTotal; + } + } + } + + return [$wrong / (float) array_sum($this->weights), $dist]; } /** + * Returns the probability of the sample of belonging to the given label + * + * Probability of a sample is calculated as the proportion of the label + * within the labels of the training samples in the decision node + * * @param array $sample + * @param mixed $label + * + * @return float + */ + protected function predictProbability(array $sample, $label) + { + $predicted = $this->predictSampleBinary($sample); + if (strval($predicted) == strval($label)) { + return $this->prob[$label]; + } + + return 0.0; + } + + /** + * @param array $sample + * * @return mixed */ - protected function predictSample(array $sample) + protected function predictSampleBinary(array $sample) { - if ($this->evaluate($this->value, $this->operator, $sample[$this->column])) { - return $this->labels[0]; + if ($this->evaluate($sample[$this->column], $this->operator, $this->value)) { + return $this->binaryLabels[0]; } - return $this->labels[1]; + + return $this->binaryLabels[1]; } + /** + * @return string + */ public function __toString() { - return "$this->column $this->operator $this->value"; + return "IF $this->column $this->operator $this->value " . + "THEN " . $this->binaryLabels[0] . " ". + "ELSE " . $this->binaryLabels[1]; } } diff --git a/src/Phpml/Classification/Linear/Perceptron.php b/src/Phpml/Classification/Linear/Perceptron.php index e2c684dd..32e41f25 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -5,12 +5,13 @@ namespace Phpml\Classification\Linear; use Phpml\Helper\Predictable; +use Phpml\Helper\OneVsRest; use Phpml\Classification\Classifier; use Phpml\Preprocessing\Normalizer; class Perceptron implements Classifier { - use Predictable; + use Predictable, OneVsRest; /** * The function whose result will be used to calculate the network error @@ -114,7 +115,7 @@ public function setChangeThreshold(float $threshold = 1e-5) * @param array $samples * @param array $targets */ - public function train(array $samples, array $targets) + public function trainBinary(array $samples, array $targets) { $this->labels = array_keys(array_count_values($targets)); if (count($this->labels) > 2) { @@ -128,7 +129,7 @@ public function train(array $samples, array $targets) // Set all target values to either -1 or 1 $this->labels = [1 => $this->labels[0], -1 => $this->labels[1]]; foreach ($targets as $target) { - $this->targets[] = $target == $this->labels[1] ? 1 : -1; + $this->targets[] = strval($target) == strval($this->labels[1]) ? 1 : -1; } // Set samples and feature count vars @@ -213,6 +214,25 @@ function ($w1, $w2) { return false; } + /** + * Checks if the sample should be normalized and if so, returns the + * normalized sample + * + * @param array $sample + * + * @return array + */ + protected function checkNormalizedSample(array $sample) + { + if ($this->normalizer) { + $samples = [$sample]; + $this->normalizer->transform($samples); + $sample = $samples[0]; + } + + return $sample; + } + /** * Calculates net output of the network as a float value for the given input * @@ -244,17 +264,34 @@ protected function outputClass(array $sample) return $this->output($sample) > 0 ? 1 : -1; } + /** + * Returns the probability of the sample of belonging to the given label. + * + * The probability is simply taken as the distance of the sample + * to the decision plane. + * + * @param array $sample + * @param mixed $label + */ + protected function predictProbability(array $sample, $label) + { + $predicted = $this->predictSampleBinary($sample); + + if (strval($predicted) == strval($label)) { + $sample = $this->checkNormalizedSample($sample); + return abs($this->output($sample)); + } + + return 0.0; + } + /** * @param array $sample * @return mixed */ - protected function predictSample(array $sample) + protected function predictSampleBinary(array $sample) { - if ($this->normalizer) { - $samples = [$sample]; - $this->normalizer->transform($samples); - $sample = $samples[0]; - } + $sample = $this->checkNormalizedSample($sample); $predictedClass = $this->outputClass($sample); diff --git a/src/Phpml/Helper/OneVsRest.php b/src/Phpml/Helper/OneVsRest.php new file mode 100644 index 00000000..12883635 --- /dev/null +++ b/src/Phpml/Helper/OneVsRest.php @@ -0,0 +1,126 @@ +classifiers = []; + + // If there are only two targets, then there is no need to perform OvR + $this->labels = array_keys(array_count_values($targets)); + if (count($this->labels) == 2) { + $classifier->trainBinary($samples, $targets); + $this->classifiers[] = $classifier; + } else { + // Train a separate classifier for each label and memorize them + $this->samples = $samples; + $this->targets = $targets; + foreach ($this->labels as $label) { + $predictor = clone $classifier; + $targets = $this->binarizeTargets($label); + $predictor->trainBinary($samples, $targets); + $this->classifiers[$label] = $predictor; + } + } + } + + /** + * Groups all targets into two groups: Targets equal to + * the given label and the others + * + * @param mixed $label + */ + private function binarizeTargets($label) + { + $targets = []; + + foreach ($this->targets as $target) { + $targets[] = $target == $label ? $label : "not_$label"; + } + + return $targets; + } + + + /** + * @param array $sample + * + * @return mixed + */ + protected function predictSample(array $sample) + { + if (count($this->labels) == 2) { + return $this->classifiers[0]->predictSampleBinary($sample); + } + + $probs = []; + + foreach ($this->classifiers as $label => $predictor) { + $probs[$label] = $predictor->predictProbability($sample, $label); + } + + arsort($probs, SORT_NUMERIC); + return key($probs); + } + + /** + * Each classifier should implement this method instead of train(samples, targets) + * + * @param array $samples + * @param array $targets + */ + abstract protected function trainBinary(array $samples, array $targets); + + /** + * Each classifier that make use of OvR approach should be able to + * return a probability for a sample to belong to the given label. + * + * @param array $sample + * + * @return mixed + */ + abstract protected function predictProbability(array $sample, string $label); + + /** + * Each classifier should implement this method instead of predictSample() + * + * @param array $sample + * + * @return mixed + */ + abstract protected function predictSampleBinary(array $sample); +} diff --git a/src/Phpml/Math/Statistic/Gaussian.php b/src/Phpml/Math/Statistic/Gaussian.php new file mode 100644 index 00000000..df27f076 --- /dev/null +++ b/src/Phpml/Math/Statistic/Gaussian.php @@ -0,0 +1,60 @@ +mean = $mean; + $this->std = $std; + } + + /** + * Returns probability density of the given $value + * + * @param float $value + * + * @return type + */ + public function pdf(float $value) + { + // Calculate the probability density by use of normal/Gaussian distribution + // Ref: https://en.wikipedia.org/wiki/Normal_distribution + $std2 = $this->std ** 2; + $mean = $this->mean; + return exp(- (($value - $mean) ** 2) / (2 * $std2)) / sqrt(2 * $std2 * pi()); + } + + /** + * Returns probability density value of the given $value based on + * given standard deviation and the mean + * + * @param float $mean + * @param float $std + * @param float $value + * + * @return float + */ + public static function distributionPdf(float $mean, float $std, float $value) + { + $normal = new self($mean, $std); + return $normal->pdf($value); + } +} diff --git a/tests/Phpml/Classification/Linear/AdalineTest.php b/tests/Phpml/Classification/Linear/AdalineTest.php index 7ea63ab2..c07fbd2b 100644 --- a/tests/Phpml/Classification/Linear/AdalineTest.php +++ b/tests/Phpml/Classification/Linear/AdalineTest.php @@ -30,6 +30,21 @@ public function testPredictSingleSample() $this->assertEquals(1, $classifier->predict([0.1, 0.99])); $this->assertEquals(1, $classifier->predict([1.1, 0.8])); + // By use of One-v-Rest, Adaline can perform multi-class classification + // The samples should be separable by lines perpendicular to the dimensions + $samples = [ + [0, 0], [0, 1], [1, 0], [1, 1], // First group : a cluster at bottom-left corner in 2D + [5, 5], [6, 5], [5, 6], [7, 5], // Second group: another cluster at the middle-right + [3, 10],[3, 10],[3, 8], [3, 9] // Third group : cluster at the top-middle + ]; + $targets = [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2]; + + $classifier = new Adaline(); + $classifier->train($samples, $targets); + $this->assertEquals(0, $classifier->predict([0.5, 0.5])); + $this->assertEquals(1, $classifier->predict([6.0, 5.0])); + $this->assertEquals(2, $classifier->predict([3.0, 9.5])); + return $classifier; } diff --git a/tests/Phpml/Classification/Linear/DecisionStumpTest.php b/tests/Phpml/Classification/Linear/DecisionStumpTest.php index f83e0953..4060ce33 100644 --- a/tests/Phpml/Classification/Linear/DecisionStumpTest.php +++ b/tests/Phpml/Classification/Linear/DecisionStumpTest.php @@ -12,8 +12,9 @@ class DecisionStumpTest extends TestCase { public function testPredictSingleSample() { - // Samples should be separable with a line perpendicular to any dimension - // given in the dataset + // Samples should be separable with a line perpendicular + // to any dimension given in the dataset + // // First: horizontal test $samples = [[0, 0], [1, 0], [0, 1], [1, 1]]; $targets = [0, 0, 1, 1]; @@ -34,6 +35,21 @@ public function testPredictSingleSample() $this->assertEquals(1, $classifier->predict([1.0, 0.99])); $this->assertEquals(1, $classifier->predict([1.1, 0.1])); + // By use of One-v-Rest, DecisionStump can perform multi-class classification + // The samples should be separable by lines perpendicular to the dimensions + $samples = [ + [0, 0], [0, 1], [1, 0], [1, 1], // First group : a cluster at bottom-left corner in 2D + [5, 5], [6, 5], [5, 6], [7, 5], // Second group: another cluster at the middle-right + [3, 10],[3, 10],[3, 8], [3, 9] // Third group : cluster at the top-middle + ]; + $targets = [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2]; + + $classifier = new DecisionStump(); + $classifier->train($samples, $targets); + $this->assertEquals(0, $classifier->predict([0.5, 0.5])); + $this->assertEquals(1, $classifier->predict([6.0, 5.0])); + $this->assertEquals(2, $classifier->predict([3.5, 9.5])); + return $classifier; } diff --git a/tests/Phpml/Classification/Linear/PerceptronTest.php b/tests/Phpml/Classification/Linear/PerceptronTest.php index 64954f7b..ef820fc2 100644 --- a/tests/Phpml/Classification/Linear/PerceptronTest.php +++ b/tests/Phpml/Classification/Linear/PerceptronTest.php @@ -30,6 +30,21 @@ public function testPredictSingleSample() $this->assertEquals(1, $classifier->predict([0.1, 0.99])); $this->assertEquals(1, $classifier->predict([1.1, 0.8])); + // By use of One-v-Rest, Perceptron can perform multi-class classification + // The samples should be separable by lines perpendicular to the dimensions + $samples = [ + [0, 0], [0, 1], [1, 0], [1, 1], // First group : a cluster at bottom-left corner in 2D + [5, 5], [6, 5], [5, 6], [7, 5], // Second group: another cluster at the middle-right + [3, 10],[3, 10],[3, 8], [3, 9] // Third group : cluster at the top-middle + ]; + $targets = [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2]; + + $classifier = new Perceptron(); + $classifier->train($samples, $targets); + $this->assertEquals(0, $classifier->predict([0.5, 0.5])); + $this->assertEquals(1, $classifier->predict([6.0, 5.0])); + $this->assertEquals(2, $classifier->predict([3.0, 9.5])); + return $classifier; } diff --git a/tests/Phpml/Math/Statistic/GaussianTest.php b/tests/Phpml/Math/Statistic/GaussianTest.php new file mode 100644 index 00000000..6bbf63b8 --- /dev/null +++ b/tests/Phpml/Math/Statistic/GaussianTest.php @@ -0,0 +1,28 @@ + $v) { + $this->assertEquals($pdf[$i], $g->pdf($v), '', $delta); + + $this->assertEquals($pdf[$i], Gaussian::distributionPdf($mean, $std, $v), '', $delta); + } + } +} From c6fbb835731a0699037ccca0307896ec243f28b2 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 5 Mar 2017 16:25:01 +0100 Subject: [PATCH 162/328] Add typehints to DecisionTree --- src/Phpml/Classification/DecisionTree.php | 81 ++++++++++--------- .../Classification/Linear/DecisionStump.php | 2 +- .../Classification/WeightedClassifier.php | 5 +- 3 files changed, 49 insertions(+), 39 deletions(-) diff --git a/src/Phpml/Classification/DecisionTree.php b/src/Phpml/Classification/DecisionTree.php index 0a70d2fb..6e890c9c 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -4,6 +4,7 @@ namespace Phpml\Classification; +use Phpml\Exception\InvalidArgumentException; use Phpml\Helper\Predictable; use Phpml\Helper\Trainable; use Phpml\Math\Statistic\Mean; @@ -13,7 +14,7 @@ class DecisionTree implements Classifier { use Trainable, Predictable; - const CONTINUOS = 1; + const CONTINUOUS = 1; const NOMINAL = 2; /** @@ -70,7 +71,7 @@ class DecisionTree implements Classifier /** * @param int $maxDepth */ - public function __construct($maxDepth = 10) + public function __construct(int $maxDepth = 10) { $this->maxDepth = $maxDepth; } @@ -85,7 +86,7 @@ public function train(array $samples, array $targets) $this->targets = array_merge($this->targets, $targets); $this->featureCount = count($this->samples[0]); - $this->columnTypes = $this->getColumnTypes($this->samples); + $this->columnTypes = self::getColumnTypes($this->samples); $this->labels = array_keys(array_count_values($this->targets)); $this->tree = $this->getSplitLeaf(range(0, count($this->samples) - 1)); @@ -105,23 +106,29 @@ public function train(array $samples, array $targets) } } - public static function getColumnTypes(array $samples) + /** + * @param array $samples + * @return array + */ + public static function getColumnTypes(array $samples) : array { $types = []; $featureCount = count($samples[0]); for ($i=0; $i < $featureCount; $i++) { $values = array_column($samples, $i); $isCategorical = self::isCategoricalColumn($values); - $types[] = $isCategorical ? self::NOMINAL : self::CONTINUOS; + $types[] = $isCategorical ? self::NOMINAL : self::CONTINUOUS; } + return $types; } /** - * @param null|array $records + * @param array $records + * @param int $depth * @return DecisionTreeLeaf */ - protected function getSplitLeaf($records, $depth = 0) + protected function getSplitLeaf(array $records, int $depth = 0) : DecisionTreeLeaf { $split = $this->getBestSplit($records); $split->level = $depth; @@ -163,7 +170,7 @@ protected function getSplitLeaf($records, $depth = 0) } } - if (count($remainingTargets) == 1 || $allSame || $depth >= $this->maxDepth) { + if ($allSame || $depth >= $this->maxDepth || count($remainingTargets) === 1) { $split->isTerminal = 1; arsort($remainingTargets); $split->classValue = key($remainingTargets); @@ -175,14 +182,15 @@ protected function getSplitLeaf($records, $depth = 0) $split->rightLeaf= $this->getSplitLeaf($rightRecords, $depth + 1); } } + return $split; } /** * @param array $records - * @return DecisionTreeLeaf[] + * @return DecisionTreeLeaf */ - protected function getBestSplit($records) + protected function getBestSplit(array $records) : DecisionTreeLeaf { $targets = array_intersect_key($this->targets, array_flip($records)); $samples = array_intersect_key($this->samples, array_flip($records)); @@ -199,18 +207,18 @@ protected function getBestSplit($records) arsort($counts); $baseValue = key($counts); $gini = $this->getGiniIndex($baseValue, $colValues, $targets); - if ($bestSplit == null || $bestGiniVal > $gini) { + if ($bestSplit === null || $bestGiniVal > $gini) { $split = new DecisionTreeLeaf(); $split->value = $baseValue; $split->giniIndex = $gini; $split->columnIndex = $i; - $split->isContinuous = $this->columnTypes[$i] == self::CONTINUOS; + $split->isContinuous = $this->columnTypes[$i] == self::CONTINUOUS; $split->records = $records; // If a numeric column is to be selected, then // the original numeric value and the selected operator // will also be saved into the leaf for future access - if ($this->columnTypes[$i] == self::CONTINUOS) { + if ($this->columnTypes[$i] == self::CONTINUOUS) { $matches = []; preg_match("/^([<>=]{1,2})\s*(.*)/", strval($split->value), $matches); $split->operator = $matches[1]; @@ -221,6 +229,7 @@ protected function getBestSplit($records) $bestGiniVal = $gini; } } + return $bestSplit; } @@ -239,10 +248,10 @@ protected function getBestSplit($records) * * @return array */ - protected function getSelectedFeatures() + protected function getSelectedFeatures() : array { $allFeatures = range(0, $this->featureCount - 1); - if ($this->numUsableFeatures == 0 && ! $this->selectedFeatures) { + if ($this->numUsableFeatures === 0 && ! $this->selectedFeatures) { return $allFeatures; } @@ -262,11 +271,12 @@ protected function getSelectedFeatures() } /** - * @param string $baseValue + * @param $baseValue * @param array $colValues * @param array $targets + * @return float */ - public function getGiniIndex($baseValue, $colValues, $targets) + public function getGiniIndex($baseValue, array $colValues, array $targets) : float { $countMatrix = []; foreach ($this->labels as $label) { @@ -274,7 +284,7 @@ public function getGiniIndex($baseValue, $colValues, $targets) } foreach ($colValues as $index => $value) { $label = $targets[$index]; - $rowIndex = $value == $baseValue ? 0 : 1; + $rowIndex = $value === $baseValue ? 0 : 1; $countMatrix[$label][$rowIndex]++; } $giniParts = [0, 0]; @@ -288,6 +298,7 @@ public function getGiniIndex($baseValue, $colValues, $targets) } $giniParts[$i] = (1 - $part) * $sum; } + return array_sum($giniParts) / count($colValues); } @@ -295,14 +306,14 @@ public function getGiniIndex($baseValue, $colValues, $targets) * @param array $samples * @return array */ - protected function preprocess(array $samples) + protected function preprocess(array $samples) : array { // Detect and convert continuous data column values into // discrete values by using the median as a threshold value $columns = []; for ($i=0; $i<$this->featureCount; $i++) { $values = array_column($samples, $i); - if ($this->columnTypes[$i] == self::CONTINUOS) { + if ($this->columnTypes[$i] == self::CONTINUOUS) { $median = Mean::median($values); foreach ($values as &$value) { if ($value <= $median) { @@ -323,7 +334,7 @@ protected function preprocess(array $samples) * @param array $columnValues * @return bool */ - protected static function isCategoricalColumn(array $columnValues) + protected static function isCategoricalColumn(array $columnValues) : bool { $count = count($columnValues); @@ -337,15 +348,13 @@ protected static function isCategoricalColumn(array $columnValues) if ($floatValues) { return false; } - if (count($numericValues) != $count) { + if (count($numericValues) !== $count) { return true; } $distinctValues = array_count_values($columnValues); - if (count($distinctValues) <= $count / 5) { - return true; - } - return false; + + return count($distinctValues) <= $count / 5; } /** @@ -357,12 +366,12 @@ protected static function isCategoricalColumn(array $columnValues) * * @param int $numFeatures * @return $this - * @throws Exception + * @throws InvalidArgumentException */ public function setNumFeatures(int $numFeatures) { if ($numFeatures < 0) { - throw new \Exception("Selected column count should be greater or equal to zero"); + throw new InvalidArgumentException('Selected column count should be greater or equal to zero'); } $this->numUsableFeatures = $numFeatures; @@ -386,11 +395,12 @@ protected function setSelectedFeatures(array $selectedFeatures) * * @param array $names * @return $this + * @throws InvalidArgumentException */ public function setColumnNames(array $names) { - if ($this->featureCount != 0 && count($names) != $this->featureCount) { - throw new \Exception("Length of the given array should be equal to feature count ($this->featureCount)"); + if ($this->featureCount !== 0 && count($names) !== $this->featureCount) { + throw new InvalidArgumentException(sprintf('Length of the given array should be equal to feature count %s', $this->featureCount)); } $this->columnNames = $names; @@ -411,7 +421,6 @@ public function getHtml() * each column in the given dataset. The importance values are * normalized and their total makes 1.
* - * @param array $labels * @return array */ public function getFeatureImportances() @@ -447,22 +456,20 @@ public function getFeatureImportances() /** * Collects and returns an array of internal nodes that use the given - * column as a split criteron + * column as a split criterion * * @param int $column - * @param DecisionTreeLeaf - * @param array $collected - * + * @param DecisionTreeLeaf $node * @return array */ - protected function getSplitNodesByColumn($column, DecisionTreeLeaf $node) + protected function getSplitNodesByColumn(int $column, DecisionTreeLeaf $node) : array { if (!$node || $node->isTerminal) { return []; } $nodes = []; - if ($node->columnIndex == $column) { + if ($node->columnIndex === $column) { $nodes[] = $node; } diff --git a/src/Phpml/Classification/Linear/DecisionStump.php b/src/Phpml/Classification/Linear/DecisionStump.php index de86fe98..8287bbc3 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -135,7 +135,7 @@ protected function trainBinary(array $samples, array $targets) 'prob' => [], 'column' => 0, 'trainingErrorRate' => 1.0]; foreach ($columns as $col) { - if ($this->columnTypes[$col] == DecisionTree::CONTINUOS) { + if ($this->columnTypes[$col] == DecisionTree::CONTINUOUS) { $split = $this->getBestNumericalSplit($col); } else { $split = $this->getBestNominalSplit($col); diff --git a/src/Phpml/Classification/WeightedClassifier.php b/src/Phpml/Classification/WeightedClassifier.php index c0ec045e..4af3de49 100644 --- a/src/Phpml/Classification/WeightedClassifier.php +++ b/src/Phpml/Classification/WeightedClassifier.php @@ -6,7 +6,10 @@ abstract class WeightedClassifier implements Classifier { - protected $weights = null; + /** + * @var array + */ + protected $weights; /** * Sets the array including a weight for each sample From 39747efdc1ff781939c45f321475f00f71ef3172 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 5 Mar 2017 16:45:48 +0100 Subject: [PATCH 163/328] Update dependecies and coding style fixes --- composer.lock | 178 +++++++++--------- .../Classification/Linear/DecisionStump.php | 19 +- 2 files changed, 97 insertions(+), 100 deletions(-) diff --git a/composer.lock b/composer.lock index 424fd17b..84f4b389 100644 --- a/composer.lock +++ b/composer.lock @@ -251,27 +251,27 @@ }, { "name": "phpspec/prophecy", - "version": "v1.6.2", + "version": "v1.7.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "6c52c2722f8460122f96f86346600e1077ce22cb" + "reference": "93d39f1f7f9326d746203c7c056f300f7f126073" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/6c52c2722f8460122f96f86346600e1077ce22cb", - "reference": "6c52c2722f8460122f96f86346600e1077ce22cb", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073", + "reference": "93d39f1f7f9326d746203c7c056f300f7f126073", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", - "sebastian/comparator": "^1.1", - "sebastian/recursion-context": "^1.0|^2.0" + "sebastian/comparator": "^1.1|^2.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" }, "require-dev": { - "phpspec/phpspec": "^2.0", + "phpspec/phpspec": "^2.5|^3.2", "phpunit/phpunit": "^4.8 || ^5.6.5" }, "type": "library", @@ -310,27 +310,29 @@ "spy", "stub" ], - "time": "2016-11-21T14:58:47+00:00" + "time": "2017-03-02T20:05:34+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "5.0.0", + "version": "5.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "e7d7a4acca58e45bdfd00221563d131cfb04ba96" + "reference": "531553c4795a1df54114342d68ca337d5d81c8a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/e7d7a4acca58e45bdfd00221563d131cfb04ba96", - "reference": "e7d7a4acca58e45bdfd00221563d131cfb04ba96", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/531553c4795a1df54114342d68ca337d5d81c8a0", + "reference": "531553c4795a1df54114342d68ca337d5d81c8a0", "shasum": "" }, "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", "php": "^7.0", "phpunit/php-file-iterator": "^1.3", "phpunit/php-text-template": "^1.2", - "phpunit/php-token-stream": "^1.4.2", + "phpunit/php-token-stream": "^1.4.11 || ^2.0", "sebastian/code-unit-reverse-lookup": "^1.0", "sebastian/environment": "^2.0", "sebastian/version": "^2.0" @@ -340,8 +342,7 @@ "phpunit/phpunit": "^6.0" }, "suggest": { - "ext-dom": "*", - "ext-xmlwriter": "*" + "ext-xdebug": "^2.5.1" }, "type": "library", "extra": { @@ -372,7 +373,7 @@ "testing", "xunit" ], - "time": "2017-02-02T10:35:41+00:00" + "time": "2017-03-01T09:14:18+00:00" }, { "name": "phpunit/php-file-iterator", @@ -464,25 +465,30 @@ }, { "name": "phpunit/php-timer", - "version": "1.0.8", + "version": "1.0.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260" + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260", - "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^5.3.3 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "~4|~5" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, "autoload": { "classmap": [ "src/" @@ -504,20 +510,20 @@ "keywords": [ "timer" ], - "time": "2016-05-12T18:03:57+00:00" + "time": "2017-02-26T11:10:40+00:00" }, { "name": "phpunit/php-token-stream", - "version": "1.4.9", + "version": "1.4.11", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b" + "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3b402f65a4cc90abf6e1104e388b896ce209631b", - "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7", + "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7", "shasum": "" }, "require": { @@ -553,20 +559,20 @@ "keywords": [ "tokenizer" ], - "time": "2016-11-15T14:06:22+00:00" + "time": "2017-02-27T10:12:30+00:00" }, { "name": "phpunit/phpunit", - "version": "6.0.1", + "version": "6.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "93a57ef4b0754737ac73604c5f51abf675d925d8" + "reference": "47ee3fa1bca5c50f1d25105201eb20df777bd7b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/93a57ef4b0754737ac73604c5f51abf675d925d8", - "reference": "93a57ef4b0754737ac73604c5f51abf675d925d8", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/47ee3fa1bca5c50f1d25105201eb20df777bd7b6", + "reference": "47ee3fa1bca5c50f1d25105201eb20df777bd7b6", "shasum": "" }, "require": { @@ -583,12 +589,12 @@ "phpunit/php-text-template": "^1.2", "phpunit/php-timer": "^1.0.6", "phpunit/phpunit-mock-objects": "^4.0", - "sebastian/comparator": "^1.2.4", + "sebastian/comparator": "^1.2.4 || ^2.0", "sebastian/diff": "^1.2", "sebastian/environment": "^2.0", - "sebastian/exporter": "^2.0", - "sebastian/global-state": "^1.1", - "sebastian/object-enumerator": "^2.0", + "sebastian/exporter": "^2.0 || ^3.0", + "sebastian/global-state": "^1.1 || ^2.0", + "sebastian/object-enumerator": "^2.0 || ^3.0", "sebastian/resource-operations": "^1.0", "sebastian/version": "^2.0" }, @@ -635,27 +641,27 @@ "testing", "xunit" ], - "time": "2017-02-03T11:40:20+00:00" + "time": "2017-03-02T15:24:03+00:00" }, { "name": "phpunit/phpunit-mock-objects", - "version": "4.0.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "3819745c44f3aff9518fd655f320c4535d541af7" + "reference": "eabce450df194817a7d7e27e19013569a903a2bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/3819745c44f3aff9518fd655f320c4535d541af7", - "reference": "3819745c44f3aff9518fd655f320c4535d541af7", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/eabce450df194817a7d7e27e19013569a903a2bf", + "reference": "eabce450df194817a7d7e27e19013569a903a2bf", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^7.0", "phpunit/php-text-template": "^1.2", - "sebastian/exporter": "^2.0" + "sebastian/exporter": "^3.0" }, "conflict": { "phpunit/phpunit": "<6.0" @@ -694,27 +700,27 @@ "mock", "xunit" ], - "time": "2017-02-02T10:36:38+00:00" + "time": "2017-03-03T06:30:20+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe" + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/c36f5e7cfce482fde5bf8d10d41a53591e0198fe", - "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", "shasum": "" }, "require": { - "php": ">=5.6" + "php": "^5.6 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "~5" + "phpunit/phpunit": "^5.7 || ^6.0" }, "type": "library", "extra": { @@ -739,34 +745,34 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2016-02-13T06:45:14+00:00" + "time": "2017-03-04T06:30:41+00:00" }, { "name": "sebastian/comparator", - "version": "1.2.4", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + "reference": "20f84f468cb67efee293246e6a09619b891f55f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", - "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/20f84f468cb67efee293246e6a09619b891f55f0", + "reference": "20f84f468cb67efee293246e6a09619b891f55f0", "shasum": "" }, "require": { - "php": ">=5.3.3", - "sebastian/diff": "~1.2", - "sebastian/exporter": "~1.2 || ~2.0" + "php": "^7.0", + "sebastian/diff": "^1.2", + "sebastian/exporter": "^3.0" }, "require-dev": { - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -803,7 +809,7 @@ "compare", "equality" ], - "time": "2017-01-29T09:50:25+00:00" + "time": "2017-03-03T06:26:08+00:00" }, { "name": "sebastian/diff", @@ -909,30 +915,30 @@ }, { "name": "sebastian/exporter", - "version": "2.0.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" + "reference": "b82d077cb3459e393abcf4867ae8f7230dcb51f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", - "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/b82d077cb3459e393abcf4867ae8f7230dcb51f6", + "reference": "b82d077cb3459e393abcf4867ae8f7230dcb51f6", "shasum": "" }, "require": { - "php": ">=5.3.3", - "sebastian/recursion-context": "~2.0" + "php": "^7.0", + "sebastian/recursion-context": "^3.0" }, "require-dev": { "ext-mbstring": "*", - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { @@ -972,7 +978,7 @@ "export", "exporter" ], - "time": "2016-11-19T08:54:04+00:00" + "time": "2017-03-03T06:25:06+00:00" }, { "name": "sebastian/global-state", @@ -1027,29 +1033,29 @@ }, { "name": "sebastian/object-enumerator", - "version": "2.0.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "96f8a3f257b69e8128ad74d3a7fd464bcbaa3b35" + "reference": "de6e32f7192dfea2e4bedc892434f4830b5c5794" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/96f8a3f257b69e8128ad74d3a7fd464bcbaa3b35", - "reference": "96f8a3f257b69e8128ad74d3a7fd464bcbaa3b35", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/de6e32f7192dfea2e4bedc892434f4830b5c5794", + "reference": "de6e32f7192dfea2e4bedc892434f4830b5c5794", "shasum": "" }, "require": { - "php": ">=5.6", - "sebastian/recursion-context": "~2.0" + "php": "^7.0", + "sebastian/recursion-context": "^3.0" }, "require-dev": { - "phpunit/phpunit": "~5" + "phpunit/phpunit": "^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { @@ -1069,32 +1075,32 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2016-11-19T07:35:10+00:00" + "time": "2017-03-03T06:21:01+00:00" }, { "name": "sebastian/recursion-context", - "version": "2.0.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", - "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { @@ -1122,7 +1128,7 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2016-11-19T07:33:16+00:00" + "time": "2017-03-03T06:23:57+00:00" }, { "name": "sebastian/resource-operations", diff --git a/src/Phpml/Classification/Linear/DecisionStump.php b/src/Phpml/Classification/Linear/DecisionStump.php index 8287bbc3..13bc4a5a 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -25,15 +25,6 @@ class DecisionStump extends WeightedClassifier */ protected $binaryLabels; - /** - * Sample weights : If used the optimization on the decision value - * will take these weights into account. If not given, all samples - * will be weighed with the same value of 1 - * - * @var array - */ - protected $weights = null; - /** * Lowest error rate obtained while training/optimizing the model * @@ -96,6 +87,7 @@ public function __construct(int $columnIndex = self::AUTO_SELECT) /** * @param array $samples * @param array $targets + * @throws \Exception */ protected function trainBinary(array $samples, array $targets) { @@ -213,12 +205,11 @@ protected function getBestNumericalSplit(int $col) } /** - * * @param int $col * * @return array */ - protected function getBestNominalSplit(int $col) + protected function getBestNominalSplit(int $col) : array { $values = array_column($this->samples, $col); $valueCounts = array_count_values($values); @@ -235,7 +226,7 @@ protected function getBestNominalSplit(int $col) 'prob' => $prob, 'column' => $col, 'trainingErrorRate' => $errorRate]; } - }// for + } } return $split; @@ -275,7 +266,7 @@ protected function evaluate($leftValue, $operator, $rightValue) * * @return array */ - protected function calculateErrorRate(float $threshold, string $operator, array $values) + protected function calculateErrorRate(float $threshold, string $operator, array $values) : array { $wrong = 0.0; $prob = []; @@ -325,7 +316,7 @@ protected function calculateErrorRate(float $threshold, string $operator, array * * @return float */ - protected function predictProbability(array $sample, $label) + protected function predictProbability(array $sample, $label) : float { $predicted = $this->predictSampleBinary($sample); if (strval($predicted) == strval($label)) { From 8be19567a2115c2fa591b26944164ffa098f83c1 Mon Sep 17 00:00:00 2001 From: Bill Nunney Date: Thu, 9 Mar 2017 14:41:15 -0500 Subject: [PATCH 164/328] Update imputation example to use transform method (#57) --- .../machine-learning/preprocessing/imputation-missing-values.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/machine-learning/preprocessing/imputation-missing-values.md b/docs/machine-learning/preprocessing/imputation-missing-values.md index 5bbefec3..48a5b3a4 100644 --- a/docs/machine-learning/preprocessing/imputation-missing-values.md +++ b/docs/machine-learning/preprocessing/imputation-missing-values.md @@ -34,7 +34,7 @@ $data = [ ]; $imputer = new Imputer(null, new MeanStrategy(), Imputer::AXIS_COLUMN); -$imputer->preprocess($data); +$imputer->transform($data); /* $data = [ From c44f3b2730d2bf263ae1e39f40acff3d7bebe4f8 Mon Sep 17 00:00:00 2001 From: Kyle Warren Date: Fri, 17 Mar 2017 06:44:45 -0400 Subject: [PATCH 165/328] Additional training for SVR (#59) * additional training SVR * additional training SVR, missed old labels reference * SVM labels parameter now targets * SVM member labels now targets * SVM init targets empty array --- .../SupportVectorMachine.php | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php index 55a53057..27b9e5a2 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -4,9 +4,14 @@ namespace Phpml\SupportVectorMachine; +use Phpml\Helper\Trainable; + + class SupportVectorMachine { - /** + use Trainable; + + /** * @var int */ private $type; @@ -84,7 +89,7 @@ class SupportVectorMachine /** * @var array */ - private $labels; + private $targets = []; /** * @param int $type @@ -126,12 +131,14 @@ public function __construct( /** * @param array $samples - * @param array $labels + * @param array $targets */ - public function train(array $samples, array $labels) + public function train(array $samples, array $targets) { - $this->labels = $labels; - $trainingSet = DataTransformer::trainingSet($samples, $labels, in_array($this->type, [Type::EPSILON_SVR, Type::NU_SVR])); + $this->samples = array_merge($this->samples, $samples); + $this->targets = array_merge($this->targets, $targets); + + $trainingSet = DataTransformer::trainingSet($this->samples, $this->targets, in_array($this->type, [Type::EPSILON_SVR, Type::NU_SVR])); file_put_contents($trainingSetFileName = $this->varPath.uniqid('phpml', true), $trainingSet); $modelFileName = $trainingSetFileName.'-model'; @@ -176,7 +183,7 @@ public function predict(array $samples) unlink($outputFileName); if (in_array($this->type, [Type::C_SVC, Type::NU_SVC])) { - $predictions = DataTransformer::predictions($predictions, $this->labels); + $predictions = DataTransformer::predictions($predictions, $this->targets); } else { $predictions = explode(PHP_EOL, trim($predictions)); } From 49234429f0b675e83c3d690fb68106994a6fa9fa Mon Sep 17 00:00:00 2001 From: Mustafa Karabulut Date: Tue, 28 Mar 2017 00:46:53 +0300 Subject: [PATCH 166/328] LogisticRegression classifier & Optimization methods (#63) * LogisticRegression classifier & Optimization methods * Minor fixes to Logistic Regression & Optimizers PR * Addition for getCostValues() method --- src/Phpml/Classification/Linear/Adaline.php | 71 +--- .../Linear/LogisticRegression.php | 276 ++++++++++++++ .../Classification/Linear/Perceptron.php | 146 +++---- src/Phpml/Helper/OneVsRest.php | 13 +- .../Helper/Optimizer/ConjugateGradient.php | 359 ++++++++++++++++++ src/Phpml/Helper/Optimizer/GD.php | 108 ++++++ src/Phpml/Helper/Optimizer/Optimizer.php | 61 +++ src/Phpml/Helper/Optimizer/StochasticGD.php | 271 +++++++++++++ .../Classification/Ensemble/AdaBoostTest.php | 2 +- .../Classification/Linear/PerceptronTest.php | 5 +- 10 files changed, 1161 insertions(+), 151 deletions(-) create mode 100644 src/Phpml/Classification/Linear/LogisticRegression.php create mode 100644 src/Phpml/Helper/Optimizer/ConjugateGradient.php create mode 100644 src/Phpml/Helper/Optimizer/GD.php create mode 100644 src/Phpml/Helper/Optimizer/Optimizer.php create mode 100644 src/Phpml/Helper/Optimizer/StochasticGD.php diff --git a/src/Phpml/Classification/Linear/Adaline.php b/src/Phpml/Classification/Linear/Adaline.php index 194451a8..8d94be41 100644 --- a/src/Phpml/Classification/Linear/Adaline.php +++ b/src/Phpml/Classification/Linear/Adaline.php @@ -19,14 +19,6 @@ class Adaline extends Perceptron */ const ONLINE_TRAINING = 2; - /** - * The function whose result will be used to calculate the network error - * for each instance - * - * @var string - */ - protected static $errorFunction = 'output'; - /** * Training type may be either 'Batch' or 'Online' learning * @@ -64,62 +56,19 @@ public function __construct(float $learningRate = 0.001, int $maxIterations = 10 */ protected function runTraining() { - // If online training is chosen, then the parent runTraining method - // will be executed with the 'output' method as the error function - if ($this->trainingType == self::ONLINE_TRAINING) { - return parent::runTraining(); - } - - // Batch learning is executed: - $currIter = 0; - while ($this->maxIterations > $currIter++) { - $weights = $this->weights; - - $outputs = array_map([$this, 'output'], $this->samples); - $updates = array_map([$this, 'gradient'], $this->targets, $outputs); - - $this->updateWeights($updates); - - if ($this->earlyStop($weights)) { - break; - } - } - } + // The cost function is the sum of squares + $callback = function ($weights, $sample, $target) { + $this->weights = $weights; - /** - * Returns the direction of gradient given the desired and actual outputs - * - * @param int $desired - * @param int $output - * @return int - */ - protected function gradient($desired, $output) - { - return $desired - $output; - } + $output = $this->output($sample); + $gradient = $output - $target; + $error = $gradient ** 2; - /** - * Updates the weights of the network given the direction of the - * gradient for each sample - * - * @param array $updates - */ - protected function updateWeights(array $updates) - { - // Updates all weights at once - for ($i=0; $i <= $this->featureCount; $i++) { - if ($i == 0) { - $this->weights[0] += $this->learningRate * array_sum($updates); - } else { - $col = array_column($this->samples, $i - 1); + return [$error, $gradient]; + }; - $error = 0; - foreach ($col as $index => $val) { - $error += $val * $updates[$index]; - } + $isBatch = $this->trainingType == self::BATCH_TRAINING; - $this->weights[$i] += $this->learningRate * $error; - } - } + return parent::runGradientDescent($callback, $isBatch); } } diff --git a/src/Phpml/Classification/Linear/LogisticRegression.php b/src/Phpml/Classification/Linear/LogisticRegression.php new file mode 100644 index 00000000..a0ec2900 --- /dev/null +++ b/src/Phpml/Classification/Linear/LogisticRegression.php @@ -0,0 +1,276 @@ + + * - 'log' : log likelihood
+ * - 'sse' : sum of squared errors
+ * + * @var string + */ + protected $costFunction = 'sse'; + + /** + * Regularization term: only 'L2' is supported + * + * @var string + */ + protected $penalty = 'L2'; + + /** + * Lambda (λ) parameter of regularization term. If λ is set to 0, then + * regularization term is cancelled. + * + * @var float + */ + protected $lambda = 0.5; + + /** + * Initalize a Logistic Regression classifier with maximum number of iterations + * and learning rule to be applied
+ * + * Maximum number of iterations can be an integer value greater than 0
+ * If normalizeInputs is set to true, then every input given to the algorithm will be standardized + * by use of standard deviation and mean calculation
+ * + * Cost function can be 'log' for log-likelihood and 'sse' for sum of squared errors
+ * + * Penalty (Regularization term) can be 'L2' or empty string to cancel penalty term + * + * @param int $maxIterations + * @param bool $normalizeInputs + * @param int $trainingType + * @param string $cost + * @param string $penalty + * + * @throws \Exception + */ + public function __construct(int $maxIterations = 500, bool $normalizeInputs = true, + int $trainingType = self::CONJUGATE_GRAD_TRAINING, string $cost = 'sse', + string $penalty = 'L2') + { + $trainingTypes = range(self::BATCH_TRAINING, self::CONJUGATE_GRAD_TRAINING); + if (! in_array($trainingType, $trainingTypes)) { + throw new \Exception("Logistic regression can only be trained with " . + "batch (gradient descent), online (stochastic gradient descent) " . + "or conjugate batch (conjugate gradients) algorithms"); + } + + if (! in_array($cost, ['log', 'sse'])) { + throw new \Exception("Logistic regression cost function can be one of the following: \n" . + "'log' for log-likelihood and 'sse' for sum of squared errors"); + } + + if ($penalty != '' && strtoupper($penalty) !== 'L2') { + throw new \Exception("Logistic regression supports only 'L2' regularization"); + } + + $this->learningRate = 0.001; + + parent::__construct($this->learningRate, $maxIterations, $normalizeInputs); + + $this->trainingType = $trainingType; + $this->costFunction = $cost; + $this->penalty = $penalty; + } + + /** + * Sets the learning rate if gradient descent algorithm is + * selected for training + * + * @param float $learningRate + */ + public function setLearningRate(float $learningRate) + { + $this->learningRate = $learningRate; + } + + /** + * Lambda (λ) parameter of regularization term. If 0 is given, + * then the regularization term is cancelled + * + * @param float $lambda + */ + public function setLambda(float $lambda) + { + $this->lambda = $lambda; + } + + /** + * Adapts the weights with respect to given samples and targets + * by use of selected solver + */ + protected function runTraining() + { + $callback = $this->getCostFunction(); + + switch ($this->trainingType) { + case self::BATCH_TRAINING: + return $this->runGradientDescent($callback, true); + + case self::ONLINE_TRAINING: + return $this->runGradientDescent($callback, false); + + case self::CONJUGATE_GRAD_TRAINING: + return $this->runConjugateGradient($callback); + } + } + + /** + * Executes Conjugate Gradient method to optimize the + * weights of the LogReg model + */ + protected function runConjugateGradient(\Closure $gradientFunc) + { + $optimizer = (new ConjugateGradient($this->featureCount)) + ->setMaxIterations($this->maxIterations); + + $this->weights = $optimizer->runOptimization($this->samples, $this->targets, $gradientFunc); + $this->costValues = $optimizer->getCostValues(); + } + + /** + * Returns the appropriate callback function for the selected cost function + * + * @return \Closure + */ + protected function getCostFunction() + { + $penalty = 0; + if ($this->penalty == 'L2') { + $penalty = $this->lambda; + } + + switch ($this->costFunction) { + case 'log': + /* + * Negative of Log-likelihood cost function to be minimized: + * J(x) = ∑( - y . log(h(x)) - (1 - y) . log(1 - h(x))) + * + * If regularization term is given, then it will be added to the cost: + * for L2 : J(x) = J(x) + λ/m . w + * + * The gradient of the cost function to be used with gradient descent: + * ∇J(x) = -(y - h(x)) = (h(x) - y) + */ + $callback = function ($weights, $sample, $y) use ($penalty) { + $this->weights = $weights; + $hX = $this->output($sample); + + // In cases where $hX = 1 or $hX = 0, the log-likelihood + // value will give a NaN, so we fix these values + if ($hX == 1) { + $hX = 1 - 1e-10; + } + if ($hX == 0) { + $hX = 1e-10; + } + $error = -$y * log($hX) - (1 - $y) * log(1 - $hX); + $gradient = $hX - $y; + + return [$error, $gradient, $penalty]; + }; + + return $callback; + + case 'sse': + /** + * Sum of squared errors or least squared errors cost function: + * J(x) = ∑ (y - h(x))^2 + * + * If regularization term is given, then it will be added to the cost: + * for L2 : J(x) = J(x) + λ/m . w + * + * The gradient of the cost function: + * ∇J(x) = -(h(x) - y) . h(x) . (1 - h(x)) + */ + $callback = function ($weights, $sample, $y) use ($penalty) { + $this->weights = $weights; + $hX = $this->output($sample); + + $error = ($y - $hX) ** 2; + $gradient = -($y - $hX) * $hX * (1 - $hX); + + return [$error, $gradient, $penalty]; + }; + + return $callback; + } + } + + /** + * Returns the output of the network, a float value between 0.0 and 1.0 + * + * @param array $sample + * + * @return float + */ + protected function output(array $sample) + { + $sum = parent::output($sample); + + return 1.0 / (1.0 + exp(-$sum)); + } + + /** + * Returns the class value (either -1 or 1) for the given input + * + * @param array $sample + * @return int + */ + protected function outputClass(array $sample) + { + $output = $this->output($sample); + + if (round($output) > 0.5) { + return 1; + } + + return -1; + } + + /** + * Returns the probability of the sample of belonging to the given label. + * + * The probability is simply taken as the distance of the sample + * to the decision plane. + * + * @param array $sample + * @param mixed $label + */ + protected function predictProbability(array $sample, $label) + { + $predicted = $this->predictSampleBinary($sample); + + if (strval($predicted) == strval($label)) { + $sample = $this->checkNormalizedSample($sample); + return abs($this->output($sample) - 0.5); + } + + return 0.0; + } +} diff --git a/src/Phpml/Classification/Linear/Perceptron.php b/src/Phpml/Classification/Linear/Perceptron.php index 32e41f25..8280bcbf 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -6,6 +6,8 @@ use Phpml\Helper\Predictable; use Phpml\Helper\OneVsRest; +use Phpml\Helper\Optimizer\StochasticGD; +use Phpml\Helper\Optimizer\GD; use Phpml\Classification\Classifier; use Phpml\Preprocessing\Normalizer; @@ -13,14 +15,6 @@ class Perceptron implements Classifier { use Predictable, OneVsRest; - /** - * The function whose result will be used to calculate the network error - * for each instance - * - * @var string - */ - protected static $errorFunction = 'outputClass'; - /** * @var array */ @@ -62,12 +56,14 @@ class Perceptron implements Classifier protected $normalizer; /** - * Minimum amount of change in the weights between iterations - * that needs to be obtained to continue the training - * - * @var float + * @var bool + */ + protected $enableEarlyStop = true; + + /** + * @var array */ - protected $threshold = 1e-5; + protected $costValues = []; /** * Initalize a perceptron classifier with given learning rate and maximum @@ -97,20 +93,6 @@ public function __construct(float $learningRate = 0.001, int $maxIterations = 10 $this->maxIterations = $maxIterations; } - /** - * Sets minimum value for the change in the weights - * between iterations to continue the iterations.
- * - * If the weight change is less than given value then the - * algorithm will stop training - * - * @param float $threshold - */ - public function setChangeThreshold(float $threshold = 1e-5) - { - $this->threshold = $threshold; - } - /** * @param array $samples * @param array $targets @@ -136,82 +118,72 @@ public function trainBinary(array $samples, array $targets) $this->samples = array_merge($this->samples, $samples); $this->featureCount = count($this->samples[0]); - // Init weights with random values - $this->weights = array_fill(0, $this->featureCount + 1, 0); - foreach ($this->weights as &$weight) { - $weight = rand() / (float) getrandmax(); - } - // Do training $this->runTraining(); } /** - * Adapts the weights with respect to given samples and targets - * by use of perceptron learning rule + * Normally enabling early stopping for the optimization procedure may + * help saving processing time while in some cases it may result in + * premature convergence.
+ * + * If "false" is given, the optimization procedure will always be executed + * for $maxIterations times + * + * @param bool $enable + */ + public function setEarlyStop(bool $enable = true) + { + $this->enableEarlyStop = $enable; + + return $this; + } + + /** + * Returns the cost values obtained during the training. + * + * @return array + */ + public function getCostValues() + { + return $this->costValues; + } + + /** + * Trains the perceptron model with Stochastic Gradient Descent optimization + * to get the correct set of weights */ protected function runTraining() { - $currIter = 0; - $bestWeights = null; - $bestScore = count($this->samples); - $bestWeightIter = 0; - - while ($this->maxIterations > $currIter++) { - $weights = $this->weights; - $misClassified = 0; - foreach ($this->samples as $index => $sample) { - $target = $this->targets[$index]; - $prediction = $this->{static::$errorFunction}($sample); - $update = $target - $prediction; - if ($target != $prediction) { - $misClassified++; - } - // Update bias - $this->weights[0] += $update * $this->learningRate; // Bias - // Update other weights - for ($i=1; $i <= $this->featureCount; $i++) { - $this->weights[$i] += $update * $sample[$i - 1] * $this->learningRate; - } - } + // The cost function is the sum of squares + $callback = function ($weights, $sample, $target) { + $this->weights = $weights; - // Save the best weights in the "pocket" so that - // any future weights worse than this will be disregarded - if ($bestWeights == null || $misClassified <= $bestScore) { - $bestWeights = $weights; - $bestScore = $misClassified; - $bestWeightIter = $currIter; - } + $prediction = $this->outputClass($sample); + $gradient = $prediction - $target; + $error = $gradient**2; - // Check for early stop - if ($this->earlyStop($weights)) { - break; - } - } + return [$error, $gradient]; + }; - // The weights in the pocket are better than or equal to the last state - // so, we use these weights - $this->weights = $bestWeights; + $this->runGradientDescent($callback); } /** - * @param array $oldWeights - * - * @return boolean + * Executes Stochastic Gradient Descent algorithm for + * the given cost function */ - protected function earlyStop($oldWeights) + protected function runGradientDescent(\Closure $gradientFunc, bool $isBatch = false) { - // Check for early stop: No change larger than 1e-5 - $diff = array_map( - function ($w1, $w2) { - return abs($w1 - $w2) > 1e-5 ? 1 : 0; - }, - $oldWeights, $this->weights); - - if (array_sum($diff) == 0) { - return true; - } + $class = $isBatch ? GD::class : StochasticGD::class; + + $optimizer = (new $class($this->featureCount)) + ->setLearningRate($this->learningRate) + ->setMaxIterations($this->maxIterations) + ->setChangeThreshold(1e-6) + ->setEarlyStop($this->enableEarlyStop); - return false; + $this->weights = $optimizer->runOptimization($this->samples, $this->targets, $gradientFunc); + $this->costValues = $optimizer->getCostValues(); } /** diff --git a/src/Phpml/Helper/OneVsRest.php b/src/Phpml/Helper/OneVsRest.php index 12883635..9e7bc82c 100644 --- a/src/Phpml/Helper/OneVsRest.php +++ b/src/Phpml/Helper/OneVsRest.php @@ -15,7 +15,7 @@ trait OneVsRest * @var array */ protected $targets = []; - + /** * @var array */ @@ -26,6 +26,11 @@ trait OneVsRest */ protected $labels; + /** + * @var array + */ + protected $costValues; + /** * Train a binary classifier in the OvR style * @@ -56,6 +61,12 @@ public function train(array $samples, array $targets) $this->classifiers[$label] = $predictor; } } + + // If the underlying classifier is capable of giving the cost values + // during the training, then assign it to the relevant variable + if (method_exists($this->classifiers[0], 'getCostValues')) { + $this->costValues = $this->classifiers[0]->getCostValues(); + } } /** diff --git a/src/Phpml/Helper/Optimizer/ConjugateGradient.php b/src/Phpml/Helper/Optimizer/ConjugateGradient.php new file mode 100644 index 00000000..9bcb338d --- /dev/null +++ b/src/Phpml/Helper/Optimizer/ConjugateGradient.php @@ -0,0 +1,359 @@ +samples = $samples; + $this->targets = $targets; + $this->gradientCb = $gradientCb; + $this->sampleCount = count($samples); + $this->costValues = []; + + $d = mp::muls($this->gradient($this->theta), -1); + + for ($i=0; $i < $this->maxIterations; $i++) { + // Obtain α that minimizes f(θ + α.d) + $alpha = $this->getAlpha(array_sum($d)); + + // θ(k+1) = θ(k) + α.d + $thetaNew = $this->getNewTheta($alpha, $d); + + // β = ||∇f(x(k+1))||² ∕ ||∇f(x(k))||² + $beta = $this->getBeta($thetaNew); + + // d(k+1) =–∇f(x(k+1)) + β(k).d(k) + $d = $this->getNewDirection($thetaNew, $beta, $d); + + // Save values for the next iteration + $oldTheta = $this->theta; + $this->costValues[] = $this->cost($thetaNew); + + $this->theta = $thetaNew; + if ($this->enableEarlyStop && $this->earlyStop($oldTheta)) { + break; + } + } + + return $this->theta; + } + + /** + * Executes the callback function for the problem and returns + * sum of the gradient for all samples & targets. + * + * @param array $theta + * + * @return float + */ + protected function gradient(array $theta) + { + list($_, $gradient, $_) = parent::gradient($theta); + + return $gradient; + } + + /** + * Returns the value of f(x) for given solution + * + * @param array $theta + * + * @return float + */ + protected function cost(array $theta) + { + list($cost, $_, $_) = parent::gradient($theta); + + return array_sum($cost) / $this->sampleCount; + } + + /** + * Calculates alpha that minimizes the function f(θ + α.d) + * by performing a line search that does not rely upon the derivation. + * + * There are several alternatives for this function. For now, we + * prefer a method inspired from the bisection method for its simplicity. + * This algorithm attempts to find an optimum alpha value between 0.0001 and 0.01 + * + * Algorithm as follows: + * a) Probe a small alpha (0.0001) and calculate cost function + * b) Probe a larger alpha (0.01) and calculate cost function + * b-1) If cost function decreases, continue enlarging alpha + * b-2) If cost function increases, take the midpoint and try again + * + * @param float $d + * + * @return array + */ + protected function getAlpha(float $d) + { + $small = 0.0001 * $d; + $large = 0.01 * $d; + + // Obtain θ + α.d for two initial values, x0 and x1 + $x0 = mp::adds($this->theta, $small); + $x1 = mp::adds($this->theta, $large); + + $epsilon = 0.0001; + $iteration = 0; + do { + $fx1 = $this->cost($x1); + $fx0 = $this->cost($x0); + + // If the difference between two values is small enough + // then break the loop + if (abs($fx1 - $fx0) <= $epsilon) { + break; + } + + if ($fx1 < $fx0) { + $x0 = $x1; + $x1 = mp::adds($x1, 0.01); // Enlarge second + } else { + $x1 = mp::divs(mp::add($x1, $x0), 2.0); + } // Get to the midpoint + + $error = $fx1 / $this->dimensions; + } while ($error <= $epsilon || $iteration++ < 10); + + // Return α = θ / d + if ($d == 0) { + return $x1[0] - $this->theta[0]; + } + + return ($x1[0] - $this->theta[0]) / $d; + } + + /** + * Calculates new set of solutions with given alpha (for each θ(k)) and + * gradient direction. + * + * θ(k+1) = θ(k) + α.d + * + * @param float $alpha + * @param array $d + * + * return array + */ + protected function getNewTheta(float $alpha, array $d) + { + $theta = $this->theta; + + for ($i=0; $i < $this->dimensions + 1; $i++) { + if ($i == 0) { + $theta[$i] += $alpha * array_sum($d); + } else { + $sum = 0.0; + foreach ($this->samples as $si => $sample) { + $sum += $sample[$i - 1] * $d[$si] * $alpha; + } + + $theta[$i] += $sum; + } + } + + return $theta; + } + + /** + * Calculates new beta (β) for given set of solutions by using + * Fletcher–Reeves method. + * + * β = ||f(x(k+1))||² ∕ ||f(x(k))||² + * + * See: + * R. Fletcher and C. M. Reeves, "Function minimization by conjugate gradients", Comput. J. 7 (1964), 149–154. + * + * @param array $newTheta + * + * @return float + */ + protected function getBeta(array $newTheta) + { + $dNew = array_sum($this->gradient($newTheta)); + $dOld = array_sum($this->gradient($this->theta)) + 1e-100; + + return $dNew ** 2 / $dOld ** 2; + } + + /** + * Calculates the new conjugate direction + * + * d(k+1) =–∇f(x(k+1)) + β(k).d(k) + * + * @param array $theta + * @param float $beta + * @param array $d + * + * @return array + */ + protected function getNewDirection(array $theta, float $beta, array $d) + { + $grad = $this->gradient($theta); + + return mp::add(mp::muls($grad, -1), mp::muls($d, $beta)); + } +} + +/** + * Handles element-wise vector operations between vector-vector + * and vector-scalar variables + */ +class mp +{ + /** + * Element-wise multiplication of two vectors of the same size + * + * @param array $m1 + * @param array $m2 + * + * @return array + */ + public static function mul(array $m1, array $m2) + { + $res = []; + foreach ($m1 as $i => $val) { + $res[] = $val * $m2[$i]; + } + + return $res; + } + + /** + * Element-wise division of two vectors of the same size + * + * @param array $m1 + * @param array $m2 + * + * @return array + */ + public static function div(array $m1, array $m2) + { + $res = []; + foreach ($m1 as $i => $val) { + $res[] = $val / $m2[$i]; + } + + return $res; + } + + /** + * Element-wise addition of two vectors of the same size + * + * @param array $m1 + * @param array $m2 + * + * @return array + */ + public static function add(array $m1, array $m2, $mag = 1) + { + $res = []; + foreach ($m1 as $i => $val) { + $res[] = $val + $mag * $m2[$i]; + } + + return $res; + } + + /** + * Element-wise subtraction of two vectors of the same size + * + * @param array $m1 + * @param array $m2 + * + * @return array + */ + public static function sub(array $m1, array $m2) + { + return self::add($m1, $m2, -1); + } + + /** + * Element-wise multiplication of a vector with a scalar + * + * @param array $m1 + * @param float $m2 + * + * @return array + */ + public static function muls(array $m1, float $m2) + { + $res = []; + foreach ($m1 as $val) { + $res[] = $val * $m2; + } + + return $res; + } + + /** + * Element-wise division of a vector with a scalar + * + * @param array $m1 + * @param float $m2 + * + * @return array + */ + public static function divs(array $m1, float $m2) + { + $res = []; + foreach ($m1 as $val) { + $res[] = $val / ($m2 + 1e-32); + } + + return $res; + } + + /** + * Element-wise addition of a vector with a scalar + * + * @param array $m1 + * @param float $m2 + * + * @return array + */ + public static function adds(array $m1, float $m2, $mag = 1) + { + $res = []; + foreach ($m1 as $val) { + $res[] = $val + $mag * $m2; + } + + return $res; + } + + /** + * Element-wise subtraction of a vector with a scalar + * + * @param array $m1 + * @param float $m2 + * + * @return array + */ + public static function subs(array $m1, array $m2) + { + return self::adds($m1, $m2, -1); + } +} diff --git a/src/Phpml/Helper/Optimizer/GD.php b/src/Phpml/Helper/Optimizer/GD.php new file mode 100644 index 00000000..14029309 --- /dev/null +++ b/src/Phpml/Helper/Optimizer/GD.php @@ -0,0 +1,108 @@ +samples = $samples; + $this->targets = $targets; + $this->gradientCb = $gradientCb; + $this->sampleCount = count($this->samples); + + // Batch learning is executed: + $currIter = 0; + $this->costValues = []; + while ($this->maxIterations > $currIter++) { + $theta = $this->theta; + + // Calculate update terms for each sample + list($errors, $updates, $totalPenalty) = $this->gradient($theta); + + $this->updateWeightsWithUpdates($updates, $totalPenalty); + + $this->costValues[] = array_sum($errors)/$this->sampleCount; + + if ($this->earlyStop($theta)) { + break; + } + } + + return $this->theta; + } + + /** + * Calculates gradient, cost function and penalty term for each sample + * then returns them as an array of values + * + * @param array $theta + * + * @return array + */ + protected function gradient(array $theta) + { + $costs = []; + $gradient= []; + $totalPenalty = 0; + + foreach ($this->samples as $index => $sample) { + $target = $this->targets[$index]; + + $result = ($this->gradientCb)($theta, $sample, $target); + list($cost, $grad, $penalty) = array_pad($result, 3, 0); + + $costs[] = $cost; + $gradient[]= $grad; + $totalPenalty += $penalty; + } + + $totalPenalty /= $this->sampleCount; + + return [$costs, $gradient, $totalPenalty]; + } + + /** + * @param array $updates + * @param float $penalty + */ + protected function updateWeightsWithUpdates(array $updates, float $penalty) + { + // Updates all weights at once + for ($i=0; $i <= $this->dimensions; $i++) { + if ($i == 0) { + $this->theta[0] -= $this->learningRate * array_sum($updates); + } else { + $col = array_column($this->samples, $i - 1); + + $error = 0; + foreach ($col as $index => $val) { + $error += $val * $updates[$index]; + } + + $this->theta[$i] -= $this->learningRate * + ($error + $penalty * $this->theta[$i]); + } + } + } +} diff --git a/src/Phpml/Helper/Optimizer/Optimizer.php b/src/Phpml/Helper/Optimizer/Optimizer.php new file mode 100644 index 00000000..9ef4c4d0 --- /dev/null +++ b/src/Phpml/Helper/Optimizer/Optimizer.php @@ -0,0 +1,61 @@ +dimensions = $dimensions; + + // Inits the weights randomly + $this->theta = []; + for ($i=0; $i < $this->dimensions; $i++) { + $this->theta[] = rand() / (float) getrandmax(); + } + } + + /** + * Sets the weights manually + * + * @param array $theta + */ + public function setInitialTheta(array $theta) + { + if (count($theta) != $this->dimensions) { + throw new \Exception("Number of values in the weights array should be $this->dimensions"); + } + + $this->theta = $theta; + + return $this; + } + + /** + * Executes the optimization with the given samples & targets + * and returns the weights + * + */ + abstract protected function runOptimization(array $samples, array $targets, \Closure $gradientCb); +} diff --git a/src/Phpml/Helper/Optimizer/StochasticGD.php b/src/Phpml/Helper/Optimizer/StochasticGD.php new file mode 100644 index 00000000..5379a283 --- /dev/null +++ b/src/Phpml/Helper/Optimizer/StochasticGD.php @@ -0,0 +1,271 @@ + + * + * Larger values of lr may overshoot the optimum or even cause divergence + * while small values slows down the convergence and increases the time + * required for the training + * + * @var float + */ + protected $learningRate = 0.001; + + /** + * Minimum amount of change in the weights and error values + * between iterations that needs to be obtained to continue the training + * + * @var float + */ + protected $threshold = 1e-4; + + /** + * Enable/Disable early stopping by checking the weight & cost values + * to see whether they changed large enough to continue the optimization + * + * @var bool + */ + protected $enableEarlyStop = true; + /** + * List of values obtained by evaluating the cost function at each iteration + * of the algorithm + * + * @var array + */ + protected $costValues= []; + + /** + * Initializes the SGD optimizer for the given number of dimensions + * + * @param int $dimensions + */ + public function __construct(int $dimensions) + { + // Add one more dimension for the bias + parent::__construct($dimensions + 1); + + $this->dimensions = $dimensions; + } + + /** + * Sets minimum value for the change in the theta values + * between iterations to continue the iterations.
+ * + * If change in the theta is less than given value then the + * algorithm will stop training + * + * @param float $threshold + * + * @return $this + */ + public function setChangeThreshold(float $threshold = 1e-5) + { + $this->threshold = $threshold; + + return $this; + } + + /** + * Enable/Disable early stopping by checking at each iteration + * whether changes in theta or cost value are not large enough + * + * @param bool $enable + * + * @return $this + */ + public function setEarlyStop(bool $enable = true) + { + $this->enableEarlyStop = $enable; + + return $this; + } + + /** + * @param float $learningRate + * + * @return $this + */ + public function setLearningRate(float $learningRate) + { + $this->learningRate = $learningRate; + + return $this; + } + + /** + * @param int $maxIterations + * + * @return $this + */ + public function setMaxIterations(int $maxIterations) + { + $this->maxIterations = $maxIterations; + + return $this; + } + + /** + * Optimization procedure finds the unknow variables for the equation A.ϴ = y + * for the given samples (A) and targets (y).
+ * + * The cost function to minimize and the gradient of the function are to be + * handled by the callback function provided as the third parameter of the method. + * + * @param array $samples + * @param array $targets + * @param \Closure $gradientCb + * + * @return array + */ + public function runOptimization(array $samples, array $targets, \Closure $gradientCb) + { + $this->samples = $samples; + $this->targets = $targets; + $this->gradientCb = $gradientCb; + + $currIter = 0; + $bestTheta = null; + $bestScore = 0.0; + $bestWeightIter = 0; + $this->costValues = []; + + while ($this->maxIterations > $currIter++) { + $theta = $this->theta; + + // Update the guess + $cost = $this->updateTheta(); + + // Save the best theta in the "pocket" so that + // any future set of theta worse than this will be disregarded + if ($bestTheta == null || $cost <= $bestScore) { + $bestTheta = $theta; + $bestScore = $cost; + $bestWeightIter = $currIter; + } + + // Add the cost value for this iteration to the list + $this->costValues[] = $cost; + + // Check for early stop + if ($this->enableEarlyStop && $this->earlyStop($theta)) { + break; + } + } + + // Solution in the pocket is better than or equal to the last state + // so, we use this solution + return $this->theta = $bestTheta; + } + + /** + * @return float + */ + protected function updateTheta() + { + $jValue = 0.0; + $theta = $this->theta; + + foreach ($this->samples as $index => $sample) { + $target = $this->targets[$index]; + + $result = ($this->gradientCb)($theta, $sample, $target); + + list($error, $gradient, $penalty) = array_pad($result, 3, 0); + + // Update bias + $this->theta[0] -= $this->learningRate * $gradient; + + // Update other values + for ($i=1; $i <= $this->dimensions; $i++) { + $this->theta[$i] -= $this->learningRate * + ($gradient * $sample[$i - 1] + $penalty * $this->theta[$i]); + } + + // Sum error rate + $jValue += $error; + } + + return $jValue / count($this->samples); + } + + /** + * Checks if the optimization is not effective enough and can be stopped + * in case large enough changes in the solution do not happen + * + * @param array $oldTheta + * + * @return boolean + */ + protected function earlyStop($oldTheta) + { + // Check for early stop: No change larger than threshold (default 1e-5) + $diff = array_map( + function ($w1, $w2) { + return abs($w1 - $w2) > $this->threshold ? 1 : 0; + }, + $oldTheta, $this->theta); + + if (array_sum($diff) == 0) { + return true; + } + + // Check if the last two cost values are almost the same + $costs = array_slice($this->costValues, -2); + if (count($costs) == 2 && abs($costs[1] - $costs[0]) < $this->threshold) { + return true; + } + + return false; + } + + /** + * Returns the list of cost values for each iteration executed in + * last run of the optimization + * + * @return array + */ + public function getCostValues() + { + return $this->costValues; + } +} diff --git a/tests/Phpml/Classification/Ensemble/AdaBoostTest.php b/tests/Phpml/Classification/Ensemble/AdaBoostTest.php index c9e4d86a..8c177520 100644 --- a/tests/Phpml/Classification/Ensemble/AdaBoostTest.php +++ b/tests/Phpml/Classification/Ensemble/AdaBoostTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Classification\Linear; +namespace tests\Classification\Ensemble; use Phpml\Classification\Ensemble\AdaBoost; use Phpml\ModelManager; diff --git a/tests/Phpml/Classification/Linear/PerceptronTest.php b/tests/Phpml/Classification/Linear/PerceptronTest.php index ef820fc2..1f40c461 100644 --- a/tests/Phpml/Classification/Linear/PerceptronTest.php +++ b/tests/Phpml/Classification/Linear/PerceptronTest.php @@ -16,6 +16,7 @@ public function testPredictSingleSample() $samples = [[0, 0], [1, 0], [0, 1], [1, 1], [0.6, 0.6]]; $targets = [0, 0, 0, 1, 1]; $classifier = new Perceptron(0.001, 5000); + $classifier->setEarlyStop(false); $classifier->train($samples, $targets); $this->assertEquals(0, $classifier->predict([0.1, 0.2])); $this->assertEquals(0, $classifier->predict([0, 1])); @@ -25,6 +26,7 @@ public function testPredictSingleSample() $samples = [[0.1, 0.1], [0.4, 0.], [0., 0.3], [1, 0], [0, 1], [1, 1]]; $targets = [0, 0, 0, 1, 1, 1]; $classifier = new Perceptron(0.001, 5000, false); + $classifier->setEarlyStop(false); $classifier->train($samples, $targets); $this->assertEquals(0, $classifier->predict([0., 0.])); $this->assertEquals(1, $classifier->predict([0.1, 0.99])); @@ -40,11 +42,12 @@ public function testPredictSingleSample() $targets = [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2]; $classifier = new Perceptron(); + $classifier->setEarlyStop(false); $classifier->train($samples, $targets); $this->assertEquals(0, $classifier->predict([0.5, 0.5])); $this->assertEquals(1, $classifier->predict([6.0, 5.0])); $this->assertEquals(2, $classifier->predict([3.0, 9.5])); - + return $classifier; } From b27f08f420456e4fc385a1b0570271a3a3f6ca24 Mon Sep 17 00:00:00 2001 From: Humberto Castelo Branco Date: Wed, 29 Mar 2017 07:58:12 -0300 Subject: [PATCH 167/328] Add delimiter option for CsvDataset (#66) Useful option when the CSV file uses another delimiter character other than the comma, for example, as the semicolon or tab character. --- src/Phpml/Dataset/CsvDataset.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Phpml/Dataset/CsvDataset.php b/src/Phpml/Dataset/CsvDataset.php index 483b1af8..8bcd3c49 100644 --- a/src/Phpml/Dataset/CsvDataset.php +++ b/src/Phpml/Dataset/CsvDataset.php @@ -20,7 +20,7 @@ class CsvDataset extends ArrayDataset * * @throws FileException */ - public function __construct(string $filepath, int $features, bool $headingRow = true) + public function __construct(string $filepath, int $features, bool $headingRow = true, string $delimiter = ',') { if (!file_exists($filepath)) { throw FileException::missingFile(basename($filepath)); @@ -31,13 +31,13 @@ public function __construct(string $filepath, int $features, bool $headingRow = } if ($headingRow) { - $data = fgetcsv($handle, 1000, ','); + $data = fgetcsv($handle, 1000, $delimiter); $this->columnNames = array_slice($data, 0, $features); } else { $this->columnNames = range(0, $features - 1); } - while (($data = fgetcsv($handle, 1000, ',')) !== false) { + while (($data = fgetcsv($handle, 1000, $delimiter)) !== false) { $this->samples[] = array_slice($data, 0, $features); $this->targets[] = $data[$features]; } From c0463ae087c83ed4cc177d53626b48c1fa6bba14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Monlla=C3=B3?= Date: Thu, 13 Apr 2017 21:34:55 +0200 Subject: [PATCH 168/328] Fix wrong docs references (#79) --- docs/machine-learning/datasets/demo/glass.md | 4 ++-- docs/machine-learning/datasets/demo/iris.md | 4 ++-- docs/machine-learning/datasets/demo/wine.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/machine-learning/datasets/demo/glass.md b/docs/machine-learning/datasets/demo/glass.md index 1ad84d3e..5ba16659 100644 --- a/docs/machine-learning/datasets/demo/glass.md +++ b/docs/machine-learning/datasets/demo/glass.md @@ -21,9 +21,9 @@ Samples per class: To load Glass dataset simple use: ``` -use Phpml\Dataset\Demo\Glass; +use Phpml\Dataset\Demo\GlassDataset; -$dataset = new Glass(); +$dataset = new GlassDataset(); ``` ### Several samples example diff --git a/docs/machine-learning/datasets/demo/iris.md b/docs/machine-learning/datasets/demo/iris.md index 8baf7312..4b602cfa 100644 --- a/docs/machine-learning/datasets/demo/iris.md +++ b/docs/machine-learning/datasets/demo/iris.md @@ -14,9 +14,9 @@ Most popular and widely available dataset of iris flower measurement and class n To load Iris dataset simple use: ``` -use Phpml\Dataset\Demo\Iris; +use Phpml\Dataset\Demo\IrisDataset; -$dataset = new Iris(); +$dataset = new IrisDataset(); ``` ### Several samples example diff --git a/docs/machine-learning/datasets/demo/wine.md b/docs/machine-learning/datasets/demo/wine.md index 5b3f999b..76a157b8 100644 --- a/docs/machine-learning/datasets/demo/wine.md +++ b/docs/machine-learning/datasets/demo/wine.md @@ -14,9 +14,9 @@ These data are the results of a chemical analysis of wines grown in the same reg To load Wine dataset simple use: ``` -use Phpml\Dataset\Demo\Wine; +use Phpml\Dataset\Demo\WineDataset; -$dataset = new Wine(); +$dataset = new WineDataset(); ``` ### Several samples example From e1854d44a26c062a4f1538f9eb8135a12793314f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Monlla=C3=B3?= Date: Thu, 20 Apr 2017 04:26:31 +0800 Subject: [PATCH 169/328] Partial training base (#78) * Cost values for multiclass OneVsRest uses * Partial training interface * Reduce linear classifiers memory usage * Testing partial training and isolated training * Partial trainer naming switched to incremental estimator Other changes according to review's feedback. * Clean optimization data once optimize is finished * Abstract resetBinary --- src/Phpml/Classification/Linear/Adaline.php | 7 +- .../Classification/Linear/DecisionStump.php | 54 ++++--- .../Linear/LogisticRegression.php | 23 +-- .../Classification/Linear/Perceptron.php | 80 ++++++---- src/Phpml/Helper/OneVsRest.php | 137 +++++++++++++----- .../Helper/Optimizer/ConjugateGradient.php | 2 + src/Phpml/Helper/Optimizer/GD.php | 15 +- src/Phpml/Helper/Optimizer/StochasticGD.php | 20 ++- src/Phpml/IncrementalEstimator.php | 16 ++ .../Classification/Linear/AdalineTest.php | 18 ++- .../Classification/Linear/PerceptronTest.php | 18 ++- 11 files changed, 283 insertions(+), 107 deletions(-) create mode 100644 src/Phpml/IncrementalEstimator.php diff --git a/src/Phpml/Classification/Linear/Adaline.php b/src/Phpml/Classification/Linear/Adaline.php index 8d94be41..f34dc5c4 100644 --- a/src/Phpml/Classification/Linear/Adaline.php +++ b/src/Phpml/Classification/Linear/Adaline.php @@ -53,8 +53,11 @@ public function __construct(float $learningRate = 0.001, int $maxIterations = 10 /** * Adapts the weights with respect to given samples and targets * by use of gradient descent learning rule + * + * @param array $samples + * @param array $targets */ - protected function runTraining() + protected function runTraining(array $samples, array $targets) { // The cost function is the sum of squares $callback = function ($weights, $sample, $target) { @@ -69,6 +72,6 @@ protected function runTraining() $isBatch = $this->trainingType == self::BATCH_TRAINING; - return parent::runGradientDescent($callback, $isBatch); + return parent::runGradientDescent($samples, $targets, $callback, $isBatch); } } diff --git a/src/Phpml/Classification/Linear/DecisionStump.php b/src/Phpml/Classification/Linear/DecisionStump.php index 13bc4a5a..99f982ff 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -89,15 +89,13 @@ public function __construct(int $columnIndex = self::AUTO_SELECT) * @param array $targets * @throws \Exception */ - protected function trainBinary(array $samples, array $targets) + protected function trainBinary(array $samples, array $targets, array $labels) { - $this->samples = array_merge($this->samples, $samples); - $this->targets = array_merge($this->targets, $targets); - $this->binaryLabels = array_keys(array_count_values($this->targets)); - $this->featureCount = count($this->samples[0]); + $this->binaryLabels = $labels; + $this->featureCount = count($samples[0]); // If a column index is given, it should be among the existing columns - if ($this->givenColumnIndex > count($this->samples[0]) - 1) { + if ($this->givenColumnIndex > count($samples[0]) - 1) { $this->givenColumnIndex = self::AUTO_SELECT; } @@ -105,19 +103,19 @@ protected function trainBinary(array $samples, array $targets) // If none given, then assign 1 as a weight to each sample if ($this->weights) { $numWeights = count($this->weights); - if ($numWeights != count($this->samples)) { + if ($numWeights != count($samples)) { throw new \Exception("Number of sample weights does not match with number of samples"); } } else { - $this->weights = array_fill(0, count($this->samples), 1); + $this->weights = array_fill(0, count($samples), 1); } // Determine type of each column as either "continuous" or "nominal" - $this->columnTypes = DecisionTree::getColumnTypes($this->samples); + $this->columnTypes = DecisionTree::getColumnTypes($samples); // Try to find the best split in the columns of the dataset // by calculating error rate for each split point in each column - $columns = range(0, count($this->samples[0]) - 1); + $columns = range(0, count($samples[0]) - 1); if ($this->givenColumnIndex != self::AUTO_SELECT) { $columns = [$this->givenColumnIndex]; } @@ -128,9 +126,9 @@ protected function trainBinary(array $samples, array $targets) 'trainingErrorRate' => 1.0]; foreach ($columns as $col) { if ($this->columnTypes[$col] == DecisionTree::CONTINUOUS) { - $split = $this->getBestNumericalSplit($col); + $split = $this->getBestNumericalSplit($samples, $targets, $col); } else { - $split = $this->getBestNominalSplit($col); + $split = $this->getBestNominalSplit($samples, $targets, $col); } if ($split['trainingErrorRate'] < $bestSplit['trainingErrorRate']) { @@ -161,13 +159,15 @@ public function setNumericalSplitCount(float $count) /** * Determines best split point for the given column * + * @param array $samples + * @param array $targets * @param int $col * * @return array */ - protected function getBestNumericalSplit(int $col) + protected function getBestNumericalSplit(array $samples, array $targets, int $col) { - $values = array_column($this->samples, $col); + $values = array_column($samples, $col); // Trying all possible points may be accomplished in two general ways: // 1- Try all values in the $samples array ($values) // 2- Artificially split the range of values into several parts and try them @@ -182,7 +182,7 @@ protected function getBestNumericalSplit(int $col) // Before trying all possible split points, let's first try // the average value for the cut point $threshold = array_sum($values) / (float) count($values); - list($errorRate, $prob) = $this->calculateErrorRate($threshold, $operator, $values); + list($errorRate, $prob) = $this->calculateErrorRate($targets, $threshold, $operator, $values); if ($split == null || $errorRate < $split['trainingErrorRate']) { $split = ['value' => $threshold, 'operator' => $operator, 'prob' => $prob, 'column' => $col, @@ -192,7 +192,7 @@ protected function getBestNumericalSplit(int $col) // Try other possible points one by one for ($step = $minValue; $step <= $maxValue; $step+= $stepSize) { $threshold = (float)$step; - list($errorRate, $prob) = $this->calculateErrorRate($threshold, $operator, $values); + list($errorRate, $prob) = $this->calculateErrorRate($targets, $threshold, $operator, $values); if ($errorRate < $split['trainingErrorRate']) { $split = ['value' => $threshold, 'operator' => $operator, 'prob' => $prob, 'column' => $col, @@ -205,13 +205,15 @@ protected function getBestNumericalSplit(int $col) } /** + * @param array $samples + * @param array $targets * @param int $col * * @return array */ - protected function getBestNominalSplit(int $col) : array + protected function getBestNominalSplit(array $samples, array $targets, int $col) : array { - $values = array_column($this->samples, $col); + $values = array_column($samples, $col); $valueCounts = array_count_values($values); $distinctVals= array_keys($valueCounts); @@ -219,7 +221,7 @@ protected function getBestNominalSplit(int $col) : array foreach (['=', '!='] as $operator) { foreach ($distinctVals as $val) { - list($errorRate, $prob) = $this->calculateErrorRate($val, $operator, $values); + list($errorRate, $prob) = $this->calculateErrorRate($targets, $val, $operator, $values); if ($split == null || $split['trainingErrorRate'] < $errorRate) { $split = ['value' => $val, 'operator' => $operator, @@ -260,13 +262,14 @@ protected function evaluate($leftValue, $operator, $rightValue) * Calculates the ratio of wrong predictions based on the new threshold * value given as the parameter * + * @param array $targets * @param float $threshold * @param string $operator * @param array $values * * @return array */ - protected function calculateErrorRate(float $threshold, string $operator, array $values) : array + protected function calculateErrorRate(array $targets, float $threshold, string $operator, array $values) : array { $wrong = 0.0; $prob = []; @@ -280,8 +283,8 @@ protected function calculateErrorRate(float $threshold, string $operator, array $predicted = $rightLabel; } - $target = $this->targets[$index]; - if (strval($predicted) != strval($this->targets[$index])) { + $target = $targets[$index]; + if (strval($predicted) != strval($targets[$index])) { $wrong += $this->weights[$index]; } @@ -340,6 +343,13 @@ protected function predictSampleBinary(array $sample) return $this->binaryLabels[1]; } + /** + * @return void + */ + protected function resetBinary() + { + } + /** * @return string */ diff --git a/src/Phpml/Classification/Linear/LogisticRegression.php b/src/Phpml/Classification/Linear/LogisticRegression.php index a0ec2900..bd56d347 100644 --- a/src/Phpml/Classification/Linear/LogisticRegression.php +++ b/src/Phpml/Classification/Linear/LogisticRegression.php @@ -123,20 +123,23 @@ public function setLambda(float $lambda) /** * Adapts the weights with respect to given samples and targets * by use of selected solver + * + * @param array $samples + * @param array $targets */ - protected function runTraining() + protected function runTraining(array $samples, array $targets) { $callback = $this->getCostFunction(); switch ($this->trainingType) { case self::BATCH_TRAINING: - return $this->runGradientDescent($callback, true); + return $this->runGradientDescent($samples, $targets, $callback, true); case self::ONLINE_TRAINING: - return $this->runGradientDescent($callback, false); + return $this->runGradientDescent($samples, $targets, $callback, false); case self::CONJUGATE_GRAD_TRAINING: - return $this->runConjugateGradient($callback); + return $this->runConjugateGradient($samples, $targets, $callback); } } @@ -144,13 +147,15 @@ protected function runTraining() * Executes Conjugate Gradient method to optimize the * weights of the LogReg model */ - protected function runConjugateGradient(\Closure $gradientFunc) + protected function runConjugateGradient(array $samples, array $targets, \Closure $gradientFunc) { - $optimizer = (new ConjugateGradient($this->featureCount)) - ->setMaxIterations($this->maxIterations); + if (empty($this->optimizer)) { + $this->optimizer = (new ConjugateGradient($this->featureCount)) + ->setMaxIterations($this->maxIterations); + } - $this->weights = $optimizer->runOptimization($this->samples, $this->targets, $gradientFunc); - $this->costValues = $optimizer->getCostValues(); + $this->weights = $this->optimizer->runOptimization($samples, $targets, $gradientFunc); + $this->costValues = $this->optimizer->getCostValues(); } /** diff --git a/src/Phpml/Classification/Linear/Perceptron.php b/src/Phpml/Classification/Linear/Perceptron.php index 8280bcbf..2cf96cc6 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -10,20 +10,17 @@ use Phpml\Helper\Optimizer\GD; use Phpml\Classification\Classifier; use Phpml\Preprocessing\Normalizer; +use Phpml\IncrementalEstimator; +use Phpml\Helper\PartiallyTrainable; -class Perceptron implements Classifier +class Perceptron implements Classifier, IncrementalEstimator { use Predictable, OneVsRest; - /** - * @var array - */ - protected $samples = []; - /** - * @var array + * @var \Phpml\Helper\Optimizer\Optimizer */ - protected $targets = []; + protected $optimizer; /** * @var array @@ -93,32 +90,47 @@ public function __construct(float $learningRate = 0.001, int $maxIterations = 10 $this->maxIterations = $maxIterations; } + /** + * @param array $samples + * @param array $targets + * @param array $labels + */ + public function partialTrain(array $samples, array $targets, array $labels = array()) + { + return $this->trainByLabel($samples, $targets, $labels); + } + /** * @param array $samples * @param array $targets + * @param array $labels */ - public function trainBinary(array $samples, array $targets) + public function trainBinary(array $samples, array $targets, array $labels) { - $this->labels = array_keys(array_count_values($targets)); - if (count($this->labels) > 2) { - throw new \Exception("Perceptron is for binary (two-class) classification only"); - } if ($this->normalizer) { $this->normalizer->transform($samples); } // Set all target values to either -1 or 1 - $this->labels = [1 => $this->labels[0], -1 => $this->labels[1]]; - foreach ($targets as $target) { - $this->targets[] = strval($target) == strval($this->labels[1]) ? 1 : -1; + $this->labels = [1 => $labels[0], -1 => $labels[1]]; + foreach ($targets as $key => $target) { + $targets[$key] = strval($target) == strval($this->labels[1]) ? 1 : -1; } // Set samples and feature count vars - $this->samples = array_merge($this->samples, $samples); - $this->featureCount = count($this->samples[0]); + $this->featureCount = count($samples[0]); + + $this->runTraining($samples, $targets); + } - $this->runTraining(); + protected function resetBinary() + { + $this->labels = []; + $this->optimizer = null; + $this->featureCount = 0; + $this->weights = null; + $this->costValues = []; } /** @@ -151,8 +163,11 @@ public function getCostValues() /** * Trains the perceptron model with Stochastic Gradient Descent optimization * to get the correct set of weights + * + * @param array $samples + * @param array $targets */ - protected function runTraining() + protected function runTraining(array $samples, array $targets) { // The cost function is the sum of squares $callback = function ($weights, $sample, $target) { @@ -165,25 +180,30 @@ protected function runTraining() return [$error, $gradient]; }; - $this->runGradientDescent($callback); + $this->runGradientDescent($samples, $targets, $callback); } /** - * Executes Stochastic Gradient Descent algorithm for + * Executes a Gradient Descent algorithm for * the given cost function + * + * @param array $samples + * @param array $targets */ - protected function runGradientDescent(\Closure $gradientFunc, bool $isBatch = false) + protected function runGradientDescent(array $samples, array $targets, \Closure $gradientFunc, bool $isBatch = false) { $class = $isBatch ? GD::class : StochasticGD::class; - $optimizer = (new $class($this->featureCount)) - ->setLearningRate($this->learningRate) - ->setMaxIterations($this->maxIterations) - ->setChangeThreshold(1e-6) - ->setEarlyStop($this->enableEarlyStop); + if (empty($this->optimizer)) { + $this->optimizer = (new $class($this->featureCount)) + ->setLearningRate($this->learningRate) + ->setMaxIterations($this->maxIterations) + ->setChangeThreshold(1e-6) + ->setEarlyStop($this->enableEarlyStop); + } - $this->weights = $optimizer->runOptimization($this->samples, $this->targets, $gradientFunc); - $this->costValues = $optimizer->getCostValues(); + $this->weights = $this->optimizer->runOptimization($samples, $targets, $gradientFunc); + $this->costValues = $this->optimizer->getCostValues(); } /** diff --git a/src/Phpml/Helper/OneVsRest.php b/src/Phpml/Helper/OneVsRest.php index 9e7bc82c..98269cdb 100644 --- a/src/Phpml/Helper/OneVsRest.php +++ b/src/Phpml/Helper/OneVsRest.php @@ -6,30 +6,23 @@ trait OneVsRest { - /** - * @var array - */ - protected $samples = []; - - /** - * @var array - */ - protected $targets = []; /** * @var array */ - protected $classifiers; + protected $classifiers = []; /** + * All provided training targets' labels. + * * @var array */ - protected $labels; + protected $allLabels = []; /** * @var array */ - protected $costValues; + protected $costValues = []; /** * Train a binary classifier in the OvR style @@ -39,51 +32,111 @@ trait OneVsRest */ public function train(array $samples, array $targets) { - // Clone the current classifier, so that - // we don't mess up its variables while training - // multiple instances of this classifier - $classifier = clone $this; - $this->classifiers = []; + // Clears previous stuff. + $this->reset(); + + return $this->trainBylabel($samples, $targets); + } + + /** + * @param array $samples + * @param array $targets + * @param array $allLabels All training set labels + * @return void + */ + protected function trainByLabel(array $samples, array $targets, array $allLabels = array()) + { + + // Overwrites the current value if it exist. $allLabels must be provided for each partialTrain run. + if (!empty($allLabels)) { + $this->allLabels = $allLabels; + } else { + $this->allLabels = array_keys(array_count_values($targets)); + } + sort($this->allLabels, SORT_STRING); // If there are only two targets, then there is no need to perform OvR - $this->labels = array_keys(array_count_values($targets)); - if (count($this->labels) == 2) { - $classifier->trainBinary($samples, $targets); - $this->classifiers[] = $classifier; + if (count($this->allLabels) == 2) { + + // Init classifier if required. + if (empty($this->classifiers)) { + $this->classifiers[0] = $this->getClassifierCopy(); + } + + $this->classifiers[0]->trainBinary($samples, $targets, $this->allLabels); } else { // Train a separate classifier for each label and memorize them - $this->samples = $samples; - $this->targets = $targets; - foreach ($this->labels as $label) { - $predictor = clone $classifier; - $targets = $this->binarizeTargets($label); - $predictor->trainBinary($samples, $targets); - $this->classifiers[$label] = $predictor; + + foreach ($this->allLabels as $label) { + + // Init classifier if required. + if (empty($this->classifiers[$label])) { + $this->classifiers[$label] = $this->getClassifierCopy(); + } + + list($binarizedTargets, $classifierLabels) = $this->binarizeTargets($targets, $label); + $this->classifiers[$label]->trainBinary($samples, $binarizedTargets, $classifierLabels); } } - + // If the underlying classifier is capable of giving the cost values // during the training, then assign it to the relevant variable - if (method_exists($this->classifiers[0], 'getCostValues')) { - $this->costValues = $this->classifiers[0]->getCostValues(); + // Adding just the first classifier cost values to avoid complex average calculations. + $classifierref = reset($this->classifiers); + if (method_exists($classifierref, 'getCostValues')) { + $this->costValues = $classifierref->getCostValues(); } } + /** + * Resets the classifier and the vars internally used by OneVsRest to create multiple classifiers. + */ + public function reset() + { + $this->classifiers = []; + $this->allLabels = []; + $this->costValues = []; + + $this->resetBinary(); + } + + /** + * Returns an instance of the current class after cleaning up OneVsRest stuff. + * + * @return \Phpml\Estimator + */ + protected function getClassifierCopy() + { + + // Clone the current classifier, so that + // we don't mess up its variables while training + // multiple instances of this classifier + $classifier = clone $this; + $classifier->reset(); + return $classifier; + } + /** * Groups all targets into two groups: Targets equal to * the given label and the others * + * $targets is not passed by reference nor contains objects so this method + * changes will not affect the caller $targets array. + * + * @param array $targets * @param mixed $label + * @return array Binarized targets and target's labels */ - private function binarizeTargets($label) + private function binarizeTargets($targets, $label) { - $targets = []; - foreach ($this->targets as $target) { - $targets[] = $target == $label ? $label : "not_$label"; + $notLabel = "not_$label"; + foreach ($targets as $key => $target) { + $targets[$key] = $target == $label ? $label : $notLabel; } - return $targets; + $labels = array($label, $notLabel); + return array($targets, $labels); } @@ -94,7 +147,7 @@ private function binarizeTargets($label) */ protected function predictSample(array $sample) { - if (count($this->labels) == 2) { + if (count($this->allLabels) == 2) { return $this->classifiers[0]->predictSampleBinary($sample); } @@ -113,8 +166,16 @@ protected function predictSample(array $sample) * * @param array $samples * @param array $targets + * @param array $labels + */ + abstract protected function trainBinary(array $samples, array $targets, array $labels); + + /** + * To be overwritten by OneVsRest classifiers. + * + * @return void */ - abstract protected function trainBinary(array $samples, array $targets); + abstract protected function resetBinary(); /** * Each classifier that make use of OvR approach should be able to diff --git a/src/Phpml/Helper/Optimizer/ConjugateGradient.php b/src/Phpml/Helper/Optimizer/ConjugateGradient.php index 9bcb338d..18ae89a0 100644 --- a/src/Phpml/Helper/Optimizer/ConjugateGradient.php +++ b/src/Phpml/Helper/Optimizer/ConjugateGradient.php @@ -57,6 +57,8 @@ public function runOptimization(array $samples, array $targets, \Closure $gradie } } + $this->clear(); + return $this->theta; } diff --git a/src/Phpml/Helper/Optimizer/GD.php b/src/Phpml/Helper/Optimizer/GD.php index 14029309..8974c8e7 100644 --- a/src/Phpml/Helper/Optimizer/GD.php +++ b/src/Phpml/Helper/Optimizer/GD.php @@ -15,7 +15,7 @@ class GD extends StochasticGD * * @var int */ - protected $sampleCount; + protected $sampleCount = null; /** * @param array $samples @@ -49,6 +49,8 @@ public function runOptimization(array $samples, array $targets, \Closure $gradie } } + $this->clear(); + return $this->theta; } @@ -105,4 +107,15 @@ protected function updateWeightsWithUpdates(array $updates, float $penalty) } } } + + /** + * Clears the optimizer internal vars after the optimization process. + * + * @return void + */ + protected function clear() + { + $this->sampleCount = null; + parent::clear(); + } } diff --git a/src/Phpml/Helper/Optimizer/StochasticGD.php b/src/Phpml/Helper/Optimizer/StochasticGD.php index 5379a283..e9e318a8 100644 --- a/src/Phpml/Helper/Optimizer/StochasticGD.php +++ b/src/Phpml/Helper/Optimizer/StochasticGD.php @@ -16,14 +16,14 @@ class StochasticGD extends Optimizer * * @var array */ - protected $samples; + protected $samples = []; /** * y (targets) * * @var array */ - protected $targets; + protected $targets = []; /** * Callback function to get the gradient and cost value @@ -31,7 +31,7 @@ class StochasticGD extends Optimizer * * @var \Closure */ - protected $gradientCb; + protected $gradientCb = null; /** * Maximum number of iterations used to train the model @@ -192,6 +192,8 @@ public function runOptimization(array $samples, array $targets, \Closure $gradie } } + $this->clear(); + // Solution in the pocket is better than or equal to the last state // so, we use this solution return $this->theta = $bestTheta; @@ -268,4 +270,16 @@ public function getCostValues() { return $this->costValues; } + + /** + * Clears the optimizer internal vars after the optimization process. + * + * @return void + */ + protected function clear() + { + $this->samples = []; + $this->targets = []; + $this->gradientCb = null; + } } diff --git a/src/Phpml/IncrementalEstimator.php b/src/Phpml/IncrementalEstimator.php new file mode 100644 index 00000000..df188728 --- /dev/null +++ b/src/Phpml/IncrementalEstimator.php @@ -0,0 +1,16 @@ +assertEquals(1, $classifier->predict([6.0, 5.0])); $this->assertEquals(2, $classifier->predict([3.0, 9.5])); - return $classifier; + // Extra partial training should lead to the same results. + $classifier->partialTrain([[0, 1], [1, 0]], [0, 0], [0, 1, 2]); + $this->assertEquals(0, $classifier->predict([0.5, 0.5])); + $this->assertEquals(1, $classifier->predict([6.0, 5.0])); + $this->assertEquals(2, $classifier->predict([3.0, 9.5])); + + // Train should clear previous data. + $samples = [ + [0, 0], [0, 1], [1, 0], [1, 1], // First group : a cluster at bottom-left corner in 2D + [5, 5], [6, 5], [5, 6], [7, 5], // Second group: another cluster at the middle-right + [3, 10],[3, 10],[3, 8], [3, 9] // Third group : cluster at the top-middle + ]; + $targets = [2, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1]; + $classifier->train($samples, $targets); + $this->assertEquals(2, $classifier->predict([0.5, 0.5])); + $this->assertEquals(0, $classifier->predict([6.0, 5.0])); + $this->assertEquals(1, $classifier->predict([3.0, 9.5])); } public function testSaveAndRestore() diff --git a/tests/Phpml/Classification/Linear/PerceptronTest.php b/tests/Phpml/Classification/Linear/PerceptronTest.php index 1f40c461..132a6d79 100644 --- a/tests/Phpml/Classification/Linear/PerceptronTest.php +++ b/tests/Phpml/Classification/Linear/PerceptronTest.php @@ -48,7 +48,23 @@ public function testPredictSingleSample() $this->assertEquals(1, $classifier->predict([6.0, 5.0])); $this->assertEquals(2, $classifier->predict([3.0, 9.5])); - return $classifier; + // Extra partial training should lead to the same results. + $classifier->partialTrain([[0, 1], [1, 0]], [0, 0], [0, 1, 2]); + $this->assertEquals(0, $classifier->predict([0.5, 0.5])); + $this->assertEquals(1, $classifier->predict([6.0, 5.0])); + $this->assertEquals(2, $classifier->predict([3.0, 9.5])); + + // Train should clear previous data. + $samples = [ + [0, 0], [0, 1], [1, 0], [1, 1], // First group : a cluster at bottom-left corner in 2D + [5, 5], [6, 5], [5, 6], [7, 5], // Second group: another cluster at the middle-right + [3, 10],[3, 10],[3, 8], [3, 9] // Third group : cluster at the top-middle + ]; + $targets = [2, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1]; + $classifier->train($samples, $targets); + $this->assertEquals(2, $classifier->predict([0.5, 0.5])); + $this->assertEquals(0, $classifier->predict([6.0, 5.0])); + $this->assertEquals(1, $classifier->predict([3.0, 9.5])); } public function testSaveAndRestore() From 6296e44db08a9f8677cd3a1e5f216845e4ead3d9 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 19 Apr 2017 22:28:07 +0200 Subject: [PATCH 170/328] cs fixer --- src/Phpml/Classification/Linear/Perceptron.php | 4 +--- src/Phpml/Helper/OneVsRest.php | 7 +++---- src/Phpml/IncrementalEstimator.php | 2 +- src/Phpml/SupportVectorMachine/SupportVectorMachine.php | 7 +++---- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/Phpml/Classification/Linear/Perceptron.php b/src/Phpml/Classification/Linear/Perceptron.php index 2cf96cc6..91ffacf9 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -11,7 +11,6 @@ use Phpml\Classification\Classifier; use Phpml\Preprocessing\Normalizer; use Phpml\IncrementalEstimator; -use Phpml\Helper\PartiallyTrainable; class Perceptron implements Classifier, IncrementalEstimator { @@ -95,7 +94,7 @@ public function __construct(float $learningRate = 0.001, int $maxIterations = 10 * @param array $targets * @param array $labels */ - public function partialTrain(array $samples, array $targets, array $labels = array()) + public function partialTrain(array $samples, array $targets, array $labels = []) { return $this->trainByLabel($samples, $targets, $labels); } @@ -107,7 +106,6 @@ public function partialTrain(array $samples, array $targets, array $labels = arr */ public function trainBinary(array $samples, array $targets, array $labels) { - if ($this->normalizer) { $this->normalizer->transform($samples); } diff --git a/src/Phpml/Helper/OneVsRest.php b/src/Phpml/Helper/OneVsRest.php index 98269cdb..e207c46b 100644 --- a/src/Phpml/Helper/OneVsRest.php +++ b/src/Phpml/Helper/OneVsRest.php @@ -44,7 +44,7 @@ public function train(array $samples, array $targets) * @param array $allLabels All training set labels * @return void */ - protected function trainByLabel(array $samples, array $targets, array $allLabels = array()) + protected function trainByLabel(array $samples, array $targets, array $allLabels = []) { // Overwrites the current value if it exist. $allLabels must be provided for each partialTrain run. @@ -129,14 +129,13 @@ protected function getClassifierCopy() */ private function binarizeTargets($targets, $label) { - $notLabel = "not_$label"; foreach ($targets as $key => $target) { $targets[$key] = $target == $label ? $label : $notLabel; } - $labels = array($label, $notLabel); - return array($targets, $labels); + $labels = [$label, $notLabel]; + return [$targets, $labels]; } diff --git a/src/Phpml/IncrementalEstimator.php b/src/Phpml/IncrementalEstimator.php index df188728..fc6912d1 100644 --- a/src/Phpml/IncrementalEstimator.php +++ b/src/Phpml/IncrementalEstimator.php @@ -12,5 +12,5 @@ interface IncrementalEstimator * @param array $targets * @param array $labels */ - public function partialTrain(array $samples, array $targets, array $labels = array()); + public function partialTrain(array $samples, array $targets, array $labels = []); } diff --git a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php index 27b9e5a2..f3828b4d 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -6,12 +6,11 @@ use Phpml\Helper\Trainable; - class SupportVectorMachine { - use Trainable; - - /** + use Trainable; + + /** * @var int */ private $type; From a87859dd971ee14ba42b7d851a36ebd1028c318a Mon Sep 17 00:00:00 2001 From: Mustafa Karabulut Date: Sun, 23 Apr 2017 10:03:30 +0300 Subject: [PATCH 171/328] Linear algebra operations, Dimensionality reduction and some other minor changes (#81) * Lineer Algebra operations * Covariance * PCA and KernelPCA * Tests for PCA, Eigenvalues and Covariance * KernelPCA update * KernelPCA and its test * KernelPCA and its test * MatrixTest, KernelPCA and PCA tests * Readme update * Readme update --- README.md | 10 +- src/Phpml/DimensionReduction/KernelPCA.php | 246 +++++ src/Phpml/DimensionReduction/PCA.php | 228 +++++ .../Exception/InvalidArgumentException.php | 2 +- src/Phpml/Math/Distance/Euclidean.php | 13 + .../LinearAlgebra/EigenvalueDecomposition.php | 890 ++++++++++++++++++ src/Phpml/Math/Matrix.php | 127 ++- src/Phpml/Math/Statistic/Covariance.php | 155 +++ .../DimensionReduction/KernelPCATest.php | 51 + tests/Phpml/DimensionReduction/PCATest.php | 57 ++ .../LinearAlgebra/EigenDecompositionTest.php | 65 ++ tests/Phpml/Math/MatrixTest.php | 76 ++ tests/Phpml/Math/Statistic/CovarianceTest.php | 62 ++ 13 files changed, 1965 insertions(+), 17 deletions(-) create mode 100644 src/Phpml/DimensionReduction/KernelPCA.php create mode 100644 src/Phpml/DimensionReduction/PCA.php create mode 100644 src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php create mode 100644 src/Phpml/Math/Statistic/Covariance.php create mode 100644 tests/Phpml/DimensionReduction/KernelPCATest.php create mode 100644 tests/Phpml/DimensionReduction/PCATest.php create mode 100644 tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php create mode 100644 tests/Phpml/Math/Statistic/CovarianceTest.php diff --git a/README.md b/README.md index c362a2c1..bab758cd 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ $labels = ['a', 'a', 'a', 'b', 'b', 'b']; $classifier = new KNearestNeighbors(); $classifier->train($samples, $labels); -$classifier->predict([3, 2]); +$classifier->predict([3, 2]); // return 'b' ``` @@ -61,12 +61,14 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * Adaline * Decision Stump * Perceptron + * LogisticRegression * Regression * [Least Squares](http://php-ml.readthedocs.io/en/latest/machine-learning/regression/least-squares/) * [SVR](http://php-ml.readthedocs.io/en/latest/machine-learning/regression/svr/) * Clustering * [k-Means](http://php-ml.readthedocs.io/en/latest/machine-learning/clustering/k-means/) * [DBSCAN](http://php-ml.readthedocs.io/en/latest/machine-learning/clustering/dbscan/) + * Fuzzy C-Means * Metric * [Accuracy](http://php-ml.readthedocs.io/en/latest/machine-learning/metric/accuracy/) * [Confusion Matrix](http://php-ml.readthedocs.io/en/latest/machine-learning/metric/confusion-matrix/) @@ -85,6 +87,9 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * Feature Extraction * [Token Count Vectorizer](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-extraction/token-count-vectorizer/) * [Tf-idf Transformer](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-extraction/tf-idf-transformer/) +* Dimensionality Reduction + * PCA + * Kernel PCA * Datasets * [Array](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/array-dataset/) * [CSV](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/csv-dataset/) @@ -100,7 +105,8 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * [Matrix](http://php-ml.readthedocs.io/en/latest/math/matrix/) * [Set](http://php-ml.readthedocs.io/en/latest/math/set/) * [Statistic](http://php-ml.readthedocs.io/en/latest/math/statistic/) - + * Linear Algebra + ## Contribute - [Issue Tracker: github.com/php-ai/php-ml](https://github.com/php-ai/php-ml/issues) diff --git a/src/Phpml/DimensionReduction/KernelPCA.php b/src/Phpml/DimensionReduction/KernelPCA.php new file mode 100644 index 00000000..86070c72 --- /dev/null +++ b/src/Phpml/DimensionReduction/KernelPCA.php @@ -0,0 +1,246 @@ +
+ * Example: $kpca = new KernelPCA(KernelPCA::KERNEL_RBF, null, 2, 15.0); + * will initialize the algorithm with an RBF kernel having the gamma parameter as 15,0.
+ * This transformation will return the same number of rows with only 2 columns. + * + * @param int $kernel + * @param float $totalVariance Total variance to be preserved if numFeatures is not given + * @param int $numFeatures Number of columns to be returned + * @param float $gamma Gamma parameter is used with RBF and Sigmoid kernels + * + * @throws \Exception + */ + public function __construct(int $kernel = self::KERNEL_RBF, $totalVariance = null, $numFeatures = null, $gamma = null) + { + $availableKernels = [self::KERNEL_RBF, self::KERNEL_SIGMOID, self::KERNEL_LAPLACIAN, self::KERNEL_LINEAR]; + if (! in_array($kernel, $availableKernels)) { + throw new \Exception("KernelPCA can be initialized with the following kernels only: Linear, RBF, Sigmoid and Laplacian"); + } + + parent::__construct($totalVariance, $numFeatures); + + $this->kernel = $kernel; + $this->gamma = $gamma; + } + + /** + * Takes a data and returns a lower dimensional version + * of this data while preserving $totalVariance or $numFeatures.
+ * $data is an n-by-m matrix and returned array is + * n-by-k matrix where k <= m + * + * @param array $data + * + * @return array + */ + public function fit(array $data) + { + $numRows = count($data); + $this->data = $data; + + if ($this->gamma === null) { + $this->gamma = 1.0 / $numRows; + } + + $matrix = $this->calculateKernelMatrix($this->data, $numRows); + $matrix = $this->centerMatrix($matrix, $numRows); + + list($this->eigValues, $this->eigVectors) = $this->eigenDecomposition($matrix, $numRows); + + $this->fit = true; + + return Matrix::transposeArray($this->eigVectors); + } + + /** + * Calculates similarity matrix by use of selected kernel function
+ * An n-by-m matrix is given and an n-by-n matrix is returned + * + * @param array $data + * @param int $numRows + * + * @return array + */ + protected function calculateKernelMatrix(array $data, int $numRows) + { + $kernelFunc = $this->getKernel(); + + $matrix = []; + for ($i=0; $i < $numRows; $i++) { + for ($k=0; $k < $numRows; $k++) { + if ($i <= $k) { + $matrix[$i][$k] = $kernelFunc($data[$i], $data[$k]); + } else { + $matrix[$i][$k] = $matrix[$k][$i]; + } + } + } + + return $matrix; + } + + /** + * Kernel matrix is centered in its original space by using the following + * conversion: + * + * K′ = K − N.K − K.N + N.K.N where N is n-by-n matrix filled with 1/n + * + * @param array $matrix + * @param int $n + */ + protected function centerMatrix(array $matrix, int $n) + { + $N = array_fill(0, $n, array_fill(0, $n, 1.0/$n)); + $N = new Matrix($N, false); + $K = new Matrix($matrix, false); + + // K.N (This term is repeated so we cache it once) + $K_N = $K->multiply($N); + // N.K + $N_K = $N->multiply($K); + // N.K.N + $N_K_N = $N->multiply($K_N); + + return $K->subtract($N_K) + ->subtract($K_N) + ->add($N_K_N) + ->toArray(); + } + + /** + * Returns the callable kernel function + * + * @return \Closure + */ + protected function getKernel() + { + switch ($this->kernel) { + case self::KERNEL_LINEAR: + // k(x,y) = xT.y + return function ($x, $y) { + return Matrix::dot($x, $y)[0]; + }; + case self::KERNEL_RBF: + // k(x,y)=exp(-γ.|x-y|) where |..| is Euclidean distance + $dist = new Euclidean(); + return function ($x, $y) use ($dist) { + return exp(-$this->gamma * $dist->sqDistance($x, $y)); + }; + + case self::KERNEL_SIGMOID: + // k(x,y)=tanh(γ.xT.y+c0) where c0=1 + return function ($x, $y) { + $res = Matrix::dot($x, $y)[0] + 1.0; + return tanh($this->gamma * $res); + }; + + case self::KERNEL_LAPLACIAN: + // k(x,y)=exp(-γ.|x-y|) where |..| is Manhattan distance + $dist = new Manhattan(); + return function ($x, $y) use ($dist) { + return exp(-$this->gamma * $dist->distance($x, $y)); + }; + } + } + + /** + * @param array $sample + * + * @return array + */ + protected function getDistancePairs(array $sample) + { + $kernel = $this->getKernel(); + + $pairs = []; + foreach ($this->data as $row) { + $pairs[] = $kernel($row, $sample); + } + + return $pairs; + } + + /** + * @param array $pairs + * + * @return array + */ + protected function projectSample(array $pairs) + { + // Normalize eigenvectors by eig = eigVectors / eigValues + $func = function ($eigVal, $eigVect) { + $m = new Matrix($eigVect, false); + $a = $m->divideByScalar($eigVal)->toArray(); + + return $a[0]; + }; + $eig = array_map($func, $this->eigValues, $this->eigVectors); + + // return k.dot(eig) + return Matrix::dot($pairs, $eig); + } + + /** + * Transforms the given sample to a lower dimensional vector by using + * the variables obtained during the last run of fit. + * + * @param array $sample + * + * @return array + */ + public function transform(array $sample) + { + if (!$this->fit) { + throw new \Exception("KernelPCA has not been fitted with respect to original dataset, please run KernelPCA::fit() first"); + } + + if (is_array($sample[0])) { + throw new \Exception("KernelPCA::transform() accepts only one-dimensional arrays"); + } + + $pairs = $this->getDistancePairs($sample); + + return $this->projectSample($pairs); + } +} diff --git a/src/Phpml/DimensionReduction/PCA.php b/src/Phpml/DimensionReduction/PCA.php new file mode 100644 index 00000000..422dae4d --- /dev/null +++ b/src/Phpml/DimensionReduction/PCA.php @@ -0,0 +1,228 @@ + + * + * @param float $totalVariance Total explained variance to be preserved + * @param int $numFeatures Number of features to be preserved + * + * @throws \Exception + */ + public function __construct($totalVariance = null, $numFeatures = null) + { + if ($totalVariance !== null && ($totalVariance < 0.1 || $totalVariance > 0.99)) { + throw new \Exception("Total variance can be a value between 0.1 and 0.99"); + } + if ($numFeatures !== null && $numFeatures <= 0) { + throw new \Exception("Number of features to be preserved should be greater than 0"); + } + if ($totalVariance !== null && $numFeatures !== null) { + throw new \Exception("Either totalVariance or numFeatures should be specified in order to run the algorithm"); + } + + if ($numFeatures !== null) { + $this->numFeatures = $numFeatures; + } + if ($totalVariance !== null) { + $this->totalVariance = $totalVariance; + } + } + + /** + * Takes a data and returns a lower dimensional version + * of this data while preserving $totalVariance or $numFeatures.
+ * $data is an n-by-m matrix and returned array is + * n-by-k matrix where k <= m + * + * @param array $data + * + * @return array + */ + public function fit(array $data) + { + $n = count($data[0]); + + $data = $this->normalize($data, $n); + + $covMatrix = Covariance::covarianceMatrix($data, array_fill(0, $n, 0)); + + list($this->eigValues, $this->eigVectors) = $this->eigenDecomposition($covMatrix, $n); + + $this->fit = true; + + return $this->reduce($data); + } + + /** + * @param array $data + * @param int $n + */ + protected function calculateMeans(array $data, int $n) + { + // Calculate means for each dimension + $this->means = []; + for ($i=0; $i < $n; $i++) { + $column = array_column($data, $i); + $this->means[] = Mean::arithmetic($column); + } + } + + /** + * Normalization of the data includes subtracting mean from + * each dimension therefore dimensions will be centered to zero + * + * @param array $data + * @param int $n + * + * @return array + */ + protected function normalize(array $data, int $n) + { + if (empty($this->means)) { + $this->calculateMeans($data, $n); + } + + // Normalize data + foreach ($data as $i => $row) { + for ($k=0; $k < $n; $k++) { + $data[$i][$k] -= $this->means[$k]; + } + } + + return $data; + } + + /** + * Calculates eigenValues and eigenVectors of the given matrix. Returns + * top eigenVectors along with the largest eigenValues. The total explained variance + * of these eigenVectors will be no less than desired $totalVariance value + * + * @param array $matrix + * @param int $n + * + * @return array + */ + protected function eigenDecomposition(array $matrix, int $n) + { + $eig = new EigenvalueDecomposition($matrix); + $eigVals = $eig->getRealEigenvalues(); + $eigVects= $eig->getEigenvectors(); + + $totalEigVal = array_sum($eigVals); + // Sort eigenvalues in descending order + arsort($eigVals); + + $explainedVar = 0.0; + $vectors = []; + $values = []; + foreach ($eigVals as $i => $eigVal) { + $explainedVar += $eigVal / $totalEigVal; + $vectors[] = $eigVects[$i]; + $values[] = $eigVal; + + if ($this->numFeatures !== null) { + if (count($vectors) == $this->numFeatures) { + break; + } + } else { + if ($explainedVar >= $this->totalVariance) { + break; + } + } + } + + return [$values, $vectors]; + } + + /** + * Returns the reduced data + * + * @param array $data + * + * @return array + */ + protected function reduce(array $data) + { + $m1 = new Matrix($data); + $m2 = new Matrix($this->eigVectors); + + return $m1->multiply($m2->transpose())->toArray(); + } + + /** + * Transforms the given sample to a lower dimensional vector by using + * the eigenVectors obtained in the last run of fit. + * + * @param array $sample + * + * @return array + */ + public function transform(array $sample) + { + if (!$this->fit) { + throw new \Exception("PCA has not been fitted with respect to original dataset, please run PCA::fit() first"); + } + + if (! is_array($sample[0])) { + $sample = [$sample]; + } + + $sample = $this->normalize($sample, count($sample[0])); + + return $this->reduce($sample); + } +} diff --git a/src/Phpml/Exception/InvalidArgumentException.php b/src/Phpml/Exception/InvalidArgumentException.php index 42063f1d..f6b0031a 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -55,7 +55,7 @@ public static function matrixDimensionsDidNotMatch() */ public static function inconsistentMatrixSupplied() { - return new self('Inconsistent matrix aupplied'); + return new self('Inconsistent matrix applied'); } /** diff --git a/src/Phpml/Math/Distance/Euclidean.php b/src/Phpml/Math/Distance/Euclidean.php index ad60e871..1158f5d8 100644 --- a/src/Phpml/Math/Distance/Euclidean.php +++ b/src/Phpml/Math/Distance/Euclidean.php @@ -31,4 +31,17 @@ public function distance(array $a, array $b): float return sqrt((float) $distance); } + + /** + * Square of Euclidean distance + * + * @param array $a + * @param array $b + * + * @return float + */ + public function sqDistance(array $a, array $b): float + { + return $this->distance($a, $b) ** 2; + } } diff --git a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php new file mode 100644 index 00000000..27557bbd --- /dev/null +++ b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -0,0 +1,890 @@ +d = $this->V[$this->n-1]; + // Householder reduction to tridiagonal form. + for ($i = $this->n-1; $i > 0; --$i) { + $i_ = $i -1; + // Scale to avoid under/overflow. + $h = $scale = 0.0; + $scale += array_sum(array_map('abs', $this->d)); + if ($scale == 0.0) { + $this->e[$i] = $this->d[$i_]; + $this->d = array_slice($this->V[$i_], 0, $i_); + for ($j = 0; $j < $i; ++$j) { + $this->V[$j][$i] = $this->V[$i][$j] = 0.0; + } + } else { + // Generate Householder vector. + for ($k = 0; $k < $i; ++$k) { + $this->d[$k] /= $scale; + $h += pow($this->d[$k], 2); + } + $f = $this->d[$i_]; + $g = sqrt($h); + if ($f > 0) { + $g = -$g; + } + $this->e[$i] = $scale * $g; + $h = $h - $f * $g; + $this->d[$i_] = $f - $g; + for ($j = 0; $j < $i; ++$j) { + $this->e[$j] = 0.0; + } + // Apply similarity transformation to remaining columns. + for ($j = 0; $j < $i; ++$j) { + $f = $this->d[$j]; + $this->V[$j][$i] = $f; + $g = $this->e[$j] + $this->V[$j][$j] * $f; + for ($k = $j+1; $k <= $i_; ++$k) { + $g += $this->V[$k][$j] * $this->d[$k]; + $this->e[$k] += $this->V[$k][$j] * $f; + } + $this->e[$j] = $g; + } + $f = 0.0; + for ($j = 0; $j < $i; ++$j) { + if ($h === 0) { + $h = 1e-20; + } + $this->e[$j] /= $h; + $f += $this->e[$j] * $this->d[$j]; + } + $hh = $f / (2 * $h); + for ($j=0; $j < $i; ++$j) { + $this->e[$j] -= $hh * $this->d[$j]; + } + for ($j = 0; $j < $i; ++$j) { + $f = $this->d[$j]; + $g = $this->e[$j]; + for ($k = $j; $k <= $i_; ++$k) { + $this->V[$k][$j] -= ($f * $this->e[$k] + $g * $this->d[$k]); + } + $this->d[$j] = $this->V[$i-1][$j]; + $this->V[$i][$j] = 0.0; + } + } + $this->d[$i] = $h; + } + + // Accumulate transformations. + for ($i = 0; $i < $this->n-1; ++$i) { + $this->V[$this->n-1][$i] = $this->V[$i][$i]; + $this->V[$i][$i] = 1.0; + $h = $this->d[$i+1]; + if ($h != 0.0) { + for ($k = 0; $k <= $i; ++$k) { + $this->d[$k] = $this->V[$k][$i+1] / $h; + } + for ($j = 0; $j <= $i; ++$j) { + $g = 0.0; + for ($k = 0; $k <= $i; ++$k) { + $g += $this->V[$k][$i+1] * $this->V[$k][$j]; + } + for ($k = 0; $k <= $i; ++$k) { + $this->V[$k][$j] -= $g * $this->d[$k]; + } + } + } + for ($k = 0; $k <= $i; ++$k) { + $this->V[$k][$i+1] = 0.0; + } + } + + $this->d = $this->V[$this->n-1]; + $this->V[$this->n-1] = array_fill(0, $j, 0.0); + $this->V[$this->n-1][$this->n-1] = 1.0; + $this->e[0] = 0.0; + } + + + /** + * Symmetric tridiagonal QL algorithm. + * + * This is derived from the Algol procedures tql2, by + * Bowdler, Martin, Reinsch, and Wilkinson, Handbook for + * Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + * Fortran subroutine in EISPACK. + */ + private function tql2() + { + for ($i = 1; $i < $this->n; ++$i) { + $this->e[$i-1] = $this->e[$i]; + } + $this->e[$this->n-1] = 0.0; + $f = 0.0; + $tst1 = 0.0; + $eps = pow(2.0, -52.0); + + for ($l = 0; $l < $this->n; ++$l) { + // Find small subdiagonal element + $tst1 = max($tst1, abs($this->d[$l]) + abs($this->e[$l])); + $m = $l; + while ($m < $this->n) { + if (abs($this->e[$m]) <= $eps * $tst1) { + break; + } + ++$m; + } + // If m == l, $this->d[l] is an eigenvalue, + // otherwise, iterate. + if ($m > $l) { + $iter = 0; + do { + // Could check iteration count here. + $iter += 1; + // Compute implicit shift + $g = $this->d[$l]; + $p = ($this->d[$l+1] - $g) / (2.0 * $this->e[$l]); + $r = hypot($p, 1.0); + if ($p < 0) { + $r *= -1; + } + $this->d[$l] = $this->e[$l] / ($p + $r); + $this->d[$l+1] = $this->e[$l] * ($p + $r); + $dl1 = $this->d[$l+1]; + $h = $g - $this->d[$l]; + for ($i = $l + 2; $i < $this->n; ++$i) { + $this->d[$i] -= $h; + } + $f += $h; + // Implicit QL transformation. + $p = $this->d[$m]; + $c = 1.0; + $c2 = $c3 = $c; + $el1 = $this->e[$l + 1]; + $s = $s2 = 0.0; + for ($i = $m-1; $i >= $l; --$i) { + $c3 = $c2; + $c2 = $c; + $s2 = $s; + $g = $c * $this->e[$i]; + $h = $c * $p; + $r = hypot($p, $this->e[$i]); + $this->e[$i+1] = $s * $r; + $s = $this->e[$i] / $r; + $c = $p / $r; + $p = $c * $this->d[$i] - $s * $g; + $this->d[$i+1] = $h + $s * ($c * $g + $s * $this->d[$i]); + // Accumulate transformation. + for ($k = 0; $k < $this->n; ++$k) { + $h = $this->V[$k][$i+1]; + $this->V[$k][$i+1] = $s * $this->V[$k][$i] + $c * $h; + $this->V[$k][$i] = $c * $this->V[$k][$i] - $s * $h; + } + } + $p = -$s * $s2 * $c3 * $el1 * $this->e[$l] / $dl1; + $this->e[$l] = $s * $p; + $this->d[$l] = $c * $p; + // Check for convergence. + } while (abs($this->e[$l]) > $eps * $tst1); + } + $this->d[$l] = $this->d[$l] + $f; + $this->e[$l] = 0.0; + } + + // Sort eigenvalues and corresponding vectors. + for ($i = 0; $i < $this->n - 1; ++$i) { + $k = $i; + $p = $this->d[$i]; + for ($j = $i+1; $j < $this->n; ++$j) { + if ($this->d[$j] < $p) { + $k = $j; + $p = $this->d[$j]; + } + } + if ($k != $i) { + $this->d[$k] = $this->d[$i]; + $this->d[$i] = $p; + for ($j = 0; $j < $this->n; ++$j) { + $p = $this->V[$j][$i]; + $this->V[$j][$i] = $this->V[$j][$k]; + $this->V[$j][$k] = $p; + } + } + } + } + + + /** + * Nonsymmetric reduction to Hessenberg form. + * + * This is derived from the Algol procedures orthes and ortran, + * by Martin and Wilkinson, Handbook for Auto. Comp., + * Vol.ii-Linear Algebra, and the corresponding + * Fortran subroutines in EISPACK. + */ + private function orthes() + { + $low = 0; + $high = $this->n-1; + + for ($m = $low+1; $m <= $high-1; ++$m) { + // Scale column. + $scale = 0.0; + for ($i = $m; $i <= $high; ++$i) { + $scale = $scale + abs($this->H[$i][$m-1]); + } + if ($scale != 0.0) { + // Compute Householder transformation. + $h = 0.0; + for ($i = $high; $i >= $m; --$i) { + $this->ort[$i] = $this->H[$i][$m-1] / $scale; + $h += $this->ort[$i] * $this->ort[$i]; + } + $g = sqrt($h); + if ($this->ort[$m] > 0) { + $g *= -1; + } + $h -= $this->ort[$m] * $g; + $this->ort[$m] -= $g; + // Apply Householder similarity transformation + // H = (I -u * u' / h) * H * (I -u * u') / h) + for ($j = $m; $j < $this->n; ++$j) { + $f = 0.0; + for ($i = $high; $i >= $m; --$i) { + $f += $this->ort[$i] * $this->H[$i][$j]; + } + $f /= $h; + for ($i = $m; $i <= $high; ++$i) { + $this->H[$i][$j] -= $f * $this->ort[$i]; + } + } + for ($i = 0; $i <= $high; ++$i) { + $f = 0.0; + for ($j = $high; $j >= $m; --$j) { + $f += $this->ort[$j] * $this->H[$i][$j]; + } + $f = $f / $h; + for ($j = $m; $j <= $high; ++$j) { + $this->H[$i][$j] -= $f * $this->ort[$j]; + } + } + $this->ort[$m] = $scale * $this->ort[$m]; + $this->H[$m][$m-1] = $scale * $g; + } + } + + // Accumulate transformations (Algol's ortran). + for ($i = 0; $i < $this->n; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + $this->V[$i][$j] = ($i == $j ? 1.0 : 0.0); + } + } + for ($m = $high-1; $m >= $low+1; --$m) { + if ($this->H[$m][$m-1] != 0.0) { + for ($i = $m+1; $i <= $high; ++$i) { + $this->ort[$i] = $this->H[$i][$m-1]; + } + for ($j = $m; $j <= $high; ++$j) { + $g = 0.0; + for ($i = $m; $i <= $high; ++$i) { + $g += $this->ort[$i] * $this->V[$i][$j]; + } + // Double division avoids possible underflow + $g = ($g / $this->ort[$m]) / $this->H[$m][$m-1]; + for ($i = $m; $i <= $high; ++$i) { + $this->V[$i][$j] += $g * $this->ort[$i]; + } + } + } + } + } + + + /** + * Performs complex division. + */ + private function cdiv($xr, $xi, $yr, $yi) + { + if (abs($yr) > abs($yi)) { + $r = $yi / $yr; + $d = $yr + $r * $yi; + $this->cdivr = ($xr + $r * $xi) / $d; + $this->cdivi = ($xi - $r * $xr) / $d; + } else { + $r = $yr / $yi; + $d = $yi + $r * $yr; + $this->cdivr = ($r * $xr + $xi) / $d; + $this->cdivi = ($r * $xi - $xr) / $d; + } + } + + + /** + * Nonsymmetric reduction from Hessenberg to real Schur form. + * + * Code is derived from the Algol procedure hqr2, + * by Martin and Wilkinson, Handbook for Auto. Comp., + * Vol.ii-Linear Algebra, and the corresponding + * Fortran subroutine in EISPACK. + */ + private function hqr2() + { + // Initialize + $nn = $this->n; + $n = $nn - 1; + $low = 0; + $high = $nn - 1; + $eps = pow(2.0, -52.0); + $exshift = 0.0; + $p = $q = $r = $s = $z = 0; + // Store roots isolated by balanc and compute matrix norm + $norm = 0.0; + + for ($i = 0; $i < $nn; ++$i) { + if (($i < $low) or ($i > $high)) { + $this->d[$i] = $this->H[$i][$i]; + $this->e[$i] = 0.0; + } + for ($j = max($i-1, 0); $j < $nn; ++$j) { + $norm = $norm + abs($this->H[$i][$j]); + } + } + + // Outer loop over eigenvalue index + $iter = 0; + while ($n >= $low) { + // Look for single small sub-diagonal element + $l = $n; + while ($l > $low) { + $s = abs($this->H[$l-1][$l-1]) + abs($this->H[$l][$l]); + if ($s == 0.0) { + $s = $norm; + } + if (abs($this->H[$l][$l-1]) < $eps * $s) { + break; + } + --$l; + } + // Check for convergence + // One root found + if ($l == $n) { + $this->H[$n][$n] = $this->H[$n][$n] + $exshift; + $this->d[$n] = $this->H[$n][$n]; + $this->e[$n] = 0.0; + --$n; + $iter = 0; + // Two roots found + } elseif ($l == $n-1) { + $w = $this->H[$n][$n-1] * $this->H[$n-1][$n]; + $p = ($this->H[$n-1][$n-1] - $this->H[$n][$n]) / 2.0; + $q = $p * $p + $w; + $z = sqrt(abs($q)); + $this->H[$n][$n] = $this->H[$n][$n] + $exshift; + $this->H[$n-1][$n-1] = $this->H[$n-1][$n-1] + $exshift; + $x = $this->H[$n][$n]; + // Real pair + if ($q >= 0) { + if ($p >= 0) { + $z = $p + $z; + } else { + $z = $p - $z; + } + $this->d[$n-1] = $x + $z; + $this->d[$n] = $this->d[$n-1]; + if ($z != 0.0) { + $this->d[$n] = $x - $w / $z; + } + $this->e[$n-1] = 0.0; + $this->e[$n] = 0.0; + $x = $this->H[$n][$n-1]; + $s = abs($x) + abs($z); + $p = $x / $s; + $q = $z / $s; + $r = sqrt($p * $p + $q * $q); + $p = $p / $r; + $q = $q / $r; + // Row modification + for ($j = $n-1; $j < $nn; ++$j) { + $z = $this->H[$n-1][$j]; + $this->H[$n-1][$j] = $q * $z + $p * $this->H[$n][$j]; + $this->H[$n][$j] = $q * $this->H[$n][$j] - $p * $z; + } + // Column modification + for ($i = 0; $i <= $n; ++$i) { + $z = $this->H[$i][$n-1]; + $this->H[$i][$n-1] = $q * $z + $p * $this->H[$i][$n]; + $this->H[$i][$n] = $q * $this->H[$i][$n] - $p * $z; + } + // Accumulate transformations + for ($i = $low; $i <= $high; ++$i) { + $z = $this->V[$i][$n-1]; + $this->V[$i][$n-1] = $q * $z + $p * $this->V[$i][$n]; + $this->V[$i][$n] = $q * $this->V[$i][$n] - $p * $z; + } + // Complex pair + } else { + $this->d[$n-1] = $x + $p; + $this->d[$n] = $x + $p; + $this->e[$n-1] = $z; + $this->e[$n] = -$z; + } + $n = $n - 2; + $iter = 0; + // No convergence yet + } else { + // Form shift + $x = $this->H[$n][$n]; + $y = 0.0; + $w = 0.0; + if ($l < $n) { + $y = $this->H[$n-1][$n-1]; + $w = $this->H[$n][$n-1] * $this->H[$n-1][$n]; + } + // Wilkinson's original ad hoc shift + if ($iter == 10) { + $exshift += $x; + for ($i = $low; $i <= $n; ++$i) { + $this->H[$i][$i] -= $x; + } + $s = abs($this->H[$n][$n-1]) + abs($this->H[$n-1][$n-2]); + $x = $y = 0.75 * $s; + $w = -0.4375 * $s * $s; + } + // MATLAB's new ad hoc shift + if ($iter == 30) { + $s = ($y - $x) / 2.0; + $s = $s * $s + $w; + if ($s > 0) { + $s = sqrt($s); + if ($y < $x) { + $s = -$s; + } + $s = $x - $w / (($y - $x) / 2.0 + $s); + for ($i = $low; $i <= $n; ++$i) { + $this->H[$i][$i] -= $s; + } + $exshift += $s; + $x = $y = $w = 0.964; + } + } + // Could check iteration count here. + $iter = $iter + 1; + // Look for two consecutive small sub-diagonal elements + $m = $n - 2; + while ($m >= $l) { + $z = $this->H[$m][$m]; + $r = $x - $z; + $s = $y - $z; + $p = ($r * $s - $w) / $this->H[$m+1][$m] + $this->H[$m][$m+1]; + $q = $this->H[$m+1][$m+1] - $z - $r - $s; + $r = $this->H[$m+2][$m+1]; + $s = abs($p) + abs($q) + abs($r); + $p = $p / $s; + $q = $q / $s; + $r = $r / $s; + if ($m == $l) { + break; + } + if (abs($this->H[$m][$m-1]) * (abs($q) + abs($r)) < + $eps * (abs($p) * (abs($this->H[$m-1][$m-1]) + abs($z) + abs($this->H[$m+1][$m+1])))) { + break; + } + --$m; + } + for ($i = $m + 2; $i <= $n; ++$i) { + $this->H[$i][$i-2] = 0.0; + if ($i > $m+2) { + $this->H[$i][$i-3] = 0.0; + } + } + // Double QR step involving rows l:n and columns m:n + for ($k = $m; $k <= $n-1; ++$k) { + $notlast = ($k != $n-1); + if ($k != $m) { + $p = $this->H[$k][$k-1]; + $q = $this->H[$k+1][$k-1]; + $r = ($notlast ? $this->H[$k+2][$k-1] : 0.0); + $x = abs($p) + abs($q) + abs($r); + if ($x != 0.0) { + $p = $p / $x; + $q = $q / $x; + $r = $r / $x; + } + } + if ($x == 0.0) { + break; + } + $s = sqrt($p * $p + $q * $q + $r * $r); + if ($p < 0) { + $s = -$s; + } + if ($s != 0) { + if ($k != $m) { + $this->H[$k][$k-1] = -$s * $x; + } elseif ($l != $m) { + $this->H[$k][$k-1] = -$this->H[$k][$k-1]; + } + $p = $p + $s; + $x = $p / $s; + $y = $q / $s; + $z = $r / $s; + $q = $q / $p; + $r = $r / $p; + // Row modification + for ($j = $k; $j < $nn; ++$j) { + $p = $this->H[$k][$j] + $q * $this->H[$k+1][$j]; + if ($notlast) { + $p = $p + $r * $this->H[$k+2][$j]; + $this->H[$k+2][$j] = $this->H[$k+2][$j] - $p * $z; + } + $this->H[$k][$j] = $this->H[$k][$j] - $p * $x; + $this->H[$k+1][$j] = $this->H[$k+1][$j] - $p * $y; + } + // Column modification + for ($i = 0; $i <= min($n, $k+3); ++$i) { + $p = $x * $this->H[$i][$k] + $y * $this->H[$i][$k+1]; + if ($notlast) { + $p = $p + $z * $this->H[$i][$k+2]; + $this->H[$i][$k+2] = $this->H[$i][$k+2] - $p * $r; + } + $this->H[$i][$k] = $this->H[$i][$k] - $p; + $this->H[$i][$k+1] = $this->H[$i][$k+1] - $p * $q; + } + // Accumulate transformations + for ($i = $low; $i <= $high; ++$i) { + $p = $x * $this->V[$i][$k] + $y * $this->V[$i][$k+1]; + if ($notlast) { + $p = $p + $z * $this->V[$i][$k+2]; + $this->V[$i][$k+2] = $this->V[$i][$k+2] - $p * $r; + } + $this->V[$i][$k] = $this->V[$i][$k] - $p; + $this->V[$i][$k+1] = $this->V[$i][$k+1] - $p * $q; + } + } // ($s != 0) + } // k loop + } // check convergence + } // while ($n >= $low) + + // Backsubstitute to find vectors of upper triangular form + if ($norm == 0.0) { + return; + } + + for ($n = $nn-1; $n >= 0; --$n) { + $p = $this->d[$n]; + $q = $this->e[$n]; + // Real vector + if ($q == 0) { + $l = $n; + $this->H[$n][$n] = 1.0; + for ($i = $n-1; $i >= 0; --$i) { + $w = $this->H[$i][$i] - $p; + $r = 0.0; + for ($j = $l; $j <= $n; ++$j) { + $r = $r + $this->H[$i][$j] * $this->H[$j][$n]; + } + if ($this->e[$i] < 0.0) { + $z = $w; + $s = $r; + } else { + $l = $i; + if ($this->e[$i] == 0.0) { + if ($w != 0.0) { + $this->H[$i][$n] = -$r / $w; + } else { + $this->H[$i][$n] = -$r / ($eps * $norm); + } + // Solve real equations + } else { + $x = $this->H[$i][$i+1]; + $y = $this->H[$i+1][$i]; + $q = ($this->d[$i] - $p) * ($this->d[$i] - $p) + $this->e[$i] * $this->e[$i]; + $t = ($x * $s - $z * $r) / $q; + $this->H[$i][$n] = $t; + if (abs($x) > abs($z)) { + $this->H[$i+1][$n] = (-$r - $w * $t) / $x; + } else { + $this->H[$i+1][$n] = (-$s - $y * $t) / $z; + } + } + // Overflow control + $t = abs($this->H[$i][$n]); + if (($eps * $t) * $t > 1) { + for ($j = $i; $j <= $n; ++$j) { + $this->H[$j][$n] = $this->H[$j][$n] / $t; + } + } + } + } + // Complex vector + } elseif ($q < 0) { + $l = $n-1; + // Last vector component imaginary so matrix is triangular + if (abs($this->H[$n][$n-1]) > abs($this->H[$n-1][$n])) { + $this->H[$n-1][$n-1] = $q / $this->H[$n][$n-1]; + $this->H[$n-1][$n] = -($this->H[$n][$n] - $p) / $this->H[$n][$n-1]; + } else { + $this->cdiv(0.0, -$this->H[$n-1][$n], $this->H[$n-1][$n-1] - $p, $q); + $this->H[$n-1][$n-1] = $this->cdivr; + $this->H[$n-1][$n] = $this->cdivi; + } + $this->H[$n][$n-1] = 0.0; + $this->H[$n][$n] = 1.0; + for ($i = $n-2; $i >= 0; --$i) { + // double ra,sa,vr,vi; + $ra = 0.0; + $sa = 0.0; + for ($j = $l; $j <= $n; ++$j) { + $ra = $ra + $this->H[$i][$j] * $this->H[$j][$n-1]; + $sa = $sa + $this->H[$i][$j] * $this->H[$j][$n]; + } + $w = $this->H[$i][$i] - $p; + if ($this->e[$i] < 0.0) { + $z = $w; + $r = $ra; + $s = $sa; + } else { + $l = $i; + if ($this->e[$i] == 0) { + $this->cdiv(-$ra, -$sa, $w, $q); + $this->H[$i][$n-1] = $this->cdivr; + $this->H[$i][$n] = $this->cdivi; + } else { + // Solve complex equations + $x = $this->H[$i][$i+1]; + $y = $this->H[$i+1][$i]; + $vr = ($this->d[$i] - $p) * ($this->d[$i] - $p) + $this->e[$i] * $this->e[$i] - $q * $q; + $vi = ($this->d[$i] - $p) * 2.0 * $q; + if ($vr == 0.0 & $vi == 0.0) { + $vr = $eps * $norm * (abs($w) + abs($q) + abs($x) + abs($y) + abs($z)); + } + $this->cdiv($x * $r - $z * $ra + $q * $sa, $x * $s - $z * $sa - $q * $ra, $vr, $vi); + $this->H[$i][$n-1] = $this->cdivr; + $this->H[$i][$n] = $this->cdivi; + if (abs($x) > (abs($z) + abs($q))) { + $this->H[$i+1][$n-1] = (-$ra - $w * $this->H[$i][$n-1] + $q * $this->H[$i][$n]) / $x; + $this->H[$i+1][$n] = (-$sa - $w * $this->H[$i][$n] - $q * $this->H[$i][$n-1]) / $x; + } else { + $this->cdiv(-$r - $y * $this->H[$i][$n-1], -$s - $y * $this->H[$i][$n], $z, $q); + $this->H[$i+1][$n-1] = $this->cdivr; + $this->H[$i+1][$n] = $this->cdivi; + } + } + // Overflow control + $t = max(abs($this->H[$i][$n-1]), abs($this->H[$i][$n])); + if (($eps * $t) * $t > 1) { + for ($j = $i; $j <= $n; ++$j) { + $this->H[$j][$n-1] = $this->H[$j][$n-1] / $t; + $this->H[$j][$n] = $this->H[$j][$n] / $t; + } + } + } // end else + } // end for + } // end else for complex case + } // end for + + // Vectors of isolated roots + for ($i = 0; $i < $nn; ++$i) { + if ($i < $low | $i > $high) { + for ($j = $i; $j < $nn; ++$j) { + $this->V[$i][$j] = $this->H[$i][$j]; + } + } + } + + // Back transformation to get eigenvectors of original matrix + for ($j = $nn-1; $j >= $low; --$j) { + for ($i = $low; $i <= $high; ++$i) { + $z = 0.0; + for ($k = $low; $k <= min($j, $high); ++$k) { + $z = $z + $this->V[$i][$k] * $this->H[$k][$j]; + } + $this->V[$i][$j] = $z; + } + } + } // end hqr2 + + + /** + * Constructor: Check for symmetry, then construct the eigenvalue decomposition + * + * @param array $Arg + */ + public function __construct(array $Arg) + { + $this->A = $Arg; + $this->n = count($Arg[0]); + + $issymmetric = true; + for ($j = 0; ($j < $this->n) & $issymmetric; ++$j) { + for ($i = 0; ($i < $this->n) & $issymmetric; ++$i) { + $issymmetric = ($this->A[$i][$j] == $this->A[$j][$i]); + } + } + + if ($issymmetric) { + $this->V = $this->A; + // Tridiagonalize. + $this->tred2(); + // Diagonalize. + $this->tql2(); + } else { + $this->H = $this->A; + $this->ort = []; + // Reduce to Hessenberg form. + $this->orthes(); + // Reduce Hessenberg to real Schur form. + $this->hqr2(); + } + } + + /** + * Return the eigenvector matrix + * + * @access public + * @return array + */ + public function getEigenvectors() + { + $vectors = $this->V; + + // Always return the eigenvectors of length 1.0 + $vectors = new Matrix($vectors); + $vectors = array_map(function ($vect) { + $sum = 0; + for ($i=0; $itranspose()->toArray()); + + return $vectors; + } + + + /** + * Return the real parts of the eigenvalues
+ * d = real(diag(D)); + * + * @return array + */ + public function getRealEigenvalues() + { + return $this->d; + } + + + /** + * Return the imaginary parts of the eigenvalues
+ * d = imag(diag(D)) + * + * @return array + */ + public function getImagEigenvalues() + { + return $this->e; + } + + + /** + * Return the block diagonal eigenvalue matrix + * + * @return array + */ + public function getDiagonalEigenvalues() + { + for ($i = 0; $i < $this->n; ++$i) { + $D[$i] = array_fill(0, $this->n, 0.0); + $D[$i][$i] = $this->d[$i]; + if ($this->e[$i] == 0) { + continue; + } + $o = ($this->e[$i] > 0) ? $i + 1 : $i - 1; + $D[$i][$o] = $this->e[$i]; + } + return $D; + } +} // class EigenvalueDecomposition diff --git a/src/Phpml/Math/Matrix.php b/src/Phpml/Math/Matrix.php index 39f5c5ab..25101f3f 100644 --- a/src/Phpml/Math/Matrix.php +++ b/src/Phpml/Math/Matrix.php @@ -37,8 +37,15 @@ class Matrix */ public function __construct(array $matrix, bool $validate = true) { - $this->rows = count($matrix); - $this->columns = count($matrix[0]); + // When a row vector is given + if (!is_array($matrix[0])) { + $this->rows = 1; + $this->columns = count($matrix); + $matrix = [$matrix]; + } else { + $this->rows = count($matrix); + $this->columns = count($matrix[0]); + } if ($validate) { for ($i = 0; $i < $this->rows; ++$i) { @@ -74,6 +81,14 @@ public function toArray() return $this->matrix; } + /** + * @return float + */ + public function toScalar() + { + return $this->matrix[0][0]; + } + /** * @return int */ @@ -103,14 +118,10 @@ public function getColumnValues($column) throw MatrixException::columnOutOfRange(); } - $values = []; - for ($i = 0; $i < $this->rows; ++$i) { - $values[] = $this->matrix[$i][$column]; - } - - return $values; + return array_column($this->matrix, $column); } + /** * @return float|int * @@ -167,14 +178,15 @@ public function isSquare() */ public function transpose() { - $newMatrix = []; - for ($i = 0; $i < $this->rows; ++$i) { - for ($j = 0; $j < $this->columns; ++$j) { - $newMatrix[$j][$i] = $this->matrix[$i][$j]; - } + if ($this->rows == 1) { + $matrix = array_map(function ($el) { + return [$el]; + }, $this->matrix[0]); + } else { + $matrix = array_map(null, ...$this->matrix); } - return new self($newMatrix, false); + return new self($matrix, false); } /** @@ -222,6 +234,64 @@ public function divideByScalar($value) return new self($newMatrix, false); } + /** + * @param $value + * + * @return Matrix + */ + public function multiplyByScalar($value) + { + $newMatrix = []; + for ($i = 0; $i < $this->rows; ++$i) { + for ($j = 0; $j < $this->columns; ++$j) { + $newMatrix[$i][$j] = $this->matrix[$i][$j] * $value; + } + } + + return new self($newMatrix, false); + } + + /** + * Element-wise addition of the matrix with another one + * + * @param Matrix $other + */ + public function add(Matrix $other) + { + return $this->_add($other); + } + + /** + * Element-wise subtracting of another matrix from this one + * + * @param Matrix $other + */ + public function subtract(Matrix $other) + { + return $this->_add($other, -1); + } + + /** + * Element-wise addition or substraction depending on the given sign parameter + * + * @param Matrix $other + * @param type $sign + */ + protected function _add(Matrix $other, $sign = 1) + { + $a1 = $this->toArray(); + $a2 = $other->toArray(); + + $newMatrix = []; + for ($i=0; $i < $this->rows; $i++) { + for ($k=0; $k < $this->columns; $k++) { + $newMatrix[$i][$k] = $a1[$i][$k] + $sign * $a2[$i][$k]; + } + } + + return new Matrix($newMatrix, false); + } + /** * @return Matrix * @@ -283,4 +353,33 @@ public function isSingular() : bool { return 0 == $this->getDeterminant(); } + + /** + * Returns the transpose of given array + * + * @param array $array + * + * @return array + */ + public static function transposeArray(array $array) + { + return (new Matrix($array, false))->transpose()->toArray(); + } + + /** + * Returns the dot product of two arrays
+ * Matrix::dot(x, y) ==> x.y' + * + * @param array $array1 + * @param array $array2 + * + * @return array + */ + public static function dot(array $array1, array $array2) + { + $m1 = new Matrix($array1, false); + $m2 = new Matrix($array2, false); + + return $m1->multiply($m2->transpose())->toArray()[0]; + } } diff --git a/src/Phpml/Math/Statistic/Covariance.php b/src/Phpml/Math/Statistic/Covariance.php new file mode 100644 index 00000000..4a9b613d --- /dev/null +++ b/src/Phpml/Math/Statistic/Covariance.php @@ -0,0 +1,155 @@ + $xi) { + $yi = $y[$index]; + $sum += ($xi - $meanX) * ($yi - $meanY); + } + + if ($sample) { + --$n; + } + + return $sum / $n; + } + + /** + * Calculates covariance of two dimensions, i and k in the given data. + * + * @param array $data + * @param int $i + * @param int $k + * @param type $sample + * @param int $n + * @param float $meanX + * @param float $meanY + */ + public static function fromDataset(array $data, int $i, int $k, $sample = true, float $meanX = null, float $meanY = null) + { + if (empty($data)) { + throw InvalidArgumentException::arrayCantBeEmpty(); + } + + $n = count($data); + if ($sample && $n === 1) { + throw InvalidArgumentException::arraySizeToSmall(2); + } + + if ($i < 0 || $k < 0 || $i >= $n || $k >= $n) { + throw new \Exception("Given indices i and k do not match with the dimensionality of data"); + } + + if ($meanX === null || $meanY === null) { + $x = array_column($data, $i); + $y = array_column($data, $k); + + $meanX = Mean::arithmetic($x); + $meanY = Mean::arithmetic($y); + $sum = 0.0; + foreach ($x as $index => $xi) { + $yi = $y[$index]; + $sum += ($xi - $meanX) * ($yi - $meanY); + } + } else { + // In the case, whole dataset given along with dimension indices, i and k, + // we would like to avoid getting column data with array_column and operate + // over this extra copy of column data for memory efficiency purposes. + // + // Instead we traverse through the whole data and get what we actually need + // without copying the data. This way, memory use will be reduced + // with a slight cost of CPU utilization. + $sum = 0.0; + foreach ($data as $row) { + $val = []; + foreach ($row as $index => $col) { + if ($index == $i) { + $val[0] = $col - $meanX; + } + if ($index == $k) { + $val[1] = $col - $meanY; + } + } + $sum += $val[0] * $val[1]; + } + } + + if ($sample) { + --$n; + } + + return $sum / $n; + } + + /** + * Returns the covariance matrix of n-dimensional data + * + * @param array $data + * + * @return array + */ + public static function covarianceMatrix(array $data, array $means = null) + { + $n = count($data[0]); + + if ($means === null) { + $means = []; + for ($i=0; $i < $n; $i++) { + $means[] = Mean::arithmetic(array_column($data, $i)); + } + } + + $cov = []; + for ($i=0; $i < $n; $i++) { + for ($k=0; $k < $n; $k++) { + if ($i > $k) { + $cov[$i][$k] = $cov[$k][$i]; + } else { + $cov[$i][$k] = Covariance::fromDataset( + $data, $i, $k, true, $means[$i], $means[$k]); + } + } + } + + return $cov; + } +} diff --git a/tests/Phpml/DimensionReduction/KernelPCATest.php b/tests/Phpml/DimensionReduction/KernelPCATest.php new file mode 100644 index 00000000..14b2d7d2 --- /dev/null +++ b/tests/Phpml/DimensionReduction/KernelPCATest.php @@ -0,0 +1,51 @@ +fit($data); + + // Due to the fact that the sign of values can be flipped + // during the calculation of eigenValues, we have to compare + // absolute value of the values + array_map(function ($val1, $val2) use ($epsilon) { + $this->assertEquals(abs($val1), abs($val2), '', $epsilon); + }, $transformed, $reducedData); + + // Fitted KernelPCA object can also transform an arbitrary sample of the + // same dimensionality with the original dataset + $newData = [1.25, 2.25]; + $newTransformed = [0.18956227539216]; + $newTransformed2 = $kpca->transform($newData); + $this->assertEquals(abs($newTransformed[0]), abs($newTransformed2[0]), '', $epsilon); + } +} diff --git a/tests/Phpml/DimensionReduction/PCATest.php b/tests/Phpml/DimensionReduction/PCATest.php new file mode 100644 index 00000000..8f65e98d --- /dev/null +++ b/tests/Phpml/DimensionReduction/PCATest.php @@ -0,0 +1,57 @@ +fit($data); + + // Due to the fact that the sign of values can be flipped + // during the calculation of eigenValues, we have to compare + // absolute value of the values + array_map(function ($val1, $val2) use ($epsilon) { + $this->assertEquals(abs($val1), abs($val2), '', $epsilon); + }, $transformed, $reducedData); + + // Test fitted PCA object to transform an arbitrary sample of the + // same dimensionality with the original dataset + foreach ($data as $i => $row) { + $newRow = [[$transformed[$i]]]; + $newRow2= $pca->transform($row); + + array_map(function ($val1, $val2) use ($epsilon) { + $this->assertEquals(abs($val1), abs($val2), '', $epsilon); + }, $newRow, $newRow2); + } + } +} diff --git a/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php b/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php new file mode 100644 index 00000000..4bca1bda --- /dev/null +++ b/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php @@ -0,0 +1,65 @@ +getEigenvectors(); + $eigValues = $decomp->getRealEigenvalues(); + $this->assertEquals($knownEigvalues, $eigValues, '', $epsilon); + $this->assertEquals($knownEigvectors, $eigVectors, '', $epsilon); + + // Secondly, generate a symmetric square matrix + // and test for A.v=λ.v + // + // (We, for now, omit non-symmetric matrices whose eigenvalues can be complex numbers) + $len = 3; + $A = array_fill(0, $len, array_fill(0, $len, 0.0)); + srand(intval(microtime(true) * 1000)); + for ($i=0; $i < $len; $i++) { + for ($k=0; $k < $len; $k++) { + if ($i > $k) { + $A[$i][$k] = $A[$k][$i]; + } else { + $A[$i][$k] = rand(0, 10); + } + } + } + + $decomp = new EigenvalueDecomposition($A); + $eigValues = $decomp->getRealEigenvalues(); + $eigVectors= $decomp->getEigenvectors(); + + foreach ($eigValues as $index => $lambda) { + $m1 = new Matrix($A); + $m2 = (new Matrix($eigVectors[$index]))->transpose(); + + // A.v=λ.v + $leftSide = $m1->multiply($m2)->toArray(); + $rightSide= $m2->multiplyByScalar($lambda)->toArray(); + + $this->assertEquals($leftSide, $rightSide, '', $epsilon); + } + } +} diff --git a/tests/Phpml/Math/MatrixTest.php b/tests/Phpml/Math/MatrixTest.php index 0b466122..257fd72d 100644 --- a/tests/Phpml/Math/MatrixTest.php +++ b/tests/Phpml/Math/MatrixTest.php @@ -188,4 +188,80 @@ public function testCrossOutMatrix() $this->assertEquals($crossOuted, $matrix->crossOut(1, 1)->toArray()); } + + public function testToScalar() + { + $matrix = new Matrix([[1, 2, 3], [3, 2, 3]]); + + $this->assertEquals($matrix->toScalar(), 1); + } + + public function testMultiplyByScalar() + { + $matrix = new Matrix([ + [4, 6, 8], + [2, 10, 20], + ]); + + $result = [ + [-8, -12, -16], + [-4, -20, -40], + ]; + + $this->assertEquals($result, $matrix->multiplyByScalar(-2)->toArray()); + } + + public function testAdd() + { + $array1 = [1, 1, 1]; + $array2 = [2, 2, 2]; + $result = [3, 3, 3]; + + $m1 = new Matrix($array1); + $m2 = new Matrix($array2); + + $this->assertEquals($result, $m1->add($m2)->toArray()[0]); + } + + public function testSubtract() + { + $array1 = [1, 1, 1]; + $array2 = [2, 2, 2]; + $result = [-1, -1, -1]; + + $m1 = new Matrix($array1); + $m2 = new Matrix($array2); + + $this->assertEquals($result, $m1->subtract($m2)->toArray()[0]); + } + + public function testTransposeArray() + { + $array = [ + [1, 1, 1], + [2, 2, 2] + ]; + $transposed = [ + [1, 2], + [1, 2], + [1, 2] + ]; + + $this->assertEquals($transposed, Matrix::transposeArray($array)); + } + + public function testDot() + { + $vect1 = [2, 2, 2]; + $vect2 = [3, 3, 3]; + $dot = [18]; + + $this->assertEquals($dot, Matrix::dot($vect1, $vect2)); + + $matrix1 = [[1, 1], [2, 2]]; + $matrix2 = [[3, 3], [3, 3], [3, 3]]; + $dot = [6, 12]; + + $this->assertEquals($dot, Matrix::dot($matrix2, $matrix1)); + } } diff --git a/tests/Phpml/Math/Statistic/CovarianceTest.php b/tests/Phpml/Math/Statistic/CovarianceTest.php new file mode 100644 index 00000000..4b025a3a --- /dev/null +++ b/tests/Phpml/Math/Statistic/CovarianceTest.php @@ -0,0 +1,62 @@ +assertEquals($cov1, $knownCovariance[0][0], '', $epsilon); + $cov1 = Covariance::fromXYArrays($x, $x); + $this->assertEquals($cov1, $knownCovariance[0][0], '', $epsilon); + + $cov2 = Covariance::fromDataset($matrix, 0, 1); + $this->assertEquals($cov2, $knownCovariance[0][1], '', $epsilon); + $cov2 = Covariance::fromXYArrays($x, $y); + $this->assertEquals($cov2, $knownCovariance[0][1], '', $epsilon); + + // Second: calculation cov matrix with automatic means for each column + $covariance = Covariance::covarianceMatrix($matrix); + $this->assertEquals($knownCovariance, $covariance, '', $epsilon); + + // Thirdly, CovMatrix: Means are precalculated and given to the method + $x = array_column($matrix, 0); + $y = array_column($matrix, 1); + $meanX = Mean::arithmetic($x); + $meanY = Mean::arithmetic($y); + + $covariance = Covariance::covarianceMatrix($matrix, [$meanX, $meanY]); + $this->assertEquals($knownCovariance, $covariance, '', $epsilon); + } +} From 12b8b118dd8f9d5805a27823c0e1d2630395ba97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Monlla=C3=B3?= Date: Mon, 24 Apr 2017 17:47:30 +0800 Subject: [PATCH 172/328] Fix division by 0 error during normalization (#83) * Fix division by 0 error during normalization std is 0 when a feature has the same value in samples. * Expand std normalization test --- src/Phpml/Preprocessing/Normalizer.php | 7 ++++++- tests/Phpml/Preprocessing/NormalizerTest.php | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Phpml/Preprocessing/Normalizer.php b/src/Phpml/Preprocessing/Normalizer.php index 42a8f1c2..8392db7b 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -134,7 +134,12 @@ private function normalizeL2(array &$sample) private function normalizeSTD(array &$sample) { foreach ($sample as $i => $val) { - $sample[$i] = ($sample[$i] - $this->mean[$i]) / $this->std[$i]; + if ($this->std[$i] != 0) { + $sample[$i] = ($sample[$i] - $this->mean[$i]) / $this->std[$i]; + } else { + // Same value for all samples. + $sample[$i] = 0; + } } } } diff --git a/tests/Phpml/Preprocessing/NormalizerTest.php b/tests/Phpml/Preprocessing/NormalizerTest.php index 07d121cc..2492faef 100644 --- a/tests/Phpml/Preprocessing/NormalizerTest.php +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -111,6 +111,9 @@ public function testStandardNorm() for ($k=0; $k<3; $k++) { $sample[$k] = rand(1, 100); } + // Last feature's value shared across samples. + $sample[] = 1; + $samples[] = $sample; } @@ -126,6 +129,7 @@ function ($element) { return $element < -3 || $element > 3; }); $this->assertCount(0, $errors); + $this->assertEquals(0, $sample[3]); } } } From 5b373fa7c287742ee0194408147f04b3167b7d71 Mon Sep 17 00:00:00 2001 From: Mustafa Karabulut Date: Tue, 25 Apr 2017 09:58:02 +0300 Subject: [PATCH 173/328] Linear Discrimant Analysis (LDA) (#82) * Linear Discrimant Analysis (LDA) * LDA test file * Matrix inverse via LUDecomposition * LUDecomposition inverse() and det() applied * Readme update for LDA --- README.md | 3 +- .../EigenTransformerBase.php | 98 ++++++ src/Phpml/DimensionReduction/KernelPCA.php | 2 +- src/Phpml/DimensionReduction/LDA.php | 247 +++++++++++++++ src/Phpml/DimensionReduction/PCA.php | 90 +----- .../LinearAlgebra/EigenvalueDecomposition.php | 6 +- .../Math/LinearAlgebra/LUDecomposition.php | 297 ++++++++++++++++++ src/Phpml/Math/Matrix.php | 58 ++-- tests/Phpml/DimensionReduction/LDATest.php | 65 ++++ 9 files changed, 735 insertions(+), 131 deletions(-) create mode 100644 src/Phpml/DimensionReduction/EigenTransformerBase.php create mode 100644 src/Phpml/DimensionReduction/LDA.php create mode 100644 src/Phpml/Math/LinearAlgebra/LUDecomposition.php create mode 100644 tests/Phpml/DimensionReduction/LDATest.php diff --git a/README.md b/README.md index bab758cd..b17ac726 100644 --- a/README.md +++ b/README.md @@ -88,8 +88,9 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * [Token Count Vectorizer](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-extraction/token-count-vectorizer/) * [Tf-idf Transformer](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-extraction/tf-idf-transformer/) * Dimensionality Reduction - * PCA + * PCA (Principal Component Analysis) * Kernel PCA + * LDA (Linear Discriminant Analysis) * Datasets * [Array](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/array-dataset/) * [CSV](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/csv-dataset/) diff --git a/src/Phpml/DimensionReduction/EigenTransformerBase.php b/src/Phpml/DimensionReduction/EigenTransformerBase.php new file mode 100644 index 00000000..b3990024 --- /dev/null +++ b/src/Phpml/DimensionReduction/EigenTransformerBase.php @@ -0,0 +1,98 @@ +getRealEigenvalues(); + $eigVects= $eig->getEigenvectors(); + + $totalEigVal = array_sum($eigVals); + // Sort eigenvalues in descending order + arsort($eigVals); + + $explainedVar = 0.0; + $vectors = []; + $values = []; + foreach ($eigVals as $i => $eigVal) { + $explainedVar += $eigVal / $totalEigVal; + $vectors[] = $eigVects[$i]; + $values[] = $eigVal; + + if ($this->numFeatures !== null) { + if (count($vectors) == $this->numFeatures) { + break; + } + } else { + if ($explainedVar >= $this->totalVariance) { + break; + } + } + } + + $this->eigValues = $values; + $this->eigVectors = $vectors; + } + + /** + * Returns the reduced data + * + * @param array $data + * + * @return array + */ + protected function reduce(array $data) + { + $m1 = new Matrix($data); + $m2 = new Matrix($this->eigVectors); + + return $m1->multiply($m2->transpose())->toArray(); + } +} diff --git a/src/Phpml/DimensionReduction/KernelPCA.php b/src/Phpml/DimensionReduction/KernelPCA.php index 86070c72..51fb17ad 100644 --- a/src/Phpml/DimensionReduction/KernelPCA.php +++ b/src/Phpml/DimensionReduction/KernelPCA.php @@ -86,7 +86,7 @@ public function fit(array $data) $matrix = $this->calculateKernelMatrix($this->data, $numRows); $matrix = $this->centerMatrix($matrix, $numRows); - list($this->eigValues, $this->eigVectors) = $this->eigenDecomposition($matrix, $numRows); + $this->eigenDecomposition($matrix, $numRows); $this->fit = true; diff --git a/src/Phpml/DimensionReduction/LDA.php b/src/Phpml/DimensionReduction/LDA.php new file mode 100644 index 00000000..28f34d65 --- /dev/null +++ b/src/Phpml/DimensionReduction/LDA.php @@ -0,0 +1,247 @@ +
+ * The algorithm can be initialized by speciyfing + * either with the totalVariance(a value between 0.1 and 0.99) + * or numFeatures (number of features in the dataset) to be preserved. + * + * @param float|null $totalVariance Total explained variance to be preserved + * @param int|null $numFeatures Number of features to be preserved + * + * @throws \Exception + */ + public function __construct($totalVariance = null, $numFeatures = null) + { + if ($totalVariance !== null && ($totalVariance < 0.1 || $totalVariance > 0.99)) { + throw new \Exception("Total variance can be a value between 0.1 and 0.99"); + } + if ($numFeatures !== null && $numFeatures <= 0) { + throw new \Exception("Number of features to be preserved should be greater than 0"); + } + if ($totalVariance !== null && $numFeatures !== null) { + throw new \Exception("Either totalVariance or numFeatures should be specified in order to run the algorithm"); + } + + if ($numFeatures !== null) { + $this->numFeatures = $numFeatures; + } + if ($totalVariance !== null) { + $this->totalVariance = $totalVariance; + } + } + + /** + * Trains the algorithm to transform the given data to a lower dimensional space. + * + * @param array $data + * @param array $classes + * + * @return array + */ + public function fit(array $data, array $classes) : array + { + $this->labels = $this->getLabels($classes); + $this->means = $this->calculateMeans($data, $classes); + + $sW = $this->calculateClassVar($data, $classes); + $sB = $this->calculateClassCov(); + + $S = $sW->inverse()->multiply($sB); + $this->eigenDecomposition($S->toArray()); + + $this->fit = true; + + return $this->reduce($data); + } + + /** + * Returns unique labels in the dataset + * + * @param array $classes + * + * @return array + */ + protected function getLabels(array $classes): array + { + $counts = array_count_values($classes); + + return array_keys($counts); + } + + + /** + * Calculates mean of each column for each class and returns + * n by m matrix where n is number of labels and m is number of columns + * + * @param type $data + * @param type $classes + * + * @return array + */ + protected function calculateMeans($data, $classes) : array + { + $means = []; + $counts= []; + $overallMean = array_fill(0, count($data[0]), 0.0); + + foreach ($data as $index => $row) { + $label = array_search($classes[$index], $this->labels); + + foreach ($row as $col => $val) { + if (! isset($means[$label][$col])) { + $means[$label][$col] = 0.0; + } + $means[$label][$col] += $val; + $overallMean[$col] += $val; + } + + if (! isset($counts[$label])) { + $counts[$label] = 0; + } + $counts[$label]++; + } + + foreach ($means as $index => $row) { + foreach ($row as $col => $sum) { + $means[$index][$col] = $sum / $counts[$index]; + } + } + + // Calculate overall mean of the dataset for each column + $numElements = array_sum($counts); + $map = function ($el) use ($numElements) { + return $el / $numElements; + }; + $this->overallMean = array_map($map, $overallMean); + $this->counts = $counts; + + return $means; + } + + + /** + * Returns in-class scatter matrix for each class, which + * is a n by m matrix where n is number of classes and + * m is number of columns + * + * @param array $data + * @param array $classes + * + * @return Matrix + */ + protected function calculateClassVar($data, $classes) + { + // s is an n (number of classes) by m (number of column) matrix + $s = array_fill(0, count($data[0]), array_fill(0, count($data[0]), 0)); + $sW = new Matrix($s, false); + + foreach ($data as $index => $row) { + $label = array_search($classes[$index], $this->labels); + $means = $this->means[$label]; + + $row = $this->calculateVar($row, $means); + + $sW = $sW->add($row); + } + + return $sW; + } + + /** + * Returns between-class scatter matrix for each class, which + * is an n by m matrix where n is number of classes and + * m is number of columns + * + * @return Matrix + */ + protected function calculateClassCov() + { + // s is an n (number of classes) by m (number of column) matrix + $s = array_fill(0, count($this->overallMean), array_fill(0, count($this->overallMean), 0)); + $sB = new Matrix($s, false); + + foreach ($this->means as $index => $classMeans) { + $row = $this->calculateVar($classMeans, $this->overallMean); + $N = $this->counts[$index]; + $sB = $sB->add($row->multiplyByScalar($N)); + } + + return $sB; + } + + /** + * Returns the result of the calculation (x - m)T.(x - m) + * + * @param array $row + * @param array $means + * + * @return Matrix + */ + protected function calculateVar(array $row, array $means) + { + $x = new Matrix($row, false); + $m = new Matrix($means, false); + $diff = $x->subtract($m); + + return $diff->transpose()->multiply($diff); + } + + /** + * Transforms the given sample to a lower dimensional vector by using + * the eigenVectors obtained in the last run of fit. + * + * @param array $sample + * + * @return array + */ + public function transform(array $sample) + { + if (!$this->fit) { + throw new \Exception("LDA has not been fitted with respect to original dataset, please run LDA::fit() first"); + } + + if (! is_array($sample[0])) { + $sample = [$sample]; + } + + return $this->reduce($sample); + } +} diff --git a/src/Phpml/DimensionReduction/PCA.php b/src/Phpml/DimensionReduction/PCA.php index 422dae4d..db2110db 100644 --- a/src/Phpml/DimensionReduction/PCA.php +++ b/src/Phpml/DimensionReduction/PCA.php @@ -4,27 +4,12 @@ namespace Phpml\DimensionReduction; -use Phpml\Math\LinearAlgebra\EigenvalueDecomposition; use Phpml\Math\Statistic\Covariance; use Phpml\Math\Statistic\Mean; use Phpml\Math\Matrix; -class PCA +class PCA extends EigenTransformerBase { - /** - * Total variance to be conserved after the reduction - * - * @var float - */ - public $totalVariance = 0.9; - - /** - * Number of features to be preserved after the reduction - * - * @var int - */ - public $numFeatures = null; - /** * Temporary storage for mean values for each dimension in given data * @@ -32,20 +17,6 @@ class PCA */ protected $means = []; - /** - * Eigenvectors of the covariance matrix - * - * @var array - */ - protected $eigVectors = []; - - /** - * Top eigenValues of the covariance matrix - * - * @var type - */ - protected $eigValues = []; - /** * @var bool */ @@ -100,7 +71,7 @@ public function fit(array $data) $covMatrix = Covariance::covarianceMatrix($data, array_fill(0, $n, 0)); - list($this->eigValues, $this->eigVectors) = $this->eigenDecomposition($covMatrix, $n); + $this->eigenDecomposition($covMatrix); $this->fit = true; @@ -146,63 +117,6 @@ protected function normalize(array $data, int $n) return $data; } - /** - * Calculates eigenValues and eigenVectors of the given matrix. Returns - * top eigenVectors along with the largest eigenValues. The total explained variance - * of these eigenVectors will be no less than desired $totalVariance value - * - * @param array $matrix - * @param int $n - * - * @return array - */ - protected function eigenDecomposition(array $matrix, int $n) - { - $eig = new EigenvalueDecomposition($matrix); - $eigVals = $eig->getRealEigenvalues(); - $eigVects= $eig->getEigenvectors(); - - $totalEigVal = array_sum($eigVals); - // Sort eigenvalues in descending order - arsort($eigVals); - - $explainedVar = 0.0; - $vectors = []; - $values = []; - foreach ($eigVals as $i => $eigVal) { - $explainedVar += $eigVal / $totalEigVal; - $vectors[] = $eigVects[$i]; - $values[] = $eigVal; - - if ($this->numFeatures !== null) { - if (count($vectors) == $this->numFeatures) { - break; - } - } else { - if ($explainedVar >= $this->totalVariance) { - break; - } - } - } - - return [$values, $vectors]; - } - - /** - * Returns the reduced data - * - * @param array $data - * - * @return array - */ - protected function reduce(array $data) - { - $m1 = new Matrix($data); - $m2 = new Matrix($this->eigVectors); - - return $m1->multiply($m2->transpose())->toArray(); - } - /** * Transforms the given sample to a lower dimensional vector by using * the eigenVectors obtained in the last run of fit. diff --git a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php index 27557bbd..5cbc1212 100644 --- a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -130,10 +130,10 @@ private function tred2() $this->e[$j] = $g; } $f = 0.0; + if ($h === 0 || $h < 1e-32) { + $h = 1e-32; + } for ($j = 0; $j < $i; ++$j) { - if ($h === 0) { - $h = 1e-20; - } $this->e[$j] /= $h; $f += $this->e[$j] * $this->d[$j]; } diff --git a/src/Phpml/Math/LinearAlgebra/LUDecomposition.php b/src/Phpml/Math/LinearAlgebra/LUDecomposition.php new file mode 100644 index 00000000..1aeb2395 --- /dev/null +++ b/src/Phpml/Math/LinearAlgebra/LUDecomposition.php @@ -0,0 +1,297 @@ += n, the LU decomposition is an m-by-n + * unit lower triangular matrix L, an n-by-n upper triangular matrix U, + * and a permutation vector piv of length m so that A(piv,:) = L*U. + * If m < n, then L is m-by-m and U is m-by-n. + * + * The LU decompostion with pivoting always exists, even if the matrix is + * singular, so the constructor will never fail. The primary use of the + * LU decomposition is in the solution of square systems of simultaneous + * linear equations. This will fail if isNonsingular() returns false. + * + * @author Paul Meagher + * @author Bartosz Matosiuk + * @author Michael Bommarito + * @version 1.1 + * @license PHP v3.0 + * + * Slightly changed to adapt the original code to PHP-ML library + * @date 2017/04/24 + * @author Mustafa Karabulut + */ + +namespace Phpml\Math\LinearAlgebra; + +use Phpml\Math\Matrix; +use Phpml\Exception\MatrixException; + +class LUDecomposition +{ + /** + * Decomposition storage + * @var array + */ + private $LU = []; + + /** + * Row dimension. + * @var int + */ + private $m; + + /** + * Column dimension. + * @var int + */ + private $n; + + /** + * Pivot sign. + * @var int + */ + private $pivsign; + + /** + * Internal storage of pivot vector. + * @var array + */ + private $piv = []; + + + /** + * LU Decomposition constructor. + * + * @param $A Rectangular matrix + * @return Structure to access L, U and piv. + */ + public function __construct(Matrix $A) + { + if ($A->getRows() != $A->getColumns()) { + throw MatrixException::notSquareMatrix(); + } + + // Use a "left-looking", dot-product, Crout/Doolittle algorithm. + $this->LU = $A->toArray(); + $this->m = $A->getRows(); + $this->n = $A->getColumns(); + for ($i = 0; $i < $this->m; ++$i) { + $this->piv[$i] = $i; + } + $this->pivsign = 1; + $LUrowi = $LUcolj = []; + + // Outer loop. + for ($j = 0; $j < $this->n; ++$j) { + // Make a copy of the j-th column to localize references. + for ($i = 0; $i < $this->m; ++$i) { + $LUcolj[$i] = &$this->LU[$i][$j]; + } + // Apply previous transformations. + for ($i = 0; $i < $this->m; ++$i) { + $LUrowi = $this->LU[$i]; + // Most of the time is spent in the following dot product. + $kmax = min($i, $j); + $s = 0.0; + for ($k = 0; $k < $kmax; ++$k) { + $s += $LUrowi[$k] * $LUcolj[$k]; + } + $LUrowi[$j] = $LUcolj[$i] -= $s; + } + // Find pivot and exchange if necessary. + $p = $j; + for ($i = $j+1; $i < $this->m; ++$i) { + if (abs($LUcolj[$i]) > abs($LUcolj[$p])) { + $p = $i; + } + } + if ($p != $j) { + for ($k = 0; $k < $this->n; ++$k) { + $t = $this->LU[$p][$k]; + $this->LU[$p][$k] = $this->LU[$j][$k]; + $this->LU[$j][$k] = $t; + } + $k = $this->piv[$p]; + $this->piv[$p] = $this->piv[$j]; + $this->piv[$j] = $k; + $this->pivsign = $this->pivsign * -1; + } + // Compute multipliers. + if (($j < $this->m) && ($this->LU[$j][$j] != 0.0)) { + for ($i = $j+1; $i < $this->m; ++$i) { + $this->LU[$i][$j] /= $this->LU[$j][$j]; + } + } + } + } // function __construct() + + + /** + * Get lower triangular factor. + * + * @return array Lower triangular factor + */ + public function getL() + { + $L = []; + for ($i = 0; $i < $this->m; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + if ($i > $j) { + $L[$i][$j] = $this->LU[$i][$j]; + } elseif ($i == $j) { + $L[$i][$j] = 1.0; + } else { + $L[$i][$j] = 0.0; + } + } + } + return new Matrix($L); + } // function getL() + + + /** + * Get upper triangular factor. + * + * @return array Upper triangular factor + */ + public function getU() + { + $U = []; + for ($i = 0; $i < $this->n; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + if ($i <= $j) { + $U[$i][$j] = $this->LU[$i][$j]; + } else { + $U[$i][$j] = 0.0; + } + } + } + return new Matrix($U); + } // function getU() + + + /** + * Return pivot permutation vector. + * + * @return array Pivot vector + */ + public function getPivot() + { + return $this->piv; + } // function getPivot() + + + /** + * Alias for getPivot + * + * @see getPivot + */ + public function getDoublePivot() + { + return $this->getPivot(); + } // function getDoublePivot() + + + /** + * Is the matrix nonsingular? + * + * @return true if U, and hence A, is nonsingular. + */ + public function isNonsingular() + { + for ($j = 0; $j < $this->n; ++$j) { + if ($this->LU[$j][$j] == 0) { + return false; + } + } + return true; + } // function isNonsingular() + + + /** + * Count determinants + * + * @return array d matrix deterninat + */ + public function det() + { + if ($this->m == $this->n) { + $d = $this->pivsign; + for ($j = 0; $j < $this->n; ++$j) { + $d *= $this->LU[$j][$j]; + } + return $d; + } else { + throw MatrixException::notSquareMatrix(); + } + } // function det() + + + /** + * Solve A*X = B + * + * @param Matrix $B A Matrix with as many rows as A and any number of columns. + * + * @return array X so that L*U*X = B(piv,:) + * + * @throws MatrixException + */ + public function solve(Matrix $B) + { + if ($B->getRows() != $this->m) { + throw MatrixException::notSquareMatrix(); + } + + if (! $this->isNonsingular()) { + throw MatrixException::singularMatrix(); + } + + // Copy right hand side with pivoting + $nx = $B->getColumns(); + $X = $this->getSubMatrix($B->toArray(), $this->piv, 0, $nx-1); + // Solve L*Y = B(piv,:) + for ($k = 0; $k < $this->n; ++$k) { + for ($i = $k+1; $i < $this->n; ++$i) { + for ($j = 0; $j < $nx; ++$j) { + $X[$i][$j] -= $X[$k][$j] * $this->LU[$i][$k]; + } + } + } + // Solve U*X = Y; + for ($k = $this->n-1; $k >= 0; --$k) { + for ($j = 0; $j < $nx; ++$j) { + $X[$k][$j] /= $this->LU[$k][$k]; + } + for ($i = 0; $i < $k; ++$i) { + for ($j = 0; $j < $nx; ++$j) { + $X[$i][$j] -= $X[$k][$j] * $this->LU[$i][$k]; + } + } + } + return $X; + } // function solve() + + /** + * @param Matrix $matrix + * @param int $j0 + * @param int $jF + * + * @return array + */ + protected function getSubMatrix(array $matrix, array $RL, int $j0, int $jF) + { + $m = count($RL); + $n = $jF - $j0; + $R = array_fill(0, $m, array_fill(0, $n+1, 0.0)); + + for ($i = 0; $i < $m; ++$i) { + for ($j = $j0; $j <= $jF; ++$j) { + $R[$i][$j - $j0]= $matrix[ $RL[$i] ][$j]; + } + } + + return $R; + } +} // class LUDecomposition diff --git a/src/Phpml/Math/Matrix.php b/src/Phpml/Math/Matrix.php index 25101f3f..c996e7fb 100644 --- a/src/Phpml/Math/Matrix.php +++ b/src/Phpml/Math/Matrix.php @@ -4,6 +4,7 @@ namespace Phpml\Math; +use Phpml\Math\LinearAlgebra\LUDecomposition; use Phpml\Exception\InvalidArgumentException; use Phpml\Exception\MatrixException; @@ -137,32 +138,8 @@ public function getDeterminant() throw MatrixException::notSquareMatrix(); } - return $this->determinant = $this->calculateDeterminant(); - } - - /** - * @return float|int - * - * @throws MatrixException - */ - private function calculateDeterminant() - { - $determinant = 0; - if ($this->rows == 1 && $this->columns == 1) { - $determinant = $this->matrix[0][0]; - } elseif ($this->rows == 2 && $this->columns == 2) { - $determinant = - $this->matrix[0][0] * $this->matrix[1][1] - - $this->matrix[0][1] * $this->matrix[1][0]; - } else { - for ($j = 0; $j < $this->columns; ++$j) { - $subMatrix = $this->crossOut(0, $j); - $minor = $this->matrix[0][$j] * $subMatrix->getDeterminant(); - $determinant += fmod((float) $j, 2.0) == 0 ? $minor : -$minor; - } - } - - return $determinant; + $lu = new LUDecomposition($this); + return $this->determinant = $lu->det(); } /** @@ -303,21 +280,26 @@ public function inverse() throw MatrixException::notSquareMatrix(); } - if ($this->isSingular()) { - throw MatrixException::singularMatrix(); - } + $LU = new LUDecomposition($this); + $identity = $this->getIdentity(); + $inverse = $LU->solve($identity); - $newMatrix = []; - for ($i = 0; $i < $this->rows; ++$i) { - for ($j = 0; $j < $this->columns; ++$j) { - $minor = $this->crossOut($i, $j)->getDeterminant(); - $newMatrix[$i][$j] = fmod((float) ($i + $j), 2.0) == 0 ? $minor : -$minor; - } - } + return new self($inverse, false); + } - $cofactorMatrix = new self($newMatrix, false); + /** + * Returns diagonal identity matrix of the same size of this matrix + * + * @return Matrix + */ + protected function getIdentity() + { + $array = array_fill(0, $this->rows, array_fill(0, $this->columns, 0)); + for ($i=0; $i < $this->rows; $i++) { + $array[$i][$i] = 1; + } - return $cofactorMatrix->transpose()->divideByScalar($this->getDeterminant()); + return new self($array, false); } /** diff --git a/tests/Phpml/DimensionReduction/LDATest.php b/tests/Phpml/DimensionReduction/LDATest.php new file mode 100644 index 00000000..5ebe0187 --- /dev/null +++ b/tests/Phpml/DimensionReduction/LDATest.php @@ -0,0 +1,65 @@ +fit($dataset->getSamples(), $dataset->getTargets()); + + // Some samples of the Iris data will be checked manually + // First 3 and last 3 rows from the original dataset + $data = [ + [5.1, 3.5, 1.4, 0.2], + [4.9, 3.0, 1.4, 0.2], + [4.7, 3.2, 1.3, 0.2], + [6.5, 3.0, 5.2, 2.0], + [6.2, 3.4, 5.4, 2.3], + [5.9, 3.0, 5.1, 1.8] + ]; + $transformed2 = [ + [-1.4922092756753, 1.9047102045574], + [-1.2576556684358, 1.608414450935], + [-1.3487505965419, 1.749846351699], + [1.7759343101456, 2.0371552314006], + [2.0059819019159, 2.4493123003226], + [1.701474913008, 1.9037880473772] + ]; + + $control = []; + $control = array_merge($control, array_slice($transformed, 0, 3)); + $control = array_merge($control, array_slice($transformed, -3)); + + $check = function ($row1, $row2) use ($epsilon) { + // Due to the fact that the sign of values can be flipped + // during the calculation of eigenValues, we have to compare + // absolute value of the values + $row1 = array_map('abs', $row1); + $row2 = array_map('abs', $row2); + $this->assertEquals($row1, $row2, '', $epsilon); + }; + array_map($check, $control, $transformed2); + + // Fitted LDA object should be able to return same values again + // for each projected row + foreach ($data as $i => $row) { + $newRow = [$transformed2[$i]]; + $newRow2= $lda->transform($row); + + array_map($check, $newRow, $newRow2); + } + } +} From 7eee6748d2c4eb03f20ca5ed2b685a53e2d9733c Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Sat, 13 May 2017 12:57:32 +0200 Subject: [PATCH 174/328] php-cs-fixer (#85) Remove obsolete php-cs-fixer.sh script Update contributing guidelines --- .php_cs | 5 +++-- CONTRIBUTING.md | 10 ++-------- tools/php-cs-fixer.sh | 6 ------ 3 files changed, 5 insertions(+), 16 deletions(-) delete mode 100755 tools/php-cs-fixer.sh diff --git a/.php_cs b/.php_cs index 5a2dd57e..0ea0c2ae 100644 --- a/.php_cs +++ b/.php_cs @@ -13,5 +13,6 @@ return PhpCsFixer\Config::create() PhpCsFixer\Finder::create() ->in(__DIR__ . '/src') ->in(__DIR__ . '/tests') - )->setRiskyAllowed(true) - ->setUsingCache(false); \ No newline at end of file + ) + ->setRiskyAllowed(true) + ->setUsingCache(false); diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d9dcaff5..889f720f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ code base clean, unified and future proof. ## Branch -You should only open pull requests against the develop branch. +You should only open pull requests against the master branch. ## Unit-Tests @@ -28,13 +28,7 @@ Please allow me time to review your pull requests. I will give my best to review ## Coding Standards -When contributing code to PHP-ML, you must follow its coding standards. To make a long story short, here is the golden tool: - -``` -tools/php-cs-fixer.sh -``` - -This script run PHP Coding Standards Fixer with `--level=symfony` param. +When contributing code to PHP-ML, you must follow its coding standards. It's as easy as executing `php-cs-fixer` (v2) in root directory. More about PHP-CS-Fixer: [http://cs.sensiolabs.org/](http://cs.sensiolabs.org/) diff --git a/tools/php-cs-fixer.sh b/tools/php-cs-fixer.sh deleted file mode 100755 index 6eb37cbd..00000000 --- a/tools/php-cs-fixer.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -echo "Fixing src/ folder" -php-cs-fixer fix src/ - -echo "Fixing tests/ folder" -php-cs-fixer fix tests/ From 43f15d2f7e077ec2f5b4cca5993b318aea0f00f6 Mon Sep 17 00:00:00 2001 From: Humberto Castelo Branco Date: Sat, 13 May 2017 07:58:06 -0300 Subject: [PATCH 175/328] New methods: setBinPath, setVarPath in SupportVectorMachine (#73) * new methods: setBinPath, setVarPath * fix whitespaces and breaklines --- .../SupportVectorMachine.php | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php index f3828b4d..9f6da70e 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -9,8 +9,8 @@ class SupportVectorMachine { use Trainable; - - /** + + /** * @var int */ private $type; @@ -128,6 +128,26 @@ public function __construct( $this->varPath = $rootPath.'var'.DIRECTORY_SEPARATOR; } + /** + * @param string $binPath + */ + public function setBinPath(string $binPath) + { + $this->binPath = $binPath; + + return $this; + } + + /** + * @param string $varPath + */ + public function setVarPath(string $varPath) + { + $this->varPath = $varPath; + + return $this; + } + /** * @param array $samples * @param array $targets From 7ab80b6e56f1f65e8dcdd0caf0aab959d44ad3c7 Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Wed, 17 May 2017 09:03:25 +0200 Subject: [PATCH 176/328] Code Style (#86) * Code Style * Code Review fixes --- src/Phpml/Classification/DecisionTree.php | 42 ++- .../DecisionTree/DecisionTreeLeaf.php | 4 +- .../Classification/Ensemble/AdaBoost.php | 8 +- src/Phpml/Classification/Ensemble/Bagging.php | 25 +- .../Classification/Ensemble/RandomForest.php | 20 +- src/Phpml/Classification/Linear/Adaline.php | 13 +- .../Classification/Linear/DecisionStump.php | 14 +- .../Linear/LogisticRegression.php | 32 +- .../Classification/Linear/Perceptron.php | 33 +- src/Phpml/Classification/NaiveBayes.php | 16 +- src/Phpml/Clustering/FuzzyCMeans.php | 47 ++- src/Phpml/Clustering/KMeans/Space.php | 4 + src/Phpml/Dataset/CsvDataset.php | 9 +- .../EigenTransformerBase.php | 6 +- src/Phpml/DimensionReduction/KernelPCA.php | 21 +- src/Phpml/DimensionReduction/LDA.php | 20 +- src/Phpml/DimensionReduction/PCA.php | 11 +- src/Phpml/Exception/DatasetException.php | 1 - src/Phpml/Exception/FileException.php | 1 - .../Exception/InvalidArgumentException.php | 4 +- src/Phpml/Exception/SerializeException.php | 1 - src/Phpml/Helper/OneVsRest.php | 11 +- .../Helper/Optimizer/ConjugateGradient.php | 28 +- src/Phpml/Helper/Optimizer/GD.php | 10 +- src/Phpml/Helper/Optimizer/Optimizer.php | 9 +- src/Phpml/Helper/Optimizer/StochasticGD.php | 4 +- src/Phpml/Helper/Predictable.php | 12 +- src/Phpml/IncrementalEstimator.php | 1 - src/Phpml/Math/Kernel/RBF.php | 4 +- .../LinearAlgebra/EigenvalueDecomposition.php | 350 +++++++++--------- .../Math/LinearAlgebra/LUDecomposition.php | 80 ++-- src/Phpml/Math/Matrix.php | 23 +- src/Phpml/Math/Statistic/Covariance.php | 30 +- src/Phpml/Math/Statistic/Gaussian.php | 2 +- src/Phpml/Math/Statistic/Mean.php | 2 +- src/Phpml/Metric/ClassificationReport.php | 4 +- src/Phpml/ModelManager.php | 7 +- .../Training/Backpropagation.php | 4 +- src/Phpml/Preprocessing/Normalizer.php | 2 +- .../SupportVectorMachine.php | 8 +- 40 files changed, 535 insertions(+), 388 deletions(-) diff --git a/src/Phpml/Classification/DecisionTree.php b/src/Phpml/Classification/DecisionTree.php index 6e890c9c..da8b81bd 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -102,19 +102,21 @@ public function train(array $samples, array $targets) $this->columnNames = array_slice($this->columnNames, 0, $this->featureCount); } elseif (count($this->columnNames) < $this->featureCount) { $this->columnNames = array_merge($this->columnNames, - range(count($this->columnNames), $this->featureCount - 1)); + range(count($this->columnNames), $this->featureCount - 1) + ); } } /** * @param array $samples + * * @return array */ public static function getColumnTypes(array $samples) : array { $types = []; $featureCount = count($samples[0]); - for ($i=0; $i < $featureCount; $i++) { + for ($i = 0; $i < $featureCount; ++$i) { $values = array_column($samples, $i); $isCategorical = self::isCategoricalColumn($values); $types[] = $isCategorical ? self::NOMINAL : self::CONTINUOUS; @@ -125,7 +127,8 @@ public static function getColumnTypes(array $samples) : array /** * @param array $records - * @param int $depth + * @param int $depth + * * @return DecisionTreeLeaf */ protected function getSplitLeaf(array $records, int $depth = 0) : DecisionTreeLeaf @@ -163,10 +166,10 @@ protected function getSplitLeaf(array $records, int $depth = 0) : DecisionTreeLe // Group remaining targets $target = $this->targets[$recordNo]; - if (! array_key_exists($target, $remainingTargets)) { + if (!array_key_exists($target, $remainingTargets)) { $remainingTargets[$target] = 1; } else { - $remainingTargets[$target]++; + ++$remainingTargets[$target]; } } @@ -188,6 +191,7 @@ protected function getSplitLeaf(array $records, int $depth = 0) : DecisionTreeLe /** * @param array $records + * * @return DecisionTreeLeaf */ protected function getBestSplit(array $records) : DecisionTreeLeaf @@ -251,7 +255,7 @@ protected function getBestSplit(array $records) : DecisionTreeLeaf protected function getSelectedFeatures() : array { $allFeatures = range(0, $this->featureCount - 1); - if ($this->numUsableFeatures === 0 && ! $this->selectedFeatures) { + if ($this->numUsableFeatures === 0 && !$this->selectedFeatures) { return $allFeatures; } @@ -271,9 +275,10 @@ protected function getSelectedFeatures() : array } /** - * @param $baseValue + * @param mixed $baseValue * @param array $colValues * @param array $targets + * * @return float */ public function getGiniIndex($baseValue, array $colValues, array $targets) : float @@ -282,13 +287,15 @@ public function getGiniIndex($baseValue, array $colValues, array $targets) : flo foreach ($this->labels as $label) { $countMatrix[$label] = [0, 0]; } + foreach ($colValues as $index => $value) { $label = $targets[$index]; $rowIndex = $value === $baseValue ? 0 : 1; - $countMatrix[$label][$rowIndex]++; + ++$countMatrix[$label][$rowIndex]; } + $giniParts = [0, 0]; - for ($i=0; $i<=1; $i++) { + for ($i = 0; $i <= 1; ++$i) { $part = 0; $sum = array_sum(array_column($countMatrix, $i)); if ($sum > 0) { @@ -296,6 +303,7 @@ public function getGiniIndex($baseValue, array $colValues, array $targets) : flo $part += pow($countMatrix[$label][$i] / floatval($sum), 2); } } + $giniParts[$i] = (1 - $part) * $sum; } @@ -304,6 +312,7 @@ public function getGiniIndex($baseValue, array $colValues, array $targets) : flo /** * @param array $samples + * * @return array */ protected function preprocess(array $samples) : array @@ -311,7 +320,7 @@ protected function preprocess(array $samples) : array // Detect and convert continuous data column values into // discrete values by using the median as a threshold value $columns = []; - for ($i=0; $i<$this->featureCount; $i++) { + for ($i = 0; $i < $this->featureCount; ++$i) { $values = array_column($samples, $i); if ($this->columnTypes[$i] == self::CONTINUOUS) { $median = Mean::median($values); @@ -332,6 +341,7 @@ protected function preprocess(array $samples) : array /** * @param array $columnValues + * * @return bool */ protected static function isCategoricalColumn(array $columnValues) : bool @@ -348,6 +358,7 @@ protected static function isCategoricalColumn(array $columnValues) : bool if ($floatValues) { return false; } + if (count($numericValues) !== $count) { return true; } @@ -365,7 +376,9 @@ protected static function isCategoricalColumn(array $columnValues) : bool * randomly selected for each split operation. * * @param int $numFeatures + * * @return $this + * * @throws InvalidArgumentException */ public function setNumFeatures(int $numFeatures) @@ -394,7 +407,9 @@ protected function setSelectedFeatures(array $selectedFeatures) * column importances are desired to be inspected. * * @param array $names + * * @return $this + * * @throws InvalidArgumentException */ public function setColumnNames(array $names) @@ -458,8 +473,9 @@ public function getFeatureImportances() * Collects and returns an array of internal nodes that use the given * column as a split criterion * - * @param int $column + * @param int $column * @param DecisionTreeLeaf $node + * * @return array */ protected function getSplitNodesByColumn(int $column, DecisionTreeLeaf $node) : array @@ -478,9 +494,11 @@ protected function getSplitNodesByColumn(int $column, DecisionTreeLeaf $node) : if ($node->leftLeaf) { $lNodes = $this->getSplitNodesByColumn($column, $node->leftLeaf); } + if ($node->rightLeaf) { $rNodes = $this->getSplitNodesByColumn($column, $node->rightLeaf); } + $nodes = array_merge($nodes, $lNodes, $rNodes); return $nodes; @@ -488,6 +506,7 @@ protected function getSplitNodesByColumn(int $column, DecisionTreeLeaf $node) : /** * @param array $sample + * * @return mixed */ protected function predictSample(array $sample) @@ -497,6 +516,7 @@ protected function predictSample(array $sample) if ($node->isTerminal) { break; } + if ($node->evaluate($sample)) { $node = $node->leftLeaf; } else { diff --git a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php index bbb31751..787108f8 100644 --- a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -92,6 +92,8 @@ public function evaluate($record) * Returns Mean Decrease Impurity (MDI) in the node. * For terminal nodes, this value is equal to 0 * + * @param int $parentRecordCount + * * @return float */ public function getNodeImpurityDecrease(int $parentRecordCount) @@ -133,7 +135,7 @@ public function getHTML($columnNames = null) } else { $col = "col_$this->columnIndex"; } - if (! preg_match("/^[<>=]{1,2}/", $value)) { + if (!preg_match("/^[<>=]{1,2}/", $value)) { $value = "=$value"; } $value = "$col $value
Gini: ". number_format($this->giniIndex, 2); diff --git a/src/Phpml/Classification/Ensemble/AdaBoost.php b/src/Phpml/Classification/Ensemble/AdaBoost.php index 3d1e4187..38571da1 100644 --- a/src/Phpml/Classification/Ensemble/AdaBoost.php +++ b/src/Phpml/Classification/Ensemble/AdaBoost.php @@ -75,6 +75,7 @@ class AdaBoost implements Classifier * improve classification performance of 'weak' classifiers such as * DecisionStump (default base classifier of AdaBoost). * + * @param int $maxIterations */ public function __construct(int $maxIterations = 50) { @@ -96,6 +97,8 @@ public function setBaseClassifier(string $baseClassifier = DecisionStump::class, /** * @param array $samples * @param array $targets + * + * @throws \Exception */ public function train(array $samples, array $targets) { @@ -123,7 +126,6 @@ public function train(array $samples, array $targets) // Execute the algorithm for a maximum number of iterations $currIter = 0; while ($this->maxIterations > $currIter++) { - // Determine the best 'weak' classifier based on current weights $classifier = $this->getBestClassifier(); $errorRate = $this->evaluateClassifier($classifier); @@ -181,7 +183,7 @@ protected function resample() $targets = []; foreach ($weights as $index => $weight) { $z = (int)round(($weight - $mean) / $std) - $minZ + 1; - for ($i=0; $i < $z; $i++) { + for ($i = 0; $i < $z; ++$i) { if (rand(0, 1) == 0) { continue; } @@ -197,6 +199,8 @@ protected function resample() * Evaluates the classifier and returns the classification error rate * * @param Classifier $classifier + * + * @return float */ protected function evaluateClassifier(Classifier $classifier) { diff --git a/src/Phpml/Classification/Ensemble/Bagging.php b/src/Phpml/Classification/Ensemble/Bagging.php index 1bb20273..1af155d9 100644 --- a/src/Phpml/Classification/Ensemble/Bagging.php +++ b/src/Phpml/Classification/Ensemble/Bagging.php @@ -59,13 +59,13 @@ class Bagging implements Classifier private $samples = []; /** - * Creates an ensemble classifier with given number of base classifiers
- * Default number of base classifiers is 100. + * Creates an ensemble classifier with given number of base classifiers + * Default number of base classifiers is 50. * The more number of base classifiers, the better performance but at the cost of procesing time * * @param int $numClassifier */ - public function __construct($numClassifier = 50) + public function __construct(int $numClassifier = 50) { $this->numClassifier = $numClassifier; } @@ -76,14 +76,17 @@ public function __construct($numClassifier = 50) * to train each base classifier. * * @param float $ratio + * * @return $this - * @throws Exception + * + * @throws \Exception */ public function setSubsetRatio(float $ratio) { if ($ratio < 0.1 || $ratio > 1.0) { throw new \Exception("Subset ratio should be between 0.1 and 1.0"); } + $this->subsetRatio = $ratio; return $this; } @@ -98,12 +101,14 @@ public function setSubsetRatio(float $ratio) * * @param string $classifier * @param array $classifierOptions + * * @return $this */ public function setClassifer(string $classifier, array $classifierOptions = []) { $this->classifier = $classifier; $this->classifierOptions = $classifierOptions; + return $this; } @@ -138,11 +143,12 @@ protected function getRandomSubset(int $index) $targets = []; srand($index); $bootstrapSize = $this->subsetRatio * $this->numSamples; - for ($i=0; $i < $bootstrapSize; $i++) { + for ($i = 0; $i < $bootstrapSize; ++$i) { $rand = rand(0, $this->numSamples - 1); $samples[] = $this->samples[$rand]; $targets[] = $this->targets[$rand]; } + return [$samples, $targets]; } @@ -152,24 +158,25 @@ protected function getRandomSubset(int $index) protected function initClassifiers() { $classifiers = []; - for ($i=0; $i<$this->numClassifier; $i++) { + for ($i = 0; $i < $this->numClassifier; ++$i) { $ref = new \ReflectionClass($this->classifier); if ($this->classifierOptions) { $obj = $ref->newInstanceArgs($this->classifierOptions); } else { $obj = $ref->newInstance(); } - $classifiers[] = $this->initSingleClassifier($obj, $i); + + $classifiers[] = $this->initSingleClassifier($obj); } return $classifiers; } /** * @param Classifier $classifier - * @param int $index + * * @return Classifier */ - protected function initSingleClassifier($classifier, $index) + protected function initSingleClassifier($classifier) { return $classifier; } diff --git a/src/Phpml/Classification/Ensemble/RandomForest.php b/src/Phpml/Classification/Ensemble/RandomForest.php index 273eb21a..7849cd8b 100644 --- a/src/Phpml/Classification/Ensemble/RandomForest.php +++ b/src/Phpml/Classification/Ensemble/RandomForest.php @@ -5,7 +5,6 @@ namespace Phpml\Classification\Ensemble; use Phpml\Classification\DecisionTree; -use Phpml\Classification\Classifier; class RandomForest extends Bagging { @@ -24,9 +23,9 @@ class RandomForest extends Bagging * may increase the prediction performance while it will also substantially * increase the processing time and the required memory * - * @param type $numClassifier + * @param int $numClassifier */ - public function __construct($numClassifier = 50) + public function __construct(int $numClassifier = 50) { parent::__construct($numClassifier); @@ -43,17 +42,21 @@ public function __construct($numClassifier = 50) * features to be taken into consideration while selecting subspace of features * * @param mixed $ratio string or float should be given + * * @return $this - * @throws Exception + * + * @throws \Exception */ public function setFeatureSubsetRatio($ratio) { if (is_float($ratio) && ($ratio < 0.1 || $ratio > 1.0)) { throw new \Exception("When a float given, feature subset ratio should be between 0.1 and 1.0"); } + if (is_string($ratio) && $ratio != 'sqrt' && $ratio != 'log') { throw new \Exception("When a string given, feature subset ratio can only be 'sqrt' or 'log' "); } + $this->featureSubsetRatio = $ratio; return $this; } @@ -62,8 +65,11 @@ public function setFeatureSubsetRatio($ratio) * RandomForest algorithm is usable *only* with DecisionTree * * @param string $classifier - * @param array $classifierOptions + * @param array $classifierOptions + * * @return $this + * + * @throws \Exception */ public function setClassifer(string $classifier, array $classifierOptions = []) { @@ -125,10 +131,10 @@ public function setColumnNames(array $names) /** * @param DecisionTree $classifier - * @param int $index + * * @return DecisionTree */ - protected function initSingleClassifier($classifier, $index) + protected function initSingleClassifier($classifier) { if (is_float($this->featureSubsetRatio)) { $featureCount = (int)($this->featureSubsetRatio * $this->featureCount); diff --git a/src/Phpml/Classification/Linear/Adaline.php b/src/Phpml/Classification/Linear/Adaline.php index f34dc5c4..b94de28d 100644 --- a/src/Phpml/Classification/Linear/Adaline.php +++ b/src/Phpml/Classification/Linear/Adaline.php @@ -4,11 +4,8 @@ namespace Phpml\Classification\Linear; -use Phpml\Classification\Classifier; - class Adaline extends Perceptron { - /** * Batch training is the default Adaline training algorithm */ @@ -35,13 +32,17 @@ class Adaline extends Perceptron * If normalizeInputs is set to true, then every input given to the algorithm will be standardized * by use of standard deviation and mean calculation * - * @param int $learningRate - * @param int $maxIterations + * @param float $learningRate + * @param int $maxIterations + * @param bool $normalizeInputs + * @param int $trainingType + * + * @throws \Exception */ public function __construct(float $learningRate = 0.001, int $maxIterations = 1000, bool $normalizeInputs = true, int $trainingType = self::BATCH_TRAINING) { - if (! in_array($trainingType, [self::BATCH_TRAINING, self::ONLINE_TRAINING])) { + if (!in_array($trainingType, [self::BATCH_TRAINING, self::ONLINE_TRAINING])) { throw new \Exception("Adaline can only be trained with batch and online/stochastic gradient descent algorithm"); } diff --git a/src/Phpml/Classification/Linear/DecisionStump.php b/src/Phpml/Classification/Linear/DecisionStump.php index 99f982ff..5a3247fe 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -87,6 +87,8 @@ public function __construct(int $columnIndex = self::AUTO_SELECT) /** * @param array $samples * @param array $targets + * @param array $labels + * * @throws \Exception */ protected function trainBinary(array $samples, array $targets, array $labels) @@ -237,13 +239,13 @@ protected function getBestNominalSplit(array $samples, array $targets, int $col) /** * - * @param type $leftValue - * @param type $operator - * @param type $rightValue + * @param mixed $leftValue + * @param string $operator + * @param mixed $rightValue * * @return boolean */ - protected function evaluate($leftValue, $operator, $rightValue) + protected function evaluate($leftValue, string $operator, $rightValue) { switch ($operator) { case '>': return $leftValue > $rightValue; @@ -288,10 +290,10 @@ protected function calculateErrorRate(array $targets, float $threshold, string $ $wrong += $this->weights[$index]; } - if (! isset($prob[$predicted][$target])) { + if (!isset($prob[$predicted][$target])) { $prob[$predicted][$target] = 0; } - $prob[$predicted][$target]++; + ++$prob[$predicted][$target]; } // Calculate probabilities: Proportion of labels in each leaf diff --git a/src/Phpml/Classification/Linear/LogisticRegression.php b/src/Phpml/Classification/Linear/LogisticRegression.php index bd56d347..bc6a3c9e 100644 --- a/src/Phpml/Classification/Linear/LogisticRegression.php +++ b/src/Phpml/Classification/Linear/LogisticRegression.php @@ -4,21 +4,19 @@ namespace Phpml\Classification\Linear; -use Phpml\Classification\Classifier; use Phpml\Helper\Optimizer\ConjugateGradient; class LogisticRegression extends Adaline { - /** * Batch training: Gradient descent algorithm (default) */ - const BATCH_TRAINING = 1; + const BATCH_TRAINING = 1; /** * Online training: Stochastic gradient descent learning */ - const ONLINE_TRAINING = 2; + const ONLINE_TRAINING = 2; /** * Conjugate Batch: Conjugate Gradient algorithm @@ -74,13 +72,13 @@ public function __construct(int $maxIterations = 500, bool $normalizeInputs = tr string $penalty = 'L2') { $trainingTypes = range(self::BATCH_TRAINING, self::CONJUGATE_GRAD_TRAINING); - if (! in_array($trainingType, $trainingTypes)) { + if (!in_array($trainingType, $trainingTypes)) { throw new \Exception("Logistic regression can only be trained with " . "batch (gradient descent), online (stochastic gradient descent) " . "or conjugate batch (conjugate gradients) algorithms"); } - if (! in_array($cost, ['log', 'sse'])) { + if (!in_array($cost, ['log', 'sse'])) { throw new \Exception("Logistic regression cost function can be one of the following: \n" . "'log' for log-likelihood and 'sse' for sum of squared errors"); } @@ -126,6 +124,8 @@ public function setLambda(float $lambda) * * @param array $samples * @param array $targets + * + * @throws \Exception */ protected function runTraining(array $samples, array $targets) { @@ -140,12 +140,18 @@ protected function runTraining(array $samples, array $targets) case self::CONJUGATE_GRAD_TRAINING: return $this->runConjugateGradient($samples, $targets, $callback); + + default: + throw new \Exception('Logistic regression has invalid training type: %s.', $this->trainingType); } } /** - * Executes Conjugate Gradient method to optimize the - * weights of the LogReg model + * Executes Conjugate Gradient method to optimize the weights of the LogReg model + * + * @param array $samples + * @param array $targets + * @param \Closure $gradientFunc */ protected function runConjugateGradient(array $samples, array $targets, \Closure $gradientFunc) { @@ -162,6 +168,8 @@ protected function runConjugateGradient(array $samples, array $targets, \Closure * Returns the appropriate callback function for the selected cost function * * @return \Closure + * + * @throws \Exception */ protected function getCostFunction() { @@ -203,7 +211,7 @@ protected function getCostFunction() return $callback; case 'sse': - /** + /* * Sum of squared errors or least squared errors cost function: * J(x) = ∑ (y - h(x))^2 * @@ -224,6 +232,9 @@ protected function getCostFunction() }; return $callback; + + default: + throw new \Exception(sprintf('Logistic regression has invalid cost function: %s.', $this->costFunction)); } } @@ -245,6 +256,7 @@ protected function output(array $sample) * Returns the class value (either -1 or 1) for the given input * * @param array $sample + * * @return int */ protected function outputClass(array $sample) @@ -266,6 +278,8 @@ protected function outputClass(array $sample) * * @param array $sample * @param mixed $label + * + * @return float */ protected function predictProbability(array $sample, $label) { diff --git a/src/Phpml/Classification/Linear/Perceptron.php b/src/Phpml/Classification/Linear/Perceptron.php index 91ffacf9..f4a8791f 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -63,22 +63,22 @@ class Perceptron implements Classifier, IncrementalEstimator /** * Initalize a perceptron classifier with given learning rate and maximum - * number of iterations used while training the perceptron
+ * number of iterations used while training the perceptron * - * Learning rate should be a float value between 0.0(exclusive) and 1.0(inclusive)
- * Maximum number of iterations can be an integer value greater than 0 - * @param int $learningRate - * @param int $maxIterations + * @param float $learningRate Value between 0.0(exclusive) and 1.0(inclusive) + * @param int $maxIterations Must be at least 1 + * @param bool $normalizeInputs + * + * @throws \Exception */ - public function __construct(float $learningRate = 0.001, int $maxIterations = 1000, - bool $normalizeInputs = true) + public function __construct(float $learningRate = 0.001, int $maxIterations = 1000, bool $normalizeInputs = true) { if ($learningRate <= 0.0 || $learningRate > 1.0) { throw new \Exception("Learning rate should be a float value between 0.0(exclusive) and 1.0(inclusive)"); } if ($maxIterations <= 0) { - throw new \Exception("Maximum number of iterations should be an integer greater than 0"); + throw new \Exception("Maximum number of iterations must be an integer greater than 0"); } if ($normalizeInputs) { @@ -96,7 +96,7 @@ public function __construct(float $learningRate = 0.001, int $maxIterations = 10 */ public function partialTrain(array $samples, array $targets, array $labels = []) { - return $this->trainByLabel($samples, $targets, $labels); + $this->trainByLabel($samples, $targets, $labels); } /** @@ -140,6 +140,8 @@ protected function resetBinary() * for $maxIterations times * * @param bool $enable + * + * @return $this */ public function setEarlyStop(bool $enable = true) { @@ -185,12 +187,14 @@ protected function runTraining(array $samples, array $targets) * Executes a Gradient Descent algorithm for * the given cost function * - * @param array $samples - * @param array $targets + * @param array $samples + * @param array $targets + * @param \Closure $gradientFunc + * @param bool $isBatch */ protected function runGradientDescent(array $samples, array $targets, \Closure $gradientFunc, bool $isBatch = false) { - $class = $isBatch ? GD::class : StochasticGD::class; + $class = $isBatch ? GD::class : StochasticGD::class; if (empty($this->optimizer)) { $this->optimizer = (new $class($this->featureCount)) @@ -262,6 +266,8 @@ protected function outputClass(array $sample) * * @param array $sample * @param mixed $label + * + * @return float */ protected function predictProbability(array $sample, $label) { @@ -277,6 +283,7 @@ protected function predictProbability(array $sample, $label) /** * @param array $sample + * * @return mixed */ protected function predictSampleBinary(array $sample) @@ -285,6 +292,6 @@ protected function predictSampleBinary(array $sample) $predictedClass = $this->outputClass($sample); - return $this->labels[ $predictedClass ]; + return $this->labels[$predictedClass]; } } diff --git a/src/Phpml/Classification/NaiveBayes.php b/src/Phpml/Classification/NaiveBayes.php index af81b00a..1a634da6 100644 --- a/src/Phpml/Classification/NaiveBayes.php +++ b/src/Phpml/Classification/NaiveBayes.php @@ -89,7 +89,7 @@ private function calculateStatistics($label, $samples) $this->mean[$label]= array_fill(0, $this->featureCount, 0); $this->dataType[$label] = array_fill(0, $this->featureCount, self::CONTINUOS); $this->discreteProb[$label] = array_fill(0, $this->featureCount, self::CONTINUOS); - for ($i=0; $i<$this->featureCount; $i++) { + for ($i = 0; $i < $this->featureCount; ++$i) { // Get the values of nth column in the samples array // Mean::arithmetic is called twice, can be optimized $values = array_column($samples, $i); @@ -114,16 +114,17 @@ private function calculateStatistics($label, $samples) /** * Calculates the probability P(label|sample_n) * - * @param array $sample - * @param int $feature + * @param array $sample + * @param int $feature * @param string $label + * * @return float */ private function sampleProbability($sample, $feature, $label) { $value = $sample[$feature]; if ($this->dataType[$label][$feature] == self::NOMINAL) { - if (! isset($this->discreteProb[$label][$feature][$value]) || + if (!isset($this->discreteProb[$label][$feature][$value]) || $this->discreteProb[$label][$feature][$value] == 0) { return self::EPSILON; } @@ -145,13 +146,15 @@ private function sampleProbability($sample, $feature, $label) /** * Return samples belonging to specific label + * * @param string $label + * * @return array */ private function getSamplesByLabel($label) { $samples = []; - for ($i=0; $i<$this->sampleCount; $i++) { + for ($i = 0; $i < $this->sampleCount; ++$i) { if ($this->targets[$i] == $label) { $samples[] = $this->samples[$i]; } @@ -171,12 +174,13 @@ protected function predictSample(array $sample) $predictions = []; foreach ($this->labels as $label) { $p = $this->p[$label]; - for ($i=0; $i<$this->featureCount; $i++) { + for ($i = 0; $i<$this->featureCount; ++$i) { $Plf = $this->sampleProbability($sample, $i, $label); $p += $Plf; } $predictions[$label] = $p; } + arsort($predictions, SORT_NUMERIC); reset($predictions); return key($predictions); diff --git a/src/Phpml/Clustering/FuzzyCMeans.php b/src/Phpml/Clustering/FuzzyCMeans.php index 424f2f15..c6a3c464 100644 --- a/src/Phpml/Clustering/FuzzyCMeans.php +++ b/src/Phpml/Clustering/FuzzyCMeans.php @@ -7,6 +7,7 @@ use Phpml\Clustering\KMeans\Point; use Phpml\Clustering\KMeans\Cluster; use Phpml\Clustering\KMeans\Space; +use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Distance\Euclidean; class FuzzyCMeans implements Clusterer @@ -25,10 +26,12 @@ class FuzzyCMeans implements Clusterer * @var Space */ private $space; + /** * @var array|float[][] */ private $membership; + /** * @var float */ @@ -56,6 +59,9 @@ class FuzzyCMeans implements Clusterer /** * @param int $clustersNumber + * @param float $fuzziness + * @param float $epsilon + * @param int $maxIterations * * @throws InvalidArgumentException */ @@ -86,14 +92,15 @@ protected function initClusters() protected function generateRandomMembership(int $rows, int $cols) { $this->membership = []; - for ($i=0; $i < $rows; $i++) { + for ($i = 0; $i < $rows; ++$i) { $row = []; $total = 0.0; - for ($k=0; $k < $cols; $k++) { + for ($k = 0; $k < $cols; ++$k) { $val = rand(1, 5) / 10.0; $row[] = $val; $total += $val; } + $this->membership[] = array_map(function ($val) use ($total) { return $val / $total; }, $row); @@ -103,21 +110,22 @@ protected function generateRandomMembership(int $rows, int $cols) protected function updateClusters() { $dim = $this->space->getDimension(); - if (! $this->clusters) { + if (!$this->clusters) { $this->clusters = []; - for ($i=0; $i<$this->clustersNumber; $i++) { + for ($i = 0; $i < $this->clustersNumber; ++$i) { $this->clusters[] = new Cluster($this->space, array_fill(0, $dim, 0.0)); } } - for ($i=0; $i<$this->clustersNumber; $i++) { + for ($i = 0; $i < $this->clustersNumber; ++$i) { $cluster = $this->clusters[$i]; $center = $cluster->getCoordinates(); - for ($k=0; $k<$dim; $k++) { + for ($k = 0; $k < $dim; ++$k) { $a = $this->getMembershipRowTotal($i, $k, true); $b = $this->getMembershipRowTotal($i, $k, false); $center[$k] = $a / $b; } + $cluster->setCoordinates($center); } } @@ -125,20 +133,22 @@ protected function updateClusters() protected function getMembershipRowTotal(int $row, int $col, bool $multiply) { $sum = 0.0; - for ($k = 0; $k < $this->sampleCount; $k++) { + for ($k = 0; $k < $this->sampleCount; ++$k) { $val = pow($this->membership[$row][$k], $this->fuzziness); if ($multiply) { $val *= $this->samples[$k][$col]; } + $sum += $val; } + return $sum; } protected function updateMembershipMatrix() { - for ($i = 0; $i < $this->clustersNumber; $i++) { - for ($k = 0; $k < $this->sampleCount; $k++) { + for ($i = 0; $i < $this->clustersNumber; ++$i) { + for ($k = 0; $k < $this->sampleCount; ++$k) { $distCalc = $this->getDistanceCalc($i, $k); $this->membership[$i][$k] = 1.0 / $distCalc; } @@ -157,11 +167,15 @@ protected function getDistanceCalc(int $row, int $col) $distance = new Euclidean(); $dist1 = $distance->distance( $this->clusters[$row]->getCoordinates(), - $this->samples[$col]); - for ($j = 0; $j < $this->clustersNumber; $j++) { + $this->samples[$col] + ); + + for ($j = 0; $j < $this->clustersNumber; ++$j) { $dist2 = $distance->distance( $this->clusters[$j]->getCoordinates(), - $this->samples[$col]); + $this->samples[$col] + ); + $val = pow($dist1 / $dist2, 2.0 / ($this->fuzziness - 1)); $sum += $val; } @@ -177,13 +191,14 @@ protected function getObjective() { $sum = 0.0; $distance = new Euclidean(); - for ($i = 0; $i < $this->clustersNumber; $i++) { + for ($i = 0; $i < $this->clustersNumber; ++$i) { $clust = $this->clusters[$i]->getCoordinates(); - for ($k = 0; $k < $this->sampleCount; $k++) { + for ($k = 0; $k < $this->sampleCount; ++$k) { $point = $this->samples[$k]; $sum += $distance->distance($clust, $point); } } + return $sum; } @@ -210,7 +225,6 @@ public function cluster(array $samples) // Our goal is minimizing the objective value while // executing the clustering steps at a maximum number of iterations $lastObjective = 0.0; - $difference = 0.0; $iterations = 0; do { // Update the membership matrix and cluster centers, respectively @@ -224,7 +238,7 @@ public function cluster(array $samples) } while ($difference > $this->epsilon && $iterations++ <= $this->maxIterations); // Attach (hard cluster) each data point to the nearest cluster - for ($k=0; $k<$this->sampleCount; $k++) { + for ($k = 0; $k < $this->sampleCount; ++$k) { $column = array_column($this->membership, $k); arsort($column); reset($column); @@ -238,6 +252,7 @@ public function cluster(array $samples) foreach ($this->clusters as $cluster) { $grouped[] = $cluster->getPoints(); } + return $grouped; } } diff --git a/src/Phpml/Clustering/KMeans/Space.php b/src/Phpml/Clustering/KMeans/Space.php index 5a4d5305..0276880d 100644 --- a/src/Phpml/Clustering/KMeans/Space.php +++ b/src/Phpml/Clustering/KMeans/Space.php @@ -156,7 +156,11 @@ protected function initializeClusters(int $clustersNumber, int $initMethod) case KMeans::INIT_KMEANS_PLUS_PLUS: $clusters = $this->initializeKMPPClusters($clustersNumber); break; + + default: + return []; } + $clusters[0]->attachAll($this); return $clusters; diff --git a/src/Phpml/Dataset/CsvDataset.php b/src/Phpml/Dataset/CsvDataset.php index 8bcd3c49..b2e94077 100644 --- a/src/Phpml/Dataset/CsvDataset.php +++ b/src/Phpml/Dataset/CsvDataset.php @@ -17,6 +17,7 @@ class CsvDataset extends ArrayDataset * @param string $filepath * @param int $features * @param bool $headingRow + * @param string $delimiter * * @throws FileException */ @@ -37,11 +38,15 @@ public function __construct(string $filepath, int $features, bool $headingRow = $this->columnNames = range(0, $features - 1); } + $samples = $targets = []; while (($data = fgetcsv($handle, 1000, $delimiter)) !== false) { - $this->samples[] = array_slice($data, 0, $features); - $this->targets[] = $data[$features]; + $samples[] = array_slice($data, 0, $features); + $targets[] = $data[$features]; } + fclose($handle); + + parent::__construct($samples, $targets); } /** diff --git a/src/Phpml/DimensionReduction/EigenTransformerBase.php b/src/Phpml/DimensionReduction/EigenTransformerBase.php index b3990024..6c0ef05f 100644 --- a/src/Phpml/DimensionReduction/EigenTransformerBase.php +++ b/src/Phpml/DimensionReduction/EigenTransformerBase.php @@ -1,4 +1,6 @@ -calculateKernelMatrix($this->data, $numRows); $matrix = $this->centerMatrix($matrix, $numRows); - $this->eigenDecomposition($matrix, $numRows); + $this->eigenDecomposition($matrix); $this->fit = true; @@ -98,7 +98,7 @@ public function fit(array $data) * An n-by-m matrix is given and an n-by-n matrix is returned * * @param array $data - * @param int $numRows + * @param int $numRows * * @return array */ @@ -107,8 +107,8 @@ protected function calculateKernelMatrix(array $data, int $numRows) $kernelFunc = $this->getKernel(); $matrix = []; - for ($i=0; $i < $numRows; $i++) { - for ($k=0; $k < $numRows; $k++) { + for ($i = 0; $i < $numRows; ++$i) { + for ($k = 0; $k < $numRows; ++$k) { if ($i <= $k) { $matrix[$i][$k] = $kernelFunc($data[$i], $data[$k]); } else { @@ -127,7 +127,9 @@ protected function calculateKernelMatrix(array $data, int $numRows) * K′ = K − N.K − K.N + N.K.N where N is n-by-n matrix filled with 1/n * * @param array $matrix - * @param int $n + * @param int $n + * + * @return array */ protected function centerMatrix(array $matrix, int $n) { @@ -152,6 +154,8 @@ protected function centerMatrix(array $matrix, int $n) * Returns the callable kernel function * * @return \Closure + * + * @throws \Exception */ protected function getKernel() { @@ -181,6 +185,9 @@ protected function getKernel() return function ($x, $y) use ($dist) { return exp(-$this->gamma * $dist->distance($x, $y)); }; + + default: + throw new \Exception(sprintf('KernelPCA initialized with invalid kernel: %d', $this->kernel)); } } @@ -228,6 +235,8 @@ protected function projectSample(array $pairs) * @param array $sample * * @return array + * + * @throws \Exception */ public function transform(array $sample) { diff --git a/src/Phpml/DimensionReduction/LDA.php b/src/Phpml/DimensionReduction/LDA.php index 28f34d65..e094c357 100644 --- a/src/Phpml/DimensionReduction/LDA.php +++ b/src/Phpml/DimensionReduction/LDA.php @@ -4,7 +4,6 @@ namespace Phpml\DimensionReduction; -use Phpml\Math\Statistic\Mean; use Phpml\Math\Matrix; class LDA extends EigenTransformerBase @@ -30,7 +29,7 @@ class LDA extends EigenTransformerBase public $counts; /** - * @var float + * @var float[] */ public $overallMean; @@ -111,12 +110,12 @@ protected function getLabels(array $classes): array * Calculates mean of each column for each class and returns * n by m matrix where n is number of labels and m is number of columns * - * @param type $data - * @param type $classes + * @param array $data + * @param array $classes * * @return array */ - protected function calculateMeans($data, $classes) : array + protected function calculateMeans(array $data, array $classes) : array { $means = []; $counts= []; @@ -126,17 +125,18 @@ protected function calculateMeans($data, $classes) : array $label = array_search($classes[$index], $this->labels); foreach ($row as $col => $val) { - if (! isset($means[$label][$col])) { + if (!isset($means[$label][$col])) { $means[$label][$col] = 0.0; } $means[$label][$col] += $val; $overallMean[$col] += $val; } - if (! isset($counts[$label])) { + if (!isset($counts[$label])) { $counts[$label] = 0; } - $counts[$label]++; + + ++$counts[$label]; } foreach ($means as $index => $row) { @@ -231,6 +231,8 @@ protected function calculateVar(array $row, array $means) * @param array $sample * * @return array + * + * @throws \Exception */ public function transform(array $sample) { @@ -238,7 +240,7 @@ public function transform(array $sample) throw new \Exception("LDA has not been fitted with respect to original dataset, please run LDA::fit() first"); } - if (! is_array($sample[0])) { + if (!is_array($sample[0])) { $sample = [$sample]; } diff --git a/src/Phpml/DimensionReduction/PCA.php b/src/Phpml/DimensionReduction/PCA.php index db2110db..acaa8e01 100644 --- a/src/Phpml/DimensionReduction/PCA.php +++ b/src/Phpml/DimensionReduction/PCA.php @@ -6,7 +6,6 @@ use Phpml\Math\Statistic\Covariance; use Phpml\Math\Statistic\Mean; -use Phpml\Math\Matrix; class PCA extends EigenTransformerBase { @@ -86,7 +85,7 @@ protected function calculateMeans(array $data, int $n) { // Calculate means for each dimension $this->means = []; - for ($i=0; $i < $n; $i++) { + for ($i = 0; $i < $n; ++$i) { $column = array_column($data, $i); $this->means[] = Mean::arithmetic($column); } @@ -97,7 +96,7 @@ protected function calculateMeans(array $data, int $n) * each dimension therefore dimensions will be centered to zero * * @param array $data - * @param int $n + * @param int $n * * @return array */ @@ -109,7 +108,7 @@ protected function normalize(array $data, int $n) // Normalize data foreach ($data as $i => $row) { - for ($k=0; $k < $n; $k++) { + for ($k = 0; $k < $n; ++$k) { $data[$i][$k] -= $this->means[$k]; } } @@ -124,6 +123,8 @@ protected function normalize(array $data, int $n) * @param array $sample * * @return array + * + * @throws \Exception */ public function transform(array $sample) { @@ -131,7 +132,7 @@ public function transform(array $sample) throw new \Exception("PCA has not been fitted with respect to original dataset, please run PCA::fit() first"); } - if (! is_array($sample[0])) { + if (!is_array($sample[0])) { $sample = [$sample]; } diff --git a/src/Phpml/Exception/DatasetException.php b/src/Phpml/Exception/DatasetException.php index 60920536..ca7b0656 100644 --- a/src/Phpml/Exception/DatasetException.php +++ b/src/Phpml/Exception/DatasetException.php @@ -6,7 +6,6 @@ class DatasetException extends \Exception { - /** * @param string $path * diff --git a/src/Phpml/Exception/FileException.php b/src/Phpml/Exception/FileException.php index 558ae48a..20b29360 100644 --- a/src/Phpml/Exception/FileException.php +++ b/src/Phpml/Exception/FileException.php @@ -6,7 +6,6 @@ class FileException extends \Exception { - /** * @param string $filepath * diff --git a/src/Phpml/Exception/InvalidArgumentException.php b/src/Phpml/Exception/InvalidArgumentException.php index f6b0031a..bf6d8ccf 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -11,7 +11,7 @@ class InvalidArgumentException extends \Exception */ public static function arraySizeNotMatch() { - return new self('Size of given arrays not match'); + return new self('Size of given arrays does not match'); } /** @@ -55,7 +55,7 @@ public static function matrixDimensionsDidNotMatch() */ public static function inconsistentMatrixSupplied() { - return new self('Inconsistent matrix applied'); + return new self('Inconsistent matrix supplied'); } /** diff --git a/src/Phpml/Exception/SerializeException.php b/src/Phpml/Exception/SerializeException.php index 70e68925..5753eb7e 100644 --- a/src/Phpml/Exception/SerializeException.php +++ b/src/Phpml/Exception/SerializeException.php @@ -6,7 +6,6 @@ class SerializeException extends \Exception { - /** * @param string $filepath * diff --git a/src/Phpml/Helper/OneVsRest.php b/src/Phpml/Helper/OneVsRest.php index e207c46b..8d71fbcb 100644 --- a/src/Phpml/Helper/OneVsRest.php +++ b/src/Phpml/Helper/OneVsRest.php @@ -6,7 +6,6 @@ trait OneVsRest { - /** * @var array */ @@ -35,18 +34,18 @@ public function train(array $samples, array $targets) // Clears previous stuff. $this->reset(); - return $this->trainBylabel($samples, $targets); + $this->trainBylabel($samples, $targets); } /** * @param array $samples * @param array $targets * @param array $allLabels All training set labels + * * @return void */ protected function trainByLabel(array $samples, array $targets, array $allLabels = []) { - // Overwrites the current value if it exist. $allLabels must be provided for each partialTrain run. if (!empty($allLabels)) { $this->allLabels = $allLabels; @@ -57,7 +56,6 @@ protected function trainByLabel(array $samples, array $targets, array $allLabels // If there are only two targets, then there is no need to perform OvR if (count($this->allLabels) == 2) { - // Init classifier if required. if (empty($this->classifiers)) { $this->classifiers[0] = $this->getClassifierCopy(); @@ -68,7 +66,6 @@ protected function trainByLabel(array $samples, array $targets, array $allLabels // Train a separate classifier for each label and memorize them foreach ($this->allLabels as $label) { - // Init classifier if required. if (empty($this->classifiers[$label])) { $this->classifiers[$label] = $this->getClassifierCopy(); @@ -107,7 +104,6 @@ public function reset() */ protected function getClassifierCopy() { - // Clone the current classifier, so that // we don't mess up its variables while training // multiple instances of this classifier @@ -180,7 +176,8 @@ abstract protected function resetBinary(); * Each classifier that make use of OvR approach should be able to * return a probability for a sample to belong to the given label. * - * @param array $sample + * @param array $sample + * @param string $label * * @return mixed */ diff --git a/src/Phpml/Helper/Optimizer/ConjugateGradient.php b/src/Phpml/Helper/Optimizer/ConjugateGradient.php index 18ae89a0..44bcd14c 100644 --- a/src/Phpml/Helper/Optimizer/ConjugateGradient.php +++ b/src/Phpml/Helper/Optimizer/ConjugateGradient.php @@ -18,8 +18,8 @@ class ConjugateGradient extends GD { /** - * @param array $samples - * @param array $targets + * @param array $samples + * @param array $targets * @param \Closure $gradientCb * * @return array @@ -34,7 +34,7 @@ public function runOptimization(array $samples, array $targets, \Closure $gradie $d = mp::muls($this->gradient($this->theta), -1); - for ($i=0; $i < $this->maxIterations; $i++) { + for ($i = 0; $i < $this->maxIterations; ++$i) { // Obtain α that minimizes f(θ + α.d) $alpha = $this->getAlpha(array_sum($d)); @@ -68,11 +68,11 @@ public function runOptimization(array $samples, array $targets, \Closure $gradie * * @param array $theta * - * @return float + * @return array */ protected function gradient(array $theta) { - list($_, $gradient, $_) = parent::gradient($theta); + list(, $gradient) = parent::gradient($theta); return $gradient; } @@ -86,7 +86,7 @@ protected function gradient(array $theta) */ protected function cost(array $theta) { - list($cost, $_, $_) = parent::gradient($theta); + list($cost) = parent::gradient($theta); return array_sum($cost) / $this->sampleCount; } @@ -107,7 +107,7 @@ protected function cost(array $theta) * * @param float $d * - * @return array + * @return float */ protected function getAlpha(float $d) { @@ -157,14 +157,14 @@ protected function getAlpha(float $d) * @param float $alpha * @param array $d * - * return array + * @return array */ protected function getNewTheta(float $alpha, array $d) { $theta = $this->theta; - for ($i=0; $i < $this->dimensions + 1; $i++) { - if ($i == 0) { + for ($i = 0; $i < $this->dimensions + 1; ++$i) { + if ($i === 0) { $theta[$i] += $alpha * array_sum($d); } else { $sum = 0.0; @@ -266,10 +266,11 @@ public static function div(array $m1, array $m2) * * @param array $m1 * @param array $m2 + * @param int $mag * * @return array */ - public static function add(array $m1, array $m2, $mag = 1) + public static function add(array $m1, array $m2, int $mag = 1) { $res = []; foreach ($m1 as $i => $val) { @@ -333,10 +334,11 @@ public static function divs(array $m1, float $m2) * * @param array $m1 * @param float $m2 + * @param int $mag * * @return array */ - public static function adds(array $m1, float $m2, $mag = 1) + public static function adds(array $m1, float $m2, int $mag = 1) { $res = []; foreach ($m1 as $val) { @@ -350,7 +352,7 @@ public static function adds(array $m1, float $m2, $mag = 1) * Element-wise subtraction of a vector with a scalar * * @param array $m1 - * @param float $m2 + * @param array $m2 * * @return array */ diff --git a/src/Phpml/Helper/Optimizer/GD.php b/src/Phpml/Helper/Optimizer/GD.php index 8974c8e7..b88b0c7c 100644 --- a/src/Phpml/Helper/Optimizer/GD.php +++ b/src/Phpml/Helper/Optimizer/GD.php @@ -18,8 +18,8 @@ class GD extends StochasticGD protected $sampleCount = null; /** - * @param array $samples - * @param array $targets + * @param array $samples + * @param array $targets * @param \Closure $gradientCb * * @return array @@ -75,7 +75,7 @@ protected function gradient(array $theta) list($cost, $grad, $penalty) = array_pad($result, 3, 0); $costs[] = $cost; - $gradient[]= $grad; + $gradient[] = $grad; $totalPenalty += $penalty; } @@ -91,8 +91,8 @@ protected function gradient(array $theta) protected function updateWeightsWithUpdates(array $updates, float $penalty) { // Updates all weights at once - for ($i=0; $i <= $this->dimensions; $i++) { - if ($i == 0) { + for ($i = 0; $i <= $this->dimensions; ++$i) { + if ($i === 0) { $this->theta[0] -= $this->learningRate * array_sum($updates); } else { $col = array_column($this->samples, $i - 1); diff --git a/src/Phpml/Helper/Optimizer/Optimizer.php b/src/Phpml/Helper/Optimizer/Optimizer.php index 9ef4c4d0..09668a95 100644 --- a/src/Phpml/Helper/Optimizer/Optimizer.php +++ b/src/Phpml/Helper/Optimizer/Optimizer.php @@ -31,7 +31,7 @@ public function __construct(int $dimensions) // Inits the weights randomly $this->theta = []; - for ($i=0; $i < $this->dimensions; $i++) { + for ($i = 0; $i < $this->dimensions; ++$i) { $this->theta[] = rand() / (float) getrandmax(); } } @@ -40,6 +40,10 @@ public function __construct(int $dimensions) * Sets the weights manually * * @param array $theta + * + * @return $this + * + * @throws \Exception */ public function setInitialTheta(array $theta) { @@ -56,6 +60,9 @@ public function setInitialTheta(array $theta) * Executes the optimization with the given samples & targets * and returns the weights * + * @param array $samples + * @param array $targets + * @param \Closure $gradientCb */ abstract protected function runOptimization(array $samples, array $targets, \Closure $gradientCb); } diff --git a/src/Phpml/Helper/Optimizer/StochasticGD.php b/src/Phpml/Helper/Optimizer/StochasticGD.php index e9e318a8..fa2401a4 100644 --- a/src/Phpml/Helper/Optimizer/StochasticGD.php +++ b/src/Phpml/Helper/Optimizer/StochasticGD.php @@ -166,7 +166,6 @@ public function runOptimization(array $samples, array $targets, \Closure $gradie $currIter = 0; $bestTheta = null; $bestScore = 0.0; - $bestWeightIter = 0; $this->costValues = []; while ($this->maxIterations > $currIter++) { @@ -180,7 +179,6 @@ public function runOptimization(array $samples, array $targets, \Closure $gradie if ($bestTheta == null || $cost <= $bestScore) { $bestTheta = $theta; $bestScore = $cost; - $bestWeightIter = $currIter; } // Add the cost value for this iteration to the list @@ -218,7 +216,7 @@ protected function updateTheta() $this->theta[0] -= $this->learningRate * $gradient; // Update other values - for ($i=1; $i <= $this->dimensions; $i++) { + for ($i = 1; $i <= $this->dimensions; ++$i) { $this->theta[$i] -= $this->learningRate * ($gradient * $sample[$i - 1] + $penalty * $this->theta[$i]); } diff --git a/src/Phpml/Helper/Predictable.php b/src/Phpml/Helper/Predictable.php index 097edaab..2ef90177 100644 --- a/src/Phpml/Helper/Predictable.php +++ b/src/Phpml/Helper/Predictable.php @@ -14,12 +14,12 @@ trait Predictable public function predict(array $samples) { if (!is_array($samples[0])) { - $predicted = $this->predictSample($samples); - } else { - $predicted = []; - foreach ($samples as $index => $sample) { - $predicted[$index] = $this->predictSample($sample); - } + return $this->predictSample($samples); + } + + $predicted = []; + foreach ($samples as $index => $sample) { + $predicted[$index] = $this->predictSample($sample); } return $predicted; diff --git a/src/Phpml/IncrementalEstimator.php b/src/Phpml/IncrementalEstimator.php index fc6912d1..4a0d1ccb 100644 --- a/src/Phpml/IncrementalEstimator.php +++ b/src/Phpml/IncrementalEstimator.php @@ -6,7 +6,6 @@ interface IncrementalEstimator { - /** * @param array $samples * @param array $targets diff --git a/src/Phpml/Math/Kernel/RBF.php b/src/Phpml/Math/Kernel/RBF.php index 8ca7d84b..2cd92db2 100644 --- a/src/Phpml/Math/Kernel/RBF.php +++ b/src/Phpml/Math/Kernel/RBF.php @@ -23,8 +23,8 @@ public function __construct(float $gamma) } /** - * @param float $a - * @param float $b + * @param array $a + * @param array $b * * @return float */ diff --git a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php index 5cbc1212..7f0ec4ba 100644 --- a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -33,7 +33,6 @@ class EigenvalueDecomposition { - /** * Row and column dimension (square matrix). * @var int @@ -42,9 +41,9 @@ class EigenvalueDecomposition /** * Internal symmetry flag. - * @var int + * @var bool */ - private $issymmetric; + private $symmetric; /** * Arrays for internal storage of eigenvalues. @@ -78,6 +77,38 @@ class EigenvalueDecomposition private $cdivr; private $cdivi; + /** + * Constructor: Check for symmetry, then construct the eigenvalue decomposition + * + * @param array $Arg + */ + public function __construct(array $Arg) + { + $this->A = $Arg; + $this->n = count($Arg[0]); + $this->symmetric = true; + + for ($j = 0; ($j < $this->n) && $this->symmetric; ++$j) { + for ($i = 0; ($i < $this->n) & $this->symmetric; ++$i) { + $this->symmetric = ($this->A[$i][$j] == $this->A[$j][$i]); + } + } + + if ($this->symmetric) { + $this->V = $this->A; + // Tridiagonalize. + $this->tred2(); + // Diagonalize. + $this->tql2(); + } else { + $this->H = $this->A; + $this->ort = []; + // Reduce to Hessenberg form. + $this->orthes(); + // Reduce Hessenberg to real Schur form. + $this->hqr2(); + } + } /** * Symmetric Householder reduction to tridiagonal form. @@ -88,10 +119,10 @@ private function tred2() // Bowdler, Martin, Reinsch, and Wilkinson, Handbook for // Auto. Comp., Vol.ii-Linear Algebra, and the corresponding // Fortran subroutine in EISPACK. - $this->d = $this->V[$this->n-1]; + $this->d = $this->V[$this->n - 1]; // Householder reduction to tridiagonal form. - for ($i = $this->n-1; $i > 0; --$i) { - $i_ = $i -1; + for ($i = $this->n - 1; $i > 0; --$i) { + $i_ = $i - 1; // Scale to avoid under/overflow. $h = $scale = 0.0; $scale += array_sum(array_map('abs', $this->d)); @@ -107,14 +138,17 @@ private function tred2() $this->d[$k] /= $scale; $h += pow($this->d[$k], 2); } + $f = $this->d[$i_]; $g = sqrt($h); if ($f > 0) { $g = -$g; } + $this->e[$i] = $scale * $g; $h = $h - $f * $g; $this->d[$i_] = $f - $g; + for ($j = 0; $j < $i; ++$j) { $this->e[$j] = 0.0; } @@ -123,22 +157,26 @@ private function tred2() $f = $this->d[$j]; $this->V[$j][$i] = $f; $g = $this->e[$j] + $this->V[$j][$j] * $f; - for ($k = $j+1; $k <= $i_; ++$k) { + + for ($k = $j + 1; $k <= $i_; ++$k) { $g += $this->V[$k][$j] * $this->d[$k]; $this->e[$k] += $this->V[$k][$j] * $f; } $this->e[$j] = $g; } + $f = 0.0; if ($h === 0 || $h < 1e-32) { $h = 1e-32; } + for ($j = 0; $j < $i; ++$j) { $this->e[$j] /= $h; $f += $this->e[$j] * $this->d[$j]; } + $hh = $f / (2 * $h); - for ($j=0; $j < $i; ++$j) { + for ($j = 0; $j < $i; ++$j) { $this->e[$j] -= $hh * $this->d[$j]; } for ($j = 0; $j < $i; ++$j) { @@ -147,7 +185,7 @@ private function tred2() for ($k = $j; $k <= $i_; ++$k) { $this->V[$k][$j] -= ($f * $this->e[$k] + $g * $this->d[$k]); } - $this->d[$j] = $this->V[$i-1][$j]; + $this->d[$j] = $this->V[$i - 1][$j]; $this->V[$i][$j] = 0.0; } } @@ -155,18 +193,18 @@ private function tred2() } // Accumulate transformations. - for ($i = 0; $i < $this->n-1; ++$i) { - $this->V[$this->n-1][$i] = $this->V[$i][$i]; + for ($i = 0; $i < $this->n - 1; ++$i) { + $this->V[$this->n - 1][$i] = $this->V[$i][$i]; $this->V[$i][$i] = 1.0; - $h = $this->d[$i+1]; + $h = $this->d[$i + 1]; if ($h != 0.0) { for ($k = 0; $k <= $i; ++$k) { - $this->d[$k] = $this->V[$k][$i+1] / $h; + $this->d[$k] = $this->V[$k][$i + 1] / $h; } for ($j = 0; $j <= $i; ++$j) { $g = 0.0; for ($k = 0; $k <= $i; ++$k) { - $g += $this->V[$k][$i+1] * $this->V[$k][$j]; + $g += $this->V[$k][$i + 1] * $this->V[$k][$j]; } for ($k = 0; $k <= $i; ++$k) { $this->V[$k][$j] -= $g * $this->d[$k]; @@ -174,13 +212,13 @@ private function tred2() } } for ($k = 0; $k <= $i; ++$k) { - $this->V[$k][$i+1] = 0.0; + $this->V[$k][$i + 1] = 0.0; } } - $this->d = $this->V[$this->n-1]; - $this->V[$this->n-1] = array_fill(0, $j, 0.0); - $this->V[$this->n-1][$this->n-1] = 1.0; + $this->d = $this->V[$this->n - 1]; + $this->V[$this->n - 1] = array_fill(0, $j, 0.0); + $this->V[$this->n - 1][$this->n - 1] = 1.0; $this->e[0] = 0.0; } @@ -196,9 +234,9 @@ private function tred2() private function tql2() { for ($i = 1; $i < $this->n; ++$i) { - $this->e[$i-1] = $this->e[$i]; + $this->e[$i - 1] = $this->e[$i]; } - $this->e[$this->n-1] = 0.0; + $this->e[$this->n - 1] = 0.0; $f = 0.0; $tst1 = 0.0; $eps = pow(2.0, -52.0); @@ -222,14 +260,14 @@ private function tql2() $iter += 1; // Compute implicit shift $g = $this->d[$l]; - $p = ($this->d[$l+1] - $g) / (2.0 * $this->e[$l]); + $p = ($this->d[$l + 1] - $g) / (2.0 * $this->e[$l]); $r = hypot($p, 1.0); if ($p < 0) { $r *= -1; } $this->d[$l] = $this->e[$l] / ($p + $r); - $this->d[$l+1] = $this->e[$l] * ($p + $r); - $dl1 = $this->d[$l+1]; + $this->d[$l + 1] = $this->e[$l] * ($p + $r); + $dl1 = $this->d[$l + 1]; $h = $g - $this->d[$l]; for ($i = $l + 2; $i < $this->n; ++$i) { $this->d[$i] -= $h; @@ -241,23 +279,23 @@ private function tql2() $c2 = $c3 = $c; $el1 = $this->e[$l + 1]; $s = $s2 = 0.0; - for ($i = $m-1; $i >= $l; --$i) { + for ($i = $m - 1; $i >= $l; --$i) { $c3 = $c2; $c2 = $c; $s2 = $s; $g = $c * $this->e[$i]; $h = $c * $p; $r = hypot($p, $this->e[$i]); - $this->e[$i+1] = $s * $r; + $this->e[$i + 1] = $s * $r; $s = $this->e[$i] / $r; $c = $p / $r; $p = $c * $this->d[$i] - $s * $g; - $this->d[$i+1] = $h + $s * ($c * $g + $s * $this->d[$i]); + $this->d[$i + 1] = $h + $s * ($c * $g + $s * $this->d[$i]); // Accumulate transformation. for ($k = 0; $k < $this->n; ++$k) { - $h = $this->V[$k][$i+1]; - $this->V[$k][$i+1] = $s * $this->V[$k][$i] + $c * $h; - $this->V[$k][$i] = $c * $this->V[$k][$i] - $s * $h; + $h = $this->V[$k][$i + 1]; + $this->V[$k][$i + 1] = $s * $this->V[$k][$i] + $c * $h; + $this->V[$k][$i] = $c * $this->V[$k][$i] - $s * $h; } } $p = -$s * $s2 * $c3 * $el1 * $this->e[$l] / $dl1; @@ -274,7 +312,7 @@ private function tql2() for ($i = 0; $i < $this->n - 1; ++$i) { $k = $i; $p = $this->d[$i]; - for ($j = $i+1; $j < $this->n; ++$j) { + for ($j = $i + 1; $j < $this->n; ++$j) { if ($this->d[$j] < $p) { $k = $j; $p = $this->d[$j]; @@ -304,19 +342,19 @@ private function tql2() private function orthes() { $low = 0; - $high = $this->n-1; + $high = $this->n - 1; - for ($m = $low+1; $m <= $high-1; ++$m) { + for ($m = $low + 1; $m <= $high - 1; ++$m) { // Scale column. $scale = 0.0; for ($i = $m; $i <= $high; ++$i) { - $scale = $scale + abs($this->H[$i][$m-1]); + $scale = $scale + abs($this->H[$i][$m - 1]); } if ($scale != 0.0) { // Compute Householder transformation. $h = 0.0; for ($i = $high; $i >= $m; --$i) { - $this->ort[$i] = $this->H[$i][$m-1] / $scale; + $this->ort[$i] = $this->H[$i][$m - 1] / $scale; $h += $this->ort[$i] * $this->ort[$i]; } $g = sqrt($h); @@ -348,7 +386,7 @@ private function orthes() } } $this->ort[$m] = $scale * $this->ort[$m]; - $this->H[$m][$m-1] = $scale * $g; + $this->H[$m][$m - 1] = $scale * $g; } } @@ -358,10 +396,10 @@ private function orthes() $this->V[$i][$j] = ($i == $j ? 1.0 : 0.0); } } - for ($m = $high-1; $m >= $low+1; --$m) { - if ($this->H[$m][$m-1] != 0.0) { - for ($i = $m+1; $i <= $high; ++$i) { - $this->ort[$i] = $this->H[$i][$m-1]; + for ($m = $high - 1; $m >= $low + 1; --$m) { + if ($this->H[$m][$m - 1] != 0.0) { + for ($i = $m + 1; $i <= $high; ++$i) { + $this->ort[$i] = $this->H[$i][$m - 1]; } for ($j = $m; $j <= $high; ++$j) { $g = 0.0; @@ -369,7 +407,7 @@ private function orthes() $g += $this->ort[$i] * $this->V[$i][$j]; } // Double division avoids possible underflow - $g = ($g / $this->ort[$m]) / $this->H[$m][$m-1]; + $g = ($g / $this->ort[$m]) / $this->H[$m][$m - 1]; for ($i = $m; $i <= $high; ++$i) { $this->V[$i][$j] += $g * $this->ort[$i]; } @@ -378,9 +416,13 @@ private function orthes() } } - /** - * Performs complex division. + * Performs complex division. + * + * @param int|float $xr + * @param int|float $xi + * @param int|float $yr + * @param int|float $yi */ private function cdiv($xr, $xi, $yr, $yi) { @@ -397,7 +439,6 @@ private function cdiv($xr, $xi, $yr, $yi) } } - /** * Nonsymmetric reduction from Hessenberg to real Schur form. * @@ -424,7 +465,7 @@ private function hqr2() $this->d[$i] = $this->H[$i][$i]; $this->e[$i] = 0.0; } - for ($j = max($i-1, 0); $j < $nn; ++$j) { + for ($j = max($i - 1, 0); $j < $nn; ++$j) { $norm = $norm + abs($this->H[$i][$j]); } } @@ -435,11 +476,11 @@ private function hqr2() // Look for single small sub-diagonal element $l = $n; while ($l > $low) { - $s = abs($this->H[$l-1][$l-1]) + abs($this->H[$l][$l]); + $s = abs($this->H[$l - 1][$l - 1]) + abs($this->H[$l][$l]); if ($s == 0.0) { $s = $norm; } - if (abs($this->H[$l][$l-1]) < $eps * $s) { + if (abs($this->H[$l][$l - 1]) < $eps * $s) { break; } --$l; @@ -453,13 +494,13 @@ private function hqr2() --$n; $iter = 0; // Two roots found - } elseif ($l == $n-1) { - $w = $this->H[$n][$n-1] * $this->H[$n-1][$n]; - $p = ($this->H[$n-1][$n-1] - $this->H[$n][$n]) / 2.0; + } elseif ($l == $n - 1) { + $w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n]; + $p = ($this->H[$n - 1][$n - 1] - $this->H[$n][$n]) / 2.0; $q = $p * $p + $w; $z = sqrt(abs($q)); $this->H[$n][$n] = $this->H[$n][$n] + $exshift; - $this->H[$n-1][$n-1] = $this->H[$n-1][$n-1] + $exshift; + $this->H[$n - 1][$n - 1] = $this->H[$n - 1][$n - 1] + $exshift; $x = $this->H[$n][$n]; // Real pair if ($q >= 0) { @@ -468,14 +509,14 @@ private function hqr2() } else { $z = $p - $z; } - $this->d[$n-1] = $x + $z; - $this->d[$n] = $this->d[$n-1]; + $this->d[$n - 1] = $x + $z; + $this->d[$n] = $this->d[$n - 1]; if ($z != 0.0) { $this->d[$n] = $x - $w / $z; } - $this->e[$n-1] = 0.0; + $this->e[$n - 1] = 0.0; $this->e[$n] = 0.0; - $x = $this->H[$n][$n-1]; + $x = $this->H[$n][$n - 1]; $s = abs($x) + abs($z); $p = $x / $s; $q = $z / $s; @@ -483,29 +524,29 @@ private function hqr2() $p = $p / $r; $q = $q / $r; // Row modification - for ($j = $n-1; $j < $nn; ++$j) { - $z = $this->H[$n-1][$j]; - $this->H[$n-1][$j] = $q * $z + $p * $this->H[$n][$j]; + for ($j = $n - 1; $j < $nn; ++$j) { + $z = $this->H[$n - 1][$j]; + $this->H[$n - 1][$j] = $q * $z + $p * $this->H[$n][$j]; $this->H[$n][$j] = $q * $this->H[$n][$j] - $p * $z; } // Column modification for ($i = 0; $i <= $n; ++$i) { - $z = $this->H[$i][$n-1]; - $this->H[$i][$n-1] = $q * $z + $p * $this->H[$i][$n]; + $z = $this->H[$i][$n - 1]; + $this->H[$i][$n - 1] = $q * $z + $p * $this->H[$i][$n]; $this->H[$i][$n] = $q * $this->H[$i][$n] - $p * $z; } // Accumulate transformations for ($i = $low; $i <= $high; ++$i) { - $z = $this->V[$i][$n-1]; - $this->V[$i][$n-1] = $q * $z + $p * $this->V[$i][$n]; + $z = $this->V[$i][$n - 1]; + $this->V[$i][$n - 1] = $q * $z + $p * $this->V[$i][$n]; $this->V[$i][$n] = $q * $this->V[$i][$n] - $p * $z; } // Complex pair } else { - $this->d[$n-1] = $x + $p; - $this->d[$n] = $x + $p; - $this->e[$n-1] = $z; - $this->e[$n] = -$z; + $this->d[$n - 1] = $x + $p; + $this->d[$n] = $x + $p; + $this->e[$n - 1] = $z; + $this->e[$n] = -$z; } $n = $n - 2; $iter = 0; @@ -516,8 +557,8 @@ private function hqr2() $y = 0.0; $w = 0.0; if ($l < $n) { - $y = $this->H[$n-1][$n-1]; - $w = $this->H[$n][$n-1] * $this->H[$n-1][$n]; + $y = $this->H[$n - 1][$n - 1]; + $w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n]; } // Wilkinson's original ad hoc shift if ($iter == 10) { @@ -525,7 +566,7 @@ private function hqr2() for ($i = $low; $i <= $n; ++$i) { $this->H[$i][$i] -= $x; } - $s = abs($this->H[$n][$n-1]) + abs($this->H[$n-1][$n-2]); + $s = abs($this->H[$n][$n - 1]) + abs($this->H[$n - 1][$n - 2]); $x = $y = 0.75 * $s; $w = -0.4375 * $s * $s; } @@ -554,9 +595,9 @@ private function hqr2() $z = $this->H[$m][$m]; $r = $x - $z; $s = $y - $z; - $p = ($r * $s - $w) / $this->H[$m+1][$m] + $this->H[$m][$m+1]; - $q = $this->H[$m+1][$m+1] - $z - $r - $s; - $r = $this->H[$m+2][$m+1]; + $p = ($r * $s - $w) / $this->H[$m + 1][$m] + $this->H[$m][$m + 1]; + $q = $this->H[$m + 1][$m + 1] - $z - $r - $s; + $r = $this->H[$m + 2][$m + 1]; $s = abs($p) + abs($q) + abs($r); $p = $p / $s; $q = $q / $s; @@ -564,25 +605,25 @@ private function hqr2() if ($m == $l) { break; } - if (abs($this->H[$m][$m-1]) * (abs($q) + abs($r)) < - $eps * (abs($p) * (abs($this->H[$m-1][$m-1]) + abs($z) + abs($this->H[$m+1][$m+1])))) { + if (abs($this->H[$m][$m - 1]) * (abs($q) + abs($r)) < + $eps * (abs($p) * (abs($this->H[$m - 1][$m - 1]) + abs($z) + abs($this->H[$m + 1][$m + 1])))) { break; } --$m; } for ($i = $m + 2; $i <= $n; ++$i) { - $this->H[$i][$i-2] = 0.0; - if ($i > $m+2) { - $this->H[$i][$i-3] = 0.0; + $this->H[$i][$i - 2] = 0.0; + if ($i > $m + 2) { + $this->H[$i][$i - 3] = 0.0; } } // Double QR step involving rows l:n and columns m:n - for ($k = $m; $k <= $n-1; ++$k) { - $notlast = ($k != $n-1); + for ($k = $m; $k <= $n - 1; ++$k) { + $notlast = ($k != $n - 1); if ($k != $m) { - $p = $this->H[$k][$k-1]; - $q = $this->H[$k+1][$k-1]; - $r = ($notlast ? $this->H[$k+2][$k-1] : 0.0); + $p = $this->H[$k][$k - 1]; + $q = $this->H[$k + 1][$k - 1]; + $r = ($notlast ? $this->H[$k + 2][$k - 1] : 0.0); $x = abs($p) + abs($q) + abs($r); if ($x != 0.0) { $p = $p / $x; @@ -599,9 +640,9 @@ private function hqr2() } if ($s != 0) { if ($k != $m) { - $this->H[$k][$k-1] = -$s * $x; + $this->H[$k][$k - 1] = -$s * $x; } elseif ($l != $m) { - $this->H[$k][$k-1] = -$this->H[$k][$k-1]; + $this->H[$k][$k - 1] = -$this->H[$k][$k - 1]; } $p = $p + $s; $x = $p / $s; @@ -611,33 +652,33 @@ private function hqr2() $r = $r / $p; // Row modification for ($j = $k; $j < $nn; ++$j) { - $p = $this->H[$k][$j] + $q * $this->H[$k+1][$j]; + $p = $this->H[$k][$j] + $q * $this->H[$k + 1][$j]; if ($notlast) { - $p = $p + $r * $this->H[$k+2][$j]; - $this->H[$k+2][$j] = $this->H[$k+2][$j] - $p * $z; + $p = $p + $r * $this->H[$k + 2][$j]; + $this->H[$k + 2][$j] = $this->H[$k + 2][$j] - $p * $z; } $this->H[$k][$j] = $this->H[$k][$j] - $p * $x; - $this->H[$k+1][$j] = $this->H[$k+1][$j] - $p * $y; + $this->H[$k + 1][$j] = $this->H[$k + 1][$j] - $p * $y; } // Column modification - for ($i = 0; $i <= min($n, $k+3); ++$i) { - $p = $x * $this->H[$i][$k] + $y * $this->H[$i][$k+1]; + for ($i = 0; $i <= min($n, $k + 3); ++$i) { + $p = $x * $this->H[$i][$k] + $y * $this->H[$i][$k + 1]; if ($notlast) { - $p = $p + $z * $this->H[$i][$k+2]; - $this->H[$i][$k+2] = $this->H[$i][$k+2] - $p * $r; + $p = $p + $z * $this->H[$i][$k + 2]; + $this->H[$i][$k + 2] = $this->H[$i][$k + 2] - $p * $r; } $this->H[$i][$k] = $this->H[$i][$k] - $p; - $this->H[$i][$k+1] = $this->H[$i][$k+1] - $p * $q; + $this->H[$i][$k + 1] = $this->H[$i][$k + 1] - $p * $q; } // Accumulate transformations for ($i = $low; $i <= $high; ++$i) { - $p = $x * $this->V[$i][$k] + $y * $this->V[$i][$k+1]; + $p = $x * $this->V[$i][$k] + $y * $this->V[$i][$k + 1]; if ($notlast) { - $p = $p + $z * $this->V[$i][$k+2]; - $this->V[$i][$k+2] = $this->V[$i][$k+2] - $p * $r; + $p = $p + $z * $this->V[$i][$k + 2]; + $this->V[$i][$k + 2] = $this->V[$i][$k + 2] - $p * $r; } $this->V[$i][$k] = $this->V[$i][$k] - $p; - $this->V[$i][$k+1] = $this->V[$i][$k+1] - $p * $q; + $this->V[$i][$k + 1] = $this->V[$i][$k + 1] - $p * $q; } } // ($s != 0) } // k loop @@ -649,19 +690,20 @@ private function hqr2() return; } - for ($n = $nn-1; $n >= 0; --$n) { + for ($n = $nn - 1; $n >= 0; --$n) { $p = $this->d[$n]; $q = $this->e[$n]; // Real vector if ($q == 0) { $l = $n; $this->H[$n][$n] = 1.0; - for ($i = $n-1; $i >= 0; --$i) { + for ($i = $n - 1; $i >= 0; --$i) { $w = $this->H[$i][$i] - $p; $r = 0.0; for ($j = $l; $j <= $n; ++$j) { $r = $r + $this->H[$i][$j] * $this->H[$j][$n]; } + if ($this->e[$i] < 0.0) { $z = $w; $s = $r; @@ -675,15 +717,15 @@ private function hqr2() } // Solve real equations } else { - $x = $this->H[$i][$i+1]; - $y = $this->H[$i+1][$i]; + $x = $this->H[$i][$i + 1]; + $y = $this->H[$i + 1][$i]; $q = ($this->d[$i] - $p) * ($this->d[$i] - $p) + $this->e[$i] * $this->e[$i]; $t = ($x * $s - $z * $r) / $q; $this->H[$i][$n] = $t; if (abs($x) > abs($z)) { - $this->H[$i+1][$n] = (-$r - $w * $t) / $x; + $this->H[$i + 1][$n] = (-$r - $w * $t) / $x; } else { - $this->H[$i+1][$n] = (-$s - $y * $t) / $z; + $this->H[$i + 1][$n] = (-$s - $y * $t) / $z; } } // Overflow control @@ -697,24 +739,24 @@ private function hqr2() } // Complex vector } elseif ($q < 0) { - $l = $n-1; + $l = $n - 1; // Last vector component imaginary so matrix is triangular - if (abs($this->H[$n][$n-1]) > abs($this->H[$n-1][$n])) { - $this->H[$n-1][$n-1] = $q / $this->H[$n][$n-1]; - $this->H[$n-1][$n] = -($this->H[$n][$n] - $p) / $this->H[$n][$n-1]; + if (abs($this->H[$n][$n - 1]) > abs($this->H[$n - 1][$n])) { + $this->H[$n - 1][$n - 1] = $q / $this->H[$n][$n - 1]; + $this->H[$n - 1][$n] = -($this->H[$n][$n] - $p) / $this->H[$n][$n - 1]; } else { - $this->cdiv(0.0, -$this->H[$n-1][$n], $this->H[$n-1][$n-1] - $p, $q); - $this->H[$n-1][$n-1] = $this->cdivr; - $this->H[$n-1][$n] = $this->cdivi; + $this->cdiv(0.0, -$this->H[$n - 1][$n], $this->H[$n - 1][$n - 1] - $p, $q); + $this->H[$n - 1][$n - 1] = $this->cdivr; + $this->H[$n - 1][$n] = $this->cdivi; } - $this->H[$n][$n-1] = 0.0; - $this->H[$n][$n] = 1.0; - for ($i = $n-2; $i >= 0; --$i) { + $this->H[$n][$n - 1] = 0.0; + $this->H[$n][$n] = 1.0; + for ($i = $n - 2; $i >= 0; --$i) { // double ra,sa,vr,vi; $ra = 0.0; $sa = 0.0; for ($j = $l; $j <= $n; ++$j) { - $ra = $ra + $this->H[$i][$j] * $this->H[$j][$n-1]; + $ra = $ra + $this->H[$i][$j] * $this->H[$j][$n - 1]; $sa = $sa + $this->H[$i][$j] * $this->H[$j][$n]; } $w = $this->H[$i][$i] - $p; @@ -726,35 +768,35 @@ private function hqr2() $l = $i; if ($this->e[$i] == 0) { $this->cdiv(-$ra, -$sa, $w, $q); - $this->H[$i][$n-1] = $this->cdivr; - $this->H[$i][$n] = $this->cdivi; + $this->H[$i][$n - 1] = $this->cdivr; + $this->H[$i][$n] = $this->cdivi; } else { // Solve complex equations - $x = $this->H[$i][$i+1]; - $y = $this->H[$i+1][$i]; + $x = $this->H[$i][$i + 1]; + $y = $this->H[$i + 1][$i]; $vr = ($this->d[$i] - $p) * ($this->d[$i] - $p) + $this->e[$i] * $this->e[$i] - $q * $q; $vi = ($this->d[$i] - $p) * 2.0 * $q; if ($vr == 0.0 & $vi == 0.0) { $vr = $eps * $norm * (abs($w) + abs($q) + abs($x) + abs($y) + abs($z)); } $this->cdiv($x * $r - $z * $ra + $q * $sa, $x * $s - $z * $sa - $q * $ra, $vr, $vi); - $this->H[$i][$n-1] = $this->cdivr; - $this->H[$i][$n] = $this->cdivi; + $this->H[$i][$n - 1] = $this->cdivr; + $this->H[$i][$n] = $this->cdivi; if (abs($x) > (abs($z) + abs($q))) { - $this->H[$i+1][$n-1] = (-$ra - $w * $this->H[$i][$n-1] + $q * $this->H[$i][$n]) / $x; - $this->H[$i+1][$n] = (-$sa - $w * $this->H[$i][$n] - $q * $this->H[$i][$n-1]) / $x; + $this->H[$i + 1][$n - 1] = (-$ra - $w * $this->H[$i][$n - 1] + $q * $this->H[$i][$n]) / $x; + $this->H[$i + 1][$n] = (-$sa - $w * $this->H[$i][$n] - $q * $this->H[$i][$n - 1]) / $x; } else { - $this->cdiv(-$r - $y * $this->H[$i][$n-1], -$s - $y * $this->H[$i][$n], $z, $q); - $this->H[$i+1][$n-1] = $this->cdivr; - $this->H[$i+1][$n] = $this->cdivi; + $this->cdiv(-$r - $y * $this->H[$i][$n - 1], -$s - $y * $this->H[$i][$n], $z, $q); + $this->H[$i + 1][$n - 1] = $this->cdivr; + $this->H[$i + 1][$n] = $this->cdivi; } } // Overflow control - $t = max(abs($this->H[$i][$n-1]), abs($this->H[$i][$n])); + $t = max(abs($this->H[$i][$n - 1]), abs($this->H[$i][$n])); if (($eps * $t) * $t > 1) { for ($j = $i; $j <= $n; ++$j) { - $this->H[$j][$n-1] = $this->H[$j][$n-1] / $t; - $this->H[$j][$n] = $this->H[$j][$n] / $t; + $this->H[$j][$n - 1] = $this->H[$j][$n - 1] / $t; + $this->H[$j][$n] = $this->H[$j][$n] / $t; } } } // end else @@ -772,7 +814,7 @@ private function hqr2() } // Back transformation to get eigenvectors of original matrix - for ($j = $nn-1; $j >= $low; --$j) { + for ($j = $nn - 1; $j >= $low; --$j) { for ($i = $low; $i <= $high; ++$i) { $z = 0.0; for ($k = $low; $k <= min($j, $high); ++$k) { @@ -783,45 +825,12 @@ private function hqr2() } } // end hqr2 - /** - * Constructor: Check for symmetry, then construct the eigenvalue decomposition + * Return the eigenvector matrix * - * @param array $Arg - */ - public function __construct(array $Arg) - { - $this->A = $Arg; - $this->n = count($Arg[0]); - - $issymmetric = true; - for ($j = 0; ($j < $this->n) & $issymmetric; ++$j) { - for ($i = 0; ($i < $this->n) & $issymmetric; ++$i) { - $issymmetric = ($this->A[$i][$j] == $this->A[$j][$i]); - } - } - - if ($issymmetric) { - $this->V = $this->A; - // Tridiagonalize. - $this->tred2(); - // Diagonalize. - $this->tql2(); - } else { - $this->H = $this->A; - $this->ort = []; - // Reduce to Hessenberg form. - $this->orthes(); - // Reduce Hessenberg to real Schur form. - $this->hqr2(); - } - } - - /** - * Return the eigenvector matrix + * @access public * - * @access public - * @return array + * @return array */ public function getEigenvectors() { @@ -831,20 +840,21 @@ public function getEigenvectors() $vectors = new Matrix($vectors); $vectors = array_map(function ($vect) { $sum = 0; - for ($i=0; $itranspose()->toArray()); return $vectors; } - /** * Return the real parts of the eigenvalues
* d = real(diag(D)); @@ -856,7 +866,6 @@ public function getRealEigenvalues() return $this->d; } - /** * Return the imaginary parts of the eigenvalues
* d = imag(diag(D)) @@ -868,7 +877,6 @@ public function getImagEigenvalues() return $this->e; } - /** * Return the block diagonal eigenvalue matrix * @@ -876,15 +884,19 @@ public function getImagEigenvalues() */ public function getDiagonalEigenvalues() { + $D = []; + for ($i = 0; $i < $this->n; ++$i) { $D[$i] = array_fill(0, $this->n, 0.0); $D[$i][$i] = $this->d[$i]; if ($this->e[$i] == 0) { continue; } + $o = ($this->e[$i] > 0) ? $i + 1 : $i - 1; $D[$i][$o] = $this->e[$i]; } + return $D; } } // class EigenvalueDecomposition diff --git a/src/Phpml/Math/LinearAlgebra/LUDecomposition.php b/src/Phpml/Math/LinearAlgebra/LUDecomposition.php index 1aeb2395..de6a15da 100644 --- a/src/Phpml/Math/LinearAlgebra/LUDecomposition.php +++ b/src/Phpml/Math/LinearAlgebra/LUDecomposition.php @@ -1,4 +1,6 @@ -piv[$i] = $i; } $this->pivsign = 1; - $LUrowi = $LUcolj = []; + $LUcolj = []; // Outer loop. for ($j = 0; $j < $this->n; ++$j) { @@ -102,7 +105,7 @@ public function __construct(Matrix $A) } // Find pivot and exchange if necessary. $p = $j; - for ($i = $j+1; $i < $this->m; ++$i) { + for ($i = $j + 1; $i < $this->m; ++$i) { if (abs($LUcolj[$i]) > abs($LUcolj[$p])) { $p = $i; } @@ -120,7 +123,7 @@ public function __construct(Matrix $A) } // Compute multipliers. if (($j < $this->m) && ($this->LU[$j][$j] != 0.0)) { - for ($i = $j+1; $i < $this->m; ++$i) { + for ($i = $j + 1; $i < $this->m; ++$i) { $this->LU[$i][$j] /= $this->LU[$j][$j]; } } @@ -129,9 +132,9 @@ public function __construct(Matrix $A) /** - * Get lower triangular factor. + * Get lower triangular factor. * - * @return array Lower triangular factor + * @return Matrix Lower triangular factor */ public function getL() { @@ -152,9 +155,9 @@ public function getL() /** - * Get upper triangular factor. + * Get upper triangular factor. * - * @return array Upper triangular factor + * @return Matrix Upper triangular factor */ public function getU() { @@ -173,9 +176,9 @@ public function getU() /** - * Return pivot permutation vector. + * Return pivot permutation vector. * - * @return array Pivot vector + * @return array Pivot vector */ public function getPivot() { @@ -184,9 +187,9 @@ public function getPivot() /** - * Alias for getPivot + * Alias for getPivot * - * @see getPivot + * @see getPivot */ public function getDoublePivot() { @@ -195,9 +198,9 @@ public function getDoublePivot() /** - * Is the matrix nonsingular? + * Is the matrix nonsingular? * - * @return true if U, and hence A, is nonsingular. + * @return true if U, and hence A, is nonsingular. */ public function isNonsingular() { @@ -206,31 +209,35 @@ public function isNonsingular() return false; } } + return true; } // function isNonsingular() /** - * Count determinants + * Count determinants + * + * @return float|int d matrix determinant * - * @return array d matrix deterninat + * @throws MatrixException */ public function det() { - if ($this->m == $this->n) { - $d = $this->pivsign; - for ($j = 0; $j < $this->n; ++$j) { - $d *= $this->LU[$j][$j]; - } - return $d; - } else { + if ($this->m !== $this->n) { throw MatrixException::notSquareMatrix(); } + + $d = $this->pivsign; + for ($j = 0; $j < $this->n; ++$j) { + $d *= $this->LU[$j][$j]; + } + + return $d; } // function det() /** - * Solve A*X = B + * Solve A*X = B * * @param Matrix $B A Matrix with as many rows as A and any number of columns. * @@ -244,23 +251,23 @@ public function solve(Matrix $B) throw MatrixException::notSquareMatrix(); } - if (! $this->isNonsingular()) { + if (!$this->isNonsingular()) { throw MatrixException::singularMatrix(); } // Copy right hand side with pivoting $nx = $B->getColumns(); - $X = $this->getSubMatrix($B->toArray(), $this->piv, 0, $nx-1); + $X = $this->getSubMatrix($B->toArray(), $this->piv, 0, $nx - 1); // Solve L*Y = B(piv,:) for ($k = 0; $k < $this->n; ++$k) { - for ($i = $k+1; $i < $this->n; ++$i) { + for ($i = $k + 1; $i < $this->n; ++$i) { for ($j = 0; $j < $nx; ++$j) { $X[$i][$j] -= $X[$k][$j] * $this->LU[$i][$k]; } } } // Solve U*X = Y; - for ($k = $this->n-1; $k >= 0; --$k) { + for ($k = $this->n - 1; $k >= 0; --$k) { for ($j = 0; $j < $nx; ++$j) { $X[$k][$j] /= $this->LU[$k][$k]; } @@ -274,9 +281,10 @@ public function solve(Matrix $B) } // function solve() /** - * @param Matrix $matrix - * @param int $j0 - * @param int $jF + * @param array $matrix + * @param array $RL + * @param int $j0 + * @param int $jF * * @return array */ @@ -284,11 +292,11 @@ protected function getSubMatrix(array $matrix, array $RL, int $j0, int $jF) { $m = count($RL); $n = $jF - $j0; - $R = array_fill(0, $m, array_fill(0, $n+1, 0.0)); + $R = array_fill(0, $m, array_fill(0, $n + 1, 0.0)); for ($i = 0; $i < $m; ++$i) { for ($j = $j0; $j <= $jF; ++$j) { - $R[$i][$j - $j0]= $matrix[ $RL[$i] ][$j]; + $R[$i][$j - $j0] = $matrix[$RL[$i]][$j]; } } diff --git a/src/Phpml/Math/Matrix.php b/src/Phpml/Math/Matrix.php index c996e7fb..3c310528 100644 --- a/src/Phpml/Math/Matrix.php +++ b/src/Phpml/Math/Matrix.php @@ -139,6 +139,7 @@ public function getDeterminant() } $lu = new LUDecomposition($this); + return $this->determinant = $lu->det(); } @@ -232,6 +233,8 @@ public function multiplyByScalar($value) * Element-wise addition of the matrix with another one * * @param Matrix $other + * + * @return Matrix */ public function add(Matrix $other) { @@ -242,6 +245,8 @@ public function add(Matrix $other) * Element-wise subtracting of another matrix from this one * * @param Matrix $other + * + * @return Matrix */ public function subtract(Matrix $other) { @@ -252,7 +257,9 @@ public function subtract(Matrix $other) * Element-wise addition or substraction depending on the given sign parameter * * @param Matrix $other - * @param type $sign + * @param int $sign + * + * @return Matrix */ protected function _add(Matrix $other, $sign = 1) { @@ -260,13 +267,13 @@ protected function _add(Matrix $other, $sign = 1) $a2 = $other->toArray(); $newMatrix = []; - for ($i=0; $i < $this->rows; $i++) { - for ($k=0; $k < $this->columns; $k++) { + for ($i = 0; $i < $this->rows; ++$i) { + for ($k = 0; $k < $this->columns; ++$k) { $newMatrix[$i][$k] = $a1[$i][$k] + $sign * $a2[$i][$k]; } } - return new Matrix($newMatrix, false); + return new self($newMatrix, false); } /** @@ -295,7 +302,7 @@ public function inverse() protected function getIdentity() { $array = array_fill(0, $this->rows, array_fill(0, $this->columns, 0)); - for ($i=0; $i < $this->rows; $i++) { + for ($i = 0; $i < $this->rows; ++$i) { $array[$i][$i] = 1; } @@ -345,7 +352,7 @@ public function isSingular() : bool */ public static function transposeArray(array $array) { - return (new Matrix($array, false))->transpose()->toArray(); + return (new self($array, false))->transpose()->toArray(); } /** @@ -359,8 +366,8 @@ public static function transposeArray(array $array) */ public static function dot(array $array1, array $array2) { - $m1 = new Matrix($array1, false); - $m2 = new Matrix($array2, false); + $m1 = new self($array1, false); + $m2 = new self($array2, false); return $m1->multiply($m2->transpose())->toArray()[0]; } diff --git a/src/Phpml/Math/Statistic/Covariance.php b/src/Phpml/Math/Statistic/Covariance.php index 4a9b613d..8c8781d7 100644 --- a/src/Phpml/Math/Statistic/Covariance.php +++ b/src/Phpml/Math/Statistic/Covariance.php @@ -13,7 +13,7 @@ class Covariance * * @param array $x * @param array $y - * @param bool $sample + * @param bool $sample * @param float $meanX * @param float $meanY * @@ -57,14 +57,18 @@ public static function fromXYArrays(array $x, array $y, $sample = true, float $m * Calculates covariance of two dimensions, i and k in the given data. * * @param array $data - * @param int $i - * @param int $k - * @param type $sample - * @param int $n + * @param int $i + * @param int $k + * @param bool $sample * @param float $meanX * @param float $meanY + * + * @return float + * + * @throws InvalidArgumentException + * @throws \Exception */ - public static function fromDataset(array $data, int $i, int $k, $sample = true, float $meanX = null, float $meanY = null) + public static function fromDataset(array $data, int $i, int $k, bool $sample = true, float $meanX = null, float $meanY = null) { if (empty($data)) { throw InvalidArgumentException::arrayCantBeEmpty(); @@ -123,7 +127,8 @@ public static function fromDataset(array $data, int $i, int $k, $sample = true, /** * Returns the covariance matrix of n-dimensional data * - * @param array $data + * @param array $data + * @param array|null $means * * @return array */ @@ -133,19 +138,20 @@ public static function covarianceMatrix(array $data, array $means = null) if ($means === null) { $means = []; - for ($i=0; $i < $n; $i++) { + for ($i = 0; $i < $n; ++$i) { $means[] = Mean::arithmetic(array_column($data, $i)); } } $cov = []; - for ($i=0; $i < $n; $i++) { - for ($k=0; $k < $n; $k++) { + for ($i = 0; $i < $n; ++$i) { + for ($k = 0; $k < $n; ++$k) { if ($i > $k) { $cov[$i][$k] = $cov[$k][$i]; } else { - $cov[$i][$k] = Covariance::fromDataset( - $data, $i, $k, true, $means[$i], $means[$k]); + $cov[$i][$k] = self::fromDataset( + $data, $i, $k, true, $means[$i], $means[$k] + ); } } } diff --git a/src/Phpml/Math/Statistic/Gaussian.php b/src/Phpml/Math/Statistic/Gaussian.php index df27f076..d09edba3 100644 --- a/src/Phpml/Math/Statistic/Gaussian.php +++ b/src/Phpml/Math/Statistic/Gaussian.php @@ -31,7 +31,7 @@ public function __construct(float $mean, float $std) * * @param float $value * - * @return type + * @return float|int */ public function pdf(float $value) { diff --git a/src/Phpml/Math/Statistic/Mean.php b/src/Phpml/Math/Statistic/Mean.php index 581a1225..bd9657ed 100644 --- a/src/Phpml/Math/Statistic/Mean.php +++ b/src/Phpml/Math/Statistic/Mean.php @@ -68,7 +68,7 @@ public static function mode(array $numbers) */ private static function checkArrayLength(array $array) { - if (0 == count($array)) { + if (empty($array)) { throw InvalidArgumentException::arrayCantBeEmpty(); } } diff --git a/src/Phpml/Metric/ClassificationReport.php b/src/Phpml/Metric/ClassificationReport.php index c7cc1478..6fc026c0 100644 --- a/src/Phpml/Metric/ClassificationReport.php +++ b/src/Phpml/Metric/ClassificationReport.php @@ -112,8 +112,8 @@ private function computeMetrics(array $truePositive, array $falsePositive, array private function computeAverage() { foreach (['precision', 'recall', 'f1score'] as $metric) { - $values = array_filter($this->$metric); - if (0 == count($values)) { + $values = array_filter($this->{$metric}); + if (empty($values)) { $this->average[$metric] = 0.0; continue; } diff --git a/src/Phpml/ModelManager.php b/src/Phpml/ModelManager.php index c03d0ed2..08ab3e63 100644 --- a/src/Phpml/ModelManager.php +++ b/src/Phpml/ModelManager.php @@ -11,7 +11,8 @@ class ModelManager { /** * @param Estimator $estimator - * @param string $filepath + * @param string $filepath + * * @throws FileException * @throws SerializeException */ @@ -23,7 +24,7 @@ public function saveToFile(Estimator $estimator, string $filepath) $serialized = serialize($estimator); if (empty($serialized)) { - throw SerializeException::cantSerialize(get_type($estimator)); + throw SerializeException::cantSerialize(gettype($estimator)); } $result = file_put_contents($filepath, $serialized, LOCK_EX); @@ -34,7 +35,9 @@ public function saveToFile(Estimator $estimator, string $filepath) /** * @param string $filepath + * * @return Estimator + * * @throws FileException * @throws SerializeException */ diff --git a/src/Phpml/NeuralNetwork/Training/Backpropagation.php b/src/Phpml/NeuralNetwork/Training/Backpropagation.php index 136e8bf5..43ac512c 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -44,10 +44,12 @@ public function __construct(Network $network, int $theta = 1) */ public function train(array $samples, array $targets, float $desiredError = 0.001, int $maxIterations = 10000) { + $samplesCount = count($samples); + for ($i = 0; $i < $maxIterations; ++$i) { $resultsWithinError = $this->trainSamples($samples, $targets, $desiredError); - if ($resultsWithinError == count($samples)) { + if ($resultsWithinError === $samplesCount) { break; } } diff --git a/src/Phpml/Preprocessing/Normalizer.php b/src/Phpml/Preprocessing/Normalizer.php index 8392db7b..c61b4478 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -84,7 +84,7 @@ public function transform(array &$samples) $this->fit($samples); foreach ($samples as &$sample) { - $this->$method($sample); + $this->{$method}($sample); } } diff --git a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php index 9f6da70e..c6ec0178 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -130,6 +130,8 @@ public function __construct( /** * @param string $binPath + * + * @return $this */ public function setBinPath(string $binPath) { @@ -140,6 +142,8 @@ public function setBinPath(string $binPath) /** * @param string $varPath + * + * @return $this */ public function setVarPath(string $varPath) { @@ -230,8 +234,8 @@ private function getOSExtension() } /** - * @param $trainingSetFileName - * @param $modelFileName + * @param string $trainingSetFileName + * @param string $modelFileName * * @return string */ From 4af8449b1c384a49a00b643f38ec20f5e20f5e86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Monlla=C3=B3?= Date: Thu, 18 May 2017 06:07:14 +0800 Subject: [PATCH 177/328] Neural networks improvements (#89) * MultilayerPerceptron interface changes - Signature closer to other algorithms - New predict method - Remove desired error - Move maxIterations to constructor * MLP tests for multiple hidden layers and multi-class * Update all MLP-related tests * coding style fixes * Backpropagation included in multilayer-perceptron --- README.md | 3 +- docs/index.md | 3 +- .../neural-network/backpropagation.md | 30 ---- .../multilayer-perceptron-classifier.md | 50 +++++++ .../neural-network/multilayer-perceptron.md | 29 ---- mkdocs.yml | 3 +- src/Phpml/Classification/MLPClassifier.php | 67 +++++++++ .../Exception/InvalidArgumentException.php | 19 ++- .../NeuralNetwork/Network/LayeredNetwork.php | 2 +- .../Network/MultilayerPerceptron.php | 82 ++++++++++- src/Phpml/NeuralNetwork/Node/Neuron.php | 2 +- src/Phpml/NeuralNetwork/Training.php | 4 +- .../Training/Backpropagation.php | 103 +++----------- src/Phpml/Regression/MLPRegressor.php | 80 ----------- .../Classification/MLPClassifierTest.php | 129 ++++++++++++++++++ .../Network/MultilayerPerceptronTest.php | 74 ---------- tests/Phpml/NeuralNetwork/Node/NeuronTest.php | 2 +- .../Training/BackpropagationTest.php | 30 ---- 18 files changed, 369 insertions(+), 343 deletions(-) delete mode 100644 docs/machine-learning/neural-network/backpropagation.md create mode 100644 docs/machine-learning/neural-network/multilayer-perceptron-classifier.md delete mode 100644 docs/machine-learning/neural-network/multilayer-perceptron.md create mode 100644 src/Phpml/Classification/MLPClassifier.php delete mode 100644 src/Phpml/Regression/MLPRegressor.php create mode 100644 tests/Phpml/Classification/MLPClassifierTest.php delete mode 100644 tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php delete mode 100644 tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php diff --git a/README.md b/README.md index b17ac726..48b7b48c 100644 --- a/README.md +++ b/README.md @@ -76,8 +76,7 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * Workflow * [Pipeline](http://php-ml.readthedocs.io/en/latest/machine-learning/workflow/pipeline) * Neural Network - * [Multilayer Perceptron](http://php-ml.readthedocs.io/en/latest/machine-learning/neural-network/multilayer-perceptron/) - * [Backpropagation training](http://php-ml.readthedocs.io/en/latest/machine-learning/neural-network/backpropagation/) + * [Multilayer Perceptron Classifier](http://php-ml.readthedocs.io/en/latest/machine-learning/neural-network/multilayer-perceptron-classifier/) * Cross Validation * [Random Split](http://php-ml.readthedocs.io/en/latest/machine-learning/cross-validation/random-split/) * [Stratified Random Split](http://php-ml.readthedocs.io/en/latest/machine-learning/cross-validation/stratified-random-split/) diff --git a/docs/index.md b/docs/index.md index 156acb23..8d284afd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -65,8 +65,7 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * Workflow * [Pipeline](machine-learning/workflow/pipeline) * Neural Network - * [Multilayer Perceptron](machine-learning/neural-network/multilayer-perceptron/) - * [Backpropagation training](machine-learning/neural-network/backpropagation/) + * [Multilayer Perceptron Classifier](machine-learning/neural-network/multilayer-perceptron-classifier/) * Cross Validation * [Random Split](machine-learning/cross-validation/random-split/) * [Stratified Random Split](machine-learning/cross-validation/stratified-random-split/) diff --git a/docs/machine-learning/neural-network/backpropagation.md b/docs/machine-learning/neural-network/backpropagation.md deleted file mode 100644 index 05823517..00000000 --- a/docs/machine-learning/neural-network/backpropagation.md +++ /dev/null @@ -1,30 +0,0 @@ -# Backpropagation - -Backpropagation, an abbreviation for "backward propagation of errors", is a common method of training artificial neural networks used in conjunction with an optimization method such as gradient descent. - -## Constructor Parameters - -* $network (Network) - network to train (for example MultilayerPerceptron instance) -* $theta (int) - network theta parameter - -``` -use Phpml\NeuralNetwork\Network\MultilayerPerceptron; -use Phpml\NeuralNetwork\Training\Backpropagation; - -$network = new MultilayerPerceptron([2, 2, 1]); -$training = new Backpropagation($network); -``` - -## Training - -Example of XOR training: - -``` -$training->train( - $samples = [[1, 0], [0, 1], [1, 1], [0, 0]], - $targets = [[1], [1], [0], [0]], - $desiredError = 0.2, - $maxIteraions = 30000 -); -``` -You can train the neural network using multiple data sets, predictions will be based on all the training data. diff --git a/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md b/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md new file mode 100644 index 00000000..6f11f68c --- /dev/null +++ b/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md @@ -0,0 +1,50 @@ +# MLPClassifier + +A multilayer perceptron (MLP) is a feedforward artificial neural network model that maps sets of input data onto a set of appropriate outputs. + +## Constructor Parameters + +* $inputLayerFeatures (int) - the number of input layer features +* $hiddenLayers (array) - array with the hidden layers configuration, each value represent number of neurons in each layers +* $classes (array) - array with the different training set classes (array keys are ignored) +* $iterations (int) - number of training iterations +* $theta (int) - network theta parameter +* $activationFunction (ActivationFunction) - neuron activation function + +``` +use Phpml\Classification\MLPClassifier; +$mlp = new MLPClassifier(4, [2], ['a', 'b', 'c']); + +// 4 nodes in input layer, 2 nodes in first hidden layer and 3 possible labels. + +``` + +## Train + +To train a MLP simply provide train samples and labels (as array). Example: + + +``` +$mlp->train( + $samples = [[1, 0, 0, 0], [0, 1, 1, 0], [1, 1, 1, 1], [0, 0, 0, 0]], + $targets = ['a', 'a', 'b', 'c'] +); + +``` + +## Predict + +To predict sample label use predict method. You can provide one sample or array of samples: + +``` +$mlp->predict([[1, 1, 1, 1], [0, 0, 0, 0]]); +// return ['b', 'c']; + +``` + +## Activation Functions + +* BinaryStep +* Gaussian +* HyperbolicTangent +* Sigmoid (default) diff --git a/docs/machine-learning/neural-network/multilayer-perceptron.md b/docs/machine-learning/neural-network/multilayer-perceptron.md deleted file mode 100644 index c1c0eef4..00000000 --- a/docs/machine-learning/neural-network/multilayer-perceptron.md +++ /dev/null @@ -1,29 +0,0 @@ -# MultilayerPerceptron - -A multilayer perceptron (MLP) is a feedforward artificial neural network model that maps sets of input data onto a set of appropriate outputs. - -## Constructor Parameters - -* $layers (array) - array with layers configuration, each value represent number of neurons in each layers -* $activationFunction (ActivationFunction) - neuron activation function - -``` -use Phpml\NeuralNetwork\Network\MultilayerPerceptron; -$mlp = new MultilayerPerceptron([2, 2, 1]); - -// 2 nodes in input layer, 2 nodes in first hidden layer and 1 node in output layer -``` - -## Methods - -* setInput(array $input) -* getOutput() -* getLayers() -* addLayer(Layer $layer) - -## Activation Functions - -* BinaryStep -* Gaussian -* HyperbolicTangent -* Sigmoid (default) diff --git a/mkdocs.yml b/mkdocs.yml index 433cc3e9..8c9c10c4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -21,8 +21,7 @@ pages: - Workflow: - Pipeline: machine-learning/workflow/pipeline.md - Neural Network: - - Multilayer Perceptron: machine-learning/neural-network/multilayer-perceptron.md - - Backpropagation training: machine-learning/neural-network/backpropagation.md + - Multilayer Perceptron Classifier: machine-learning/neural-network/multilayer-perceptron-classifier.md - Cross Validation: - RandomSplit: machine-learning/cross-validation/random-split.md - Stratified Random Split: machine-learning/cross-validation/stratified-random-split.md diff --git a/src/Phpml/Classification/MLPClassifier.php b/src/Phpml/Classification/MLPClassifier.php new file mode 100644 index 00000000..c5d00bf2 --- /dev/null +++ b/src/Phpml/Classification/MLPClassifier.php @@ -0,0 +1,67 @@ +classes)) { + throw InvalidArgumentException::invalidTarget($target); + } + return array_search($target, $this->classes); + } + + /** + * @param array $sample + * + * @return mixed + */ + protected function predictSample(array $sample) + { + $output = $this->setInput($sample)->getOutput(); + + $predictedClass = null; + $max = 0; + foreach ($output as $class => $value) { + if ($value > $max) { + $predictedClass = $class; + $max = $value; + } + } + return $this->classes[$predictedClass]; + } + + /** + * @param array $sample + * @param mixed $target + */ + protected function trainSample(array $sample, $target) + { + + // Feed-forward. + $this->setInput($sample)->getOutput(); + + // Back-propagate. + $this->backpropagation->backpropagate($this->getLayers(), $this->getTargetClass($target)); + } +} diff --git a/src/Phpml/Exception/InvalidArgumentException.php b/src/Phpml/Exception/InvalidArgumentException.php index bf6d8ccf..3e2bff5e 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -66,6 +66,14 @@ public static function invalidClustersNumber() return new self('Invalid clusters number'); } + /** + * @return InvalidArgumentException + */ + public static function invalidTarget($target) + { + return new self('Target with value ' . $target . ' is not part of the accepted classes'); + } + /** * @param string $language * @@ -89,6 +97,15 @@ public static function invalidLayerNodeClass() */ public static function invalidLayersNumber() { - return new self('Provide at least 2 layers: 1 input and 1 output'); + return new self('Provide at least 1 hidden layer'); } + + /** + * @return InvalidArgumentException + */ + public static function invalidClassesNumber() + { + return new self('Provide at least 2 different classes'); + } + } diff --git a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php index af2d7239..cd90e3f5 100644 --- a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php +++ b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php @@ -71,7 +71,7 @@ public function setInput($input) foreach ($this->getLayers() as $layer) { foreach ($layer->getNodes() as $node) { if ($node instanceof Neuron) { - $node->refresh(); + $node->reset(); } } } diff --git a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php index 04664f9c..5d7f94e0 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -4,34 +4,93 @@ namespace Phpml\NeuralNetwork\Network; +use Phpml\Estimator; use Phpml\Exception\InvalidArgumentException; +use Phpml\NeuralNetwork\Training\Backpropagation; use Phpml\NeuralNetwork\ActivationFunction; use Phpml\NeuralNetwork\Layer; use Phpml\NeuralNetwork\Node\Bias; use Phpml\NeuralNetwork\Node\Input; use Phpml\NeuralNetwork\Node\Neuron; use Phpml\NeuralNetwork\Node\Neuron\Synapse; +use Phpml\Helper\Predictable; -class MultilayerPerceptron extends LayeredNetwork +abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator { + use Predictable; + /** - * @param array $layers + * @var array + */ + protected $classes = []; + + /** + * @var int + */ + private $iterations; + + /** + * @var Backpropagation + */ + protected $backpropagation = null; + + /** + * @param int $inputLayerFeatures + * @param array $hiddenLayers + * @param array $classes + * @param int $iterations * @param ActivationFunction|null $activationFunction + * @param int $theta * * @throws InvalidArgumentException */ - public function __construct(array $layers, ActivationFunction $activationFunction = null) + public function __construct(int $inputLayerFeatures, array $hiddenLayers, array $classes, int $iterations = 10000, ActivationFunction $activationFunction = null, int $theta = 1) { - if (count($layers) < 2) { + if (empty($hiddenLayers)) { throw InvalidArgumentException::invalidLayersNumber(); } - $this->addInputLayer(array_shift($layers)); - $this->addNeuronLayers($layers, $activationFunction); + $nClasses = count($classes); + if ($nClasses < 2) { + throw InvalidArgumentException::invalidClassesNumber(); + } + $this->classes = array_values($classes); + + $this->iterations = $iterations; + + $this->addInputLayer($inputLayerFeatures); + $this->addNeuronLayers($hiddenLayers, $activationFunction); + $this->addNeuronLayers([$nClasses], $activationFunction); + $this->addBiasNodes(); $this->generateSynapses(); + + $this->backpropagation = new Backpropagation($theta); + } + + /** + * @param array $samples + * @param array $targets + */ + public function train(array $samples, array $targets) + { + for ($i = 0; $i < $this->iterations; ++$i) { + $this->trainSamples($samples, $targets); + } } + /** + * @param array $sample + * @param mixed $target + */ + protected abstract function trainSample(array $sample, $target); + + /** + * @param array $sample + * @return mixed + */ + protected abstract function predictSample(array $sample); + /** * @param int $nodes */ @@ -92,4 +151,15 @@ private function generateNeuronSynapses(Layer $currentLayer, Neuron $nextNeuron) $nextNeuron->addSynapse(new Synapse($currentNeuron)); } } + + /** + * @param array $samples + * @param array $targets + */ + private function trainSamples(array $samples, array $targets) + { + foreach ($targets as $key => $target) { + $this->trainSample($samples[$key], $target); + } + } } diff --git a/src/Phpml/NeuralNetwork/Node/Neuron.php b/src/Phpml/NeuralNetwork/Node/Neuron.php index 51944384..7c246bed 100644 --- a/src/Phpml/NeuralNetwork/Node/Neuron.php +++ b/src/Phpml/NeuralNetwork/Node/Neuron.php @@ -68,7 +68,7 @@ public function getOutput(): float return $this->output; } - public function refresh() + public function reset() { $this->output = 0; } diff --git a/src/Phpml/NeuralNetwork/Training.php b/src/Phpml/NeuralNetwork/Training.php index d876af2e..fcb6d73c 100644 --- a/src/Phpml/NeuralNetwork/Training.php +++ b/src/Phpml/NeuralNetwork/Training.php @@ -9,8 +9,6 @@ interface Training /** * @param array $samples * @param array $targets - * @param float $desiredError - * @param int $maxIterations */ - public function train(array $samples, array $targets, float $desiredError = 0.001, int $maxIterations = 10000); + public function train(array $samples, array $targets); } diff --git a/src/Phpml/NeuralNetwork/Training/Backpropagation.php b/src/Phpml/NeuralNetwork/Training/Backpropagation.php index 43ac512c..741db2b6 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -4,18 +4,11 @@ namespace Phpml\NeuralNetwork\Training; -use Phpml\NeuralNetwork\Network; use Phpml\NeuralNetwork\Node\Neuron; -use Phpml\NeuralNetwork\Training; use Phpml\NeuralNetwork\Training\Backpropagation\Sigma; -class Backpropagation implements Training +class Backpropagation { - /** - * @var Network - */ - private $network; - /** * @var int */ @@ -27,96 +20,62 @@ class Backpropagation implements Training private $sigmas; /** - * @param Network $network - * @param int $theta - */ - public function __construct(Network $network, int $theta = 1) - { - $this->network = $network; - $this->theta = $theta; - } - - /** - * @param array $samples - * @param array $targets - * @param float $desiredError - * @param int $maxIterations + * @var array */ - public function train(array $samples, array $targets, float $desiredError = 0.001, int $maxIterations = 10000) - { - $samplesCount = count($samples); - - for ($i = 0; $i < $maxIterations; ++$i) { - $resultsWithinError = $this->trainSamples($samples, $targets, $desiredError); - - if ($resultsWithinError === $samplesCount) { - break; - } - } - } + private $prevSigmas; /** - * @param array $samples - * @param array $targets - * @param float $desiredError - * - * @return int + * @param int $theta */ - private function trainSamples(array $samples, array $targets, float $desiredError): int + public function __construct(int $theta) { - $resultsWithinError = 0; - foreach ($targets as $key => $target) { - $result = $this->network->setInput($samples[$key])->getOutput(); - - if ($this->isResultWithinError($result, $target, $desiredError)) { - ++$resultsWithinError; - } else { - $this->trainSample($samples[$key], $target); - } - } - - return $resultsWithinError; + $this->theta = $theta; } /** - * @param array $sample - * @param array $target + * @param array $layers + * @param mixed $targetClass */ - private function trainSample(array $sample, array $target) + public function backpropagate(array $layers, $targetClass) { - $this->network->setInput($sample)->getOutput(); - $this->sigmas = []; - $layers = $this->network->getLayers(); $layersNumber = count($layers); + // Backpropagation. for ($i = $layersNumber; $i > 1; --$i) { + $this->sigmas = []; foreach ($layers[$i - 1]->getNodes() as $key => $neuron) { + if ($neuron instanceof Neuron) { - $sigma = $this->getSigma($neuron, $target, $key, $i == $layersNumber); + $sigma = $this->getSigma($neuron, $targetClass, $key, $i == $layersNumber); foreach ($neuron->getSynapses() as $synapse) { $synapse->changeWeight($this->theta * $sigma * $synapse->getNode()->getOutput()); } } } + $this->prevSigmas = $this->sigmas; } } /** * @param Neuron $neuron - * @param array $target + * @param int $targetClass * @param int $key * @param bool $lastLayer * * @return float */ - private function getSigma(Neuron $neuron, array $target, int $key, bool $lastLayer): float + private function getSigma(Neuron $neuron, int $targetClass, int $key, bool $lastLayer): float { $neuronOutput = $neuron->getOutput(); $sigma = $neuronOutput * (1 - $neuronOutput); if ($lastLayer) { - $sigma *= ($target[$key] - $neuronOutput); + $value = 0; + if ($targetClass === $key) { + $value = 1; + } + $sigma *= ($value - $neuronOutput); } else { $sigma *= $this->getPrevSigma($neuron); } @@ -135,28 +94,10 @@ private function getPrevSigma(Neuron $neuron): float { $sigma = 0.0; - foreach ($this->sigmas as $neuronSigma) { + foreach ($this->prevSigmas as $neuronSigma) { $sigma += $neuronSigma->getSigmaForNeuron($neuron); } return $sigma; } - - /** - * @param array $result - * @param array $target - * @param float $desiredError - * - * @return bool - */ - private function isResultWithinError(array $result, array $target, float $desiredError) - { - foreach ($target as $key => $value) { - if ($result[$key] > $value + $desiredError || $result[$key] < $value - $desiredError) { - return false; - } - } - - return true; - } } diff --git a/src/Phpml/Regression/MLPRegressor.php b/src/Phpml/Regression/MLPRegressor.php deleted file mode 100644 index 72e6a81e..00000000 --- a/src/Phpml/Regression/MLPRegressor.php +++ /dev/null @@ -1,80 +0,0 @@ -hiddenLayers = $hiddenLayers; - $this->desiredError = $desiredError; - $this->maxIterations = $maxIterations; - $this->activationFunction = $activationFunction; - } - - /** - * @param array $samples - * @param array $targets - */ - public function train(array $samples, array $targets) - { - $layers = $this->hiddenLayers; - array_unshift($layers, count($samples[0])); - $layers[] = count($targets[0]); - - $this->perceptron = new MultilayerPerceptron($layers, $this->activationFunction); - - $trainer = new Backpropagation($this->perceptron); - $trainer->train($samples, $targets, $this->desiredError, $this->maxIterations); - } - - /** - * @param array $sample - * - * @return array - */ - protected function predictSample(array $sample) - { - return $this->perceptron->setInput($sample)->getOutput(); - } -} diff --git a/tests/Phpml/Classification/MLPClassifierTest.php b/tests/Phpml/Classification/MLPClassifierTest.php new file mode 100644 index 00000000..9f8b3fca --- /dev/null +++ b/tests/Phpml/Classification/MLPClassifierTest.php @@ -0,0 +1,129 @@ +assertCount(3, $mlp->getLayers()); + + $layers = $mlp->getLayers(); + + // input layer + $this->assertCount(3, $layers[0]->getNodes()); + $this->assertNotContainsOnly(Neuron::class, $layers[0]->getNodes()); + + // hidden layer + $this->assertCount(3, $layers[1]->getNodes()); + $this->assertNotContainsOnly(Neuron::class, $layers[1]->getNodes()); + + // output layer + $this->assertCount(2, $layers[2]->getNodes()); + $this->assertContainsOnly(Neuron::class, $layers[2]->getNodes()); + } + + public function testSynapsesGeneration() + { + $mlp = new MLPClassifier(2, [2], [0, 1]); + $layers = $mlp->getLayers(); + + foreach ($layers[1]->getNodes() as $node) { + if ($node instanceof Neuron) { + $synapses = $node->getSynapses(); + $this->assertCount(3, $synapses); + + $synapsesNodes = $this->getSynapsesNodes($synapses); + foreach ($layers[0]->getNodes() as $prevNode) { + $this->assertContains($prevNode, $synapsesNodes); + } + } + } + } + + public function testBackpropagationLearning() + { + // Single layer 2 classes. + $network = new MLPClassifier(2, [2], ['a', 'b'], 1000); + $network->train( + [[1, 0], [0, 1], [1, 1], [0, 0]], + ['a', 'b', 'a', 'b'] + ); + + $this->assertEquals('a', $network->predict([1, 0])); + $this->assertEquals('b', $network->predict([0, 1])); + $this->assertEquals('a', $network->predict([1, 1])); + $this->assertEquals('b', $network->predict([0, 0])); + } + + public function testBackpropagationLearningMultilayer() + { + // Multi-layer 2 classes. + $network = new MLPClassifier(5, [3, 2], ['a', 'b']); + $network->train( + [[1, 0, 0, 0, 0], [0, 1, 1, 0, 0], [1, 1, 1, 1, 1], [0, 0, 0, 0, 0]], + ['a', 'b', 'a', 'b'] + ); + + $this->assertEquals('a', $network->predict([1, 0, 0, 0, 0])); + $this->assertEquals('b', $network->predict([0, 1, 1, 0, 0])); + $this->assertEquals('a', $network->predict([1, 1, 1, 1, 1])); + $this->assertEquals('b', $network->predict([0, 0, 0, 0, 0])); + } + + public function testBackpropagationLearningMulticlass() + { + // Multi-layer more than 2 classes. + $network = new MLPClassifier(5, [3, 2], ['a', 'b', 4]); + $network->train( + [[1, 0, 0, 0, 0], [0, 1, 0, 0, 0], [0, 0, 1, 1, 0], [1, 1, 1, 1, 1], [0, 0, 0, 0, 0]], + ['a', 'b', 'a', 'a', 4] + ); + + $this->assertEquals('a', $network->predict([1, 0, 0, 0, 0])); + $this->assertEquals('b', $network->predict([0, 1, 0, 0, 0])); + $this->assertEquals('a', $network->predict([0, 0, 1, 1, 0])); + $this->assertEquals('a', $network->predict([1, 1, 1, 1, 1])); + $this->assertEquals(4, $network->predict([0, 0, 0, 0, 0])); + } + + /** + * @expectedException \Phpml\Exception\InvalidArgumentException + */ + public function testThrowExceptionOnInvalidLayersNumber() + { + new MLPClassifier(2, [], [0, 1]); + } + + /** + * @expectedException \Phpml\Exception\InvalidArgumentException + */ + public function testThrowExceptionOnInvalidClassesNumber() + { + new MLPClassifier(2, [2], [0]); + } + + /** + * @param array $synapses + * + * @return array + */ + private function getSynapsesNodes(array $synapses): array + { + $nodes = []; + foreach ($synapses as $synapse) { + $nodes[] = $synapse->getNode(); + } + + return $nodes; + } +} diff --git a/tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php b/tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php deleted file mode 100644 index e51d4855..00000000 --- a/tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php +++ /dev/null @@ -1,74 +0,0 @@ -assertCount(3, $mlp->getLayers()); - - $layers = $mlp->getLayers(); - - // input layer - $this->assertCount(3, $layers[0]->getNodes()); - $this->assertNotContainsOnly(Neuron::class, $layers[0]->getNodes()); - - // hidden layer - $this->assertCount(3, $layers[1]->getNodes()); - $this->assertNotContainsOnly(Neuron::class, $layers[0]->getNodes()); - - // output layer - $this->assertCount(1, $layers[2]->getNodes()); - $this->assertContainsOnly(Neuron::class, $layers[2]->getNodes()); - } - - public function testSynapsesGeneration() - { - $mlp = new MultilayerPerceptron([2, 2, 1]); - $layers = $mlp->getLayers(); - - foreach ($layers[1]->getNodes() as $node) { - if ($node instanceof Neuron) { - $synapses = $node->getSynapses(); - $this->assertCount(3, $synapses); - - $synapsesNodes = $this->getSynapsesNodes($synapses); - foreach ($layers[0]->getNodes() as $prevNode) { - $this->assertContains($prevNode, $synapsesNodes); - } - } - } - } - - /** - * @param array $synapses - * - * @return array - */ - private function getSynapsesNodes(array $synapses): array - { - $nodes = []; - foreach ($synapses as $synapse) { - $nodes[] = $synapse->getNode(); - } - - return $nodes; - } - - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - */ - public function testThrowExceptionOnInvalidLayersNumber() - { - new MultilayerPerceptron([2]); - } -} diff --git a/tests/Phpml/NeuralNetwork/Node/NeuronTest.php b/tests/Phpml/NeuralNetwork/Node/NeuronTest.php index 6bd0398e..a7bc6fe5 100644 --- a/tests/Phpml/NeuralNetwork/Node/NeuronTest.php +++ b/tests/Phpml/NeuralNetwork/Node/NeuronTest.php @@ -46,7 +46,7 @@ public function testNeuronRefresh() $this->assertEquals(0.5, $neuron->getOutput(), '', 0.01); - $neuron->refresh(); + $neuron->reset(); $this->assertEquals(0.88, $neuron->getOutput(), '', 0.01); } diff --git a/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php b/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php deleted file mode 100644 index a1915a33..00000000 --- a/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php +++ /dev/null @@ -1,30 +0,0 @@ -train( - [[1, 0], [0, 1], [1, 1], [0, 0]], - [[1], [1], [0], [0]], - $desiredError = 0.3, - 40000 - ); - - $this->assertEquals(0, $network->setInput([1, 1])->getOutput()[0], '', $desiredError); - $this->assertEquals(0, $network->setInput([0, 0])->getOutput()[0], '', $desiredError); - $this->assertEquals(1, $network->setInput([1, 0])->getOutput()[0], '', $desiredError); - $this->assertEquals(1, $network->setInput([0, 1])->getOutput()[0], '', $desiredError); - } -} From 3dff40ea1d5797b82c3ef12c749fc8a580004acd Mon Sep 17 00:00:00 2001 From: Maxime COLIN Date: Mon, 22 May 2017 23:18:27 +0200 Subject: [PATCH 178/328] Add french stopwords (#92) * Add french stopwords * Add french stopwords test --- .../FeatureExtraction/StopWords/French.php | 29 +++++++++++++++++++ .../Phpml/FeatureExtraction/StopWordsTest.php | 8 +++++ 2 files changed, 37 insertions(+) create mode 100644 src/Phpml/FeatureExtraction/StopWords/French.php diff --git a/src/Phpml/FeatureExtraction/StopWords/French.php b/src/Phpml/FeatureExtraction/StopWords/French.php new file mode 100644 index 00000000..96cc1109 --- /dev/null +++ b/src/Phpml/FeatureExtraction/StopWords/French.php @@ -0,0 +1,29 @@ +stopWords); + } +} diff --git a/tests/Phpml/FeatureExtraction/StopWordsTest.php b/tests/Phpml/FeatureExtraction/StopWordsTest.php index dd0a1859..49798604 100644 --- a/tests/Phpml/FeatureExtraction/StopWordsTest.php +++ b/tests/Phpml/FeatureExtraction/StopWordsTest.php @@ -45,4 +45,12 @@ public function testPolishStopWords() $this->assertTrue($stopWords->isStopWord('wam')); $this->assertFalse($stopWords->isStopWord('transhumanizm')); } + + public function testFrenchStopWords() + { + $stopWords = StopWords::factory('French'); + + $this->assertTrue($stopWords->isStopWord('alors')); + $this->assertFalse($stopWords->isStopWord('carte')); + } } From de50490154c10a2d5c16d57814a744e5bd0dea72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Monlla=C3=B3?= Date: Tue, 23 May 2017 15:03:05 +0800 Subject: [PATCH 179/328] Neural networks partial training and persistency (#91) * Neural networks partial training and persistency * cs fixes * Add partialTrain to nn docs * Test for invalid partial training classes provided --- .../multilayer-perceptron-classifier.md | 13 +++ src/Phpml/Classification/MLPClassifier.php | 9 --- .../Exception/InvalidArgumentException.php | 4 + .../NeuralNetwork/Network/LayeredNetwork.php | 8 ++ .../Network/MultilayerPerceptron.php | 76 +++++++++++++++--- .../Training/Backpropagation.php | 10 ++- .../Classification/MLPClassifierTest.php | 80 ++++++++++++++++++- 7 files changed, 175 insertions(+), 25 deletions(-) diff --git a/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md b/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md index 6f11f68c..d2f746df 100644 --- a/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md +++ b/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md @@ -29,6 +29,19 @@ $mlp->train( $samples = [[1, 0, 0, 0], [0, 1, 1, 0], [1, 1, 1, 1], [0, 0, 0, 0]], $targets = ['a', 'a', 'b', 'c'] ); +``` + +Use partialTrain method to train in batches. Example: + +``` +$mlp->partialTrain( + $samples = [[1, 0, 0, 0], [0, 1, 1, 0]], + $targets = ['a', 'a'] +); +$mlp->partialTrain( + $samples = [[1, 1, 1, 1], [0, 0, 0, 0]], + $targets = ['b', 'c'] +); ``` diff --git a/src/Phpml/Classification/MLPClassifier.php b/src/Phpml/Classification/MLPClassifier.php index c5d00bf2..bde49a23 100644 --- a/src/Phpml/Classification/MLPClassifier.php +++ b/src/Phpml/Classification/MLPClassifier.php @@ -4,17 +4,8 @@ namespace Phpml\Classification; -use Phpml\Classification\Classifier; use Phpml\Exception\InvalidArgumentException; use Phpml\NeuralNetwork\Network\MultilayerPerceptron; -use Phpml\NeuralNetwork\Training\Backpropagation; -use Phpml\NeuralNetwork\ActivationFunction; -use Phpml\NeuralNetwork\Layer; -use Phpml\NeuralNetwork\Node\Bias; -use Phpml\NeuralNetwork\Node\Input; -use Phpml\NeuralNetwork\Node\Neuron; -use Phpml\NeuralNetwork\Node\Neuron\Synapse; -use Phpml\Helper\Predictable; class MLPClassifier extends MultilayerPerceptron implements Classifier { diff --git a/src/Phpml/Exception/InvalidArgumentException.php b/src/Phpml/Exception/InvalidArgumentException.php index 3e2bff5e..277aecdd 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -108,4 +108,8 @@ public static function invalidClassesNumber() return new self('Provide at least 2 different classes'); } + public static function inconsistentClasses() + { + return new self('The provided classes don\'t match the classes provided in the constructor'); + } } diff --git a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php index cd90e3f5..b20f6bbc 100644 --- a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php +++ b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php @@ -32,6 +32,14 @@ public function getLayers(): array return $this->layers; } + /** + * @return void + */ + public function removeLayers() + { + unset($this->layers); + } + /** * @return Layer */ diff --git a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php index 5d7f94e0..25037743 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -5,6 +5,7 @@ namespace Phpml\NeuralNetwork\Network; use Phpml\Estimator; +use Phpml\IncrementalEstimator; use Phpml\Exception\InvalidArgumentException; use Phpml\NeuralNetwork\Training\Backpropagation; use Phpml\NeuralNetwork\ActivationFunction; @@ -15,10 +16,20 @@ use Phpml\NeuralNetwork\Node\Neuron\Synapse; use Phpml\Helper\Predictable; -abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator +abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, IncrementalEstimator { use Predictable; + /** + * @var int + */ + private $inputLayerFeatures; + + /** + * @var array + */ + private $hiddenLayers; + /** * @var array */ @@ -29,6 +40,16 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator */ private $iterations; + /** + * @var ActivationFunction + */ + protected $activationFunction; + + /** + * @var int + */ + private $theta; + /** * @var Backpropagation */ @@ -50,22 +71,33 @@ public function __construct(int $inputLayerFeatures, array $hiddenLayers, array throw InvalidArgumentException::invalidLayersNumber(); } - $nClasses = count($classes); - if ($nClasses < 2) { + if (count($classes) < 2) { throw InvalidArgumentException::invalidClassesNumber(); } - $this->classes = array_values($classes); + $this->classes = array_values($classes); $this->iterations = $iterations; + $this->inputLayerFeatures = $inputLayerFeatures; + $this->hiddenLayers = $hiddenLayers; + $this->activationFunction = $activationFunction; + $this->theta = $theta; + + $this->initNetwork(); + } - $this->addInputLayer($inputLayerFeatures); - $this->addNeuronLayers($hiddenLayers, $activationFunction); - $this->addNeuronLayers([$nClasses], $activationFunction); + /** + * @return void + */ + private function initNetwork() + { + $this->addInputLayer($this->inputLayerFeatures); + $this->addNeuronLayers($this->hiddenLayers, $this->activationFunction); + $this->addNeuronLayers([count($this->classes)], $this->activationFunction); $this->addBiasNodes(); $this->generateSynapses(); - $this->backpropagation = new Backpropagation($theta); + $this->backpropagation = new Backpropagation($this->theta); } /** @@ -74,6 +106,22 @@ public function __construct(int $inputLayerFeatures, array $hiddenLayers, array */ public function train(array $samples, array $targets) { + $this->reset(); + $this->initNetwork(); + $this->partialTrain($samples, $targets, $this->classes); + } + + /** + * @param array $samples + * @param array $targets + */ + public function partialTrain(array $samples, array $targets, array $classes = []) + { + if (!empty($classes) && array_values($classes) !== $this->classes) { + // We require the list of classes in the constructor. + throw InvalidArgumentException::inconsistentClasses(); + } + for ($i = 0; $i < $this->iterations; ++$i) { $this->trainSamples($samples, $targets); } @@ -83,13 +131,21 @@ public function train(array $samples, array $targets) * @param array $sample * @param mixed $target */ - protected abstract function trainSample(array $sample, $target); + abstract protected function trainSample(array $sample, $target); /** * @param array $sample * @return mixed */ - protected abstract function predictSample(array $sample); + abstract protected function predictSample(array $sample); + + /** + * @return void + */ + protected function reset() + { + $this->removeLayers(); + } /** * @param int $nodes diff --git a/src/Phpml/NeuralNetwork/Training/Backpropagation.php b/src/Phpml/NeuralNetwork/Training/Backpropagation.php index 741db2b6..ba90b45e 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -17,12 +17,12 @@ class Backpropagation /** * @var array */ - private $sigmas; + private $sigmas = null; /** * @var array */ - private $prevSigmas; + private $prevSigmas = null; /** * @param int $theta @@ -38,14 +38,12 @@ public function __construct(int $theta) */ public function backpropagate(array $layers, $targetClass) { - $layersNumber = count($layers); // Backpropagation. for ($i = $layersNumber; $i > 1; --$i) { $this->sigmas = []; foreach ($layers[$i - 1]->getNodes() as $key => $neuron) { - if ($neuron instanceof Neuron) { $sigma = $this->getSigma($neuron, $targetClass, $key, $i == $layersNumber); foreach ($neuron->getSynapses() as $synapse) { @@ -55,6 +53,10 @@ public function backpropagate(array $layers, $targetClass) } $this->prevSigmas = $this->sigmas; } + + // Clean some memory (also it helps make MLP persistency & children more maintainable). + $this->sigmas = null; + $this->prevSigmas = null; } /** diff --git a/tests/Phpml/Classification/MLPClassifierTest.php b/tests/Phpml/Classification/MLPClassifierTest.php index 9f8b3fca..3a009c38 100644 --- a/tests/Phpml/Classification/MLPClassifierTest.php +++ b/tests/Phpml/Classification/MLPClassifierTest.php @@ -5,8 +5,8 @@ namespace tests\Phpml\Classification; use Phpml\Classification\MLPClassifier; -use Phpml\NeuralNetwork\Training\Backpropagation; use Phpml\NeuralNetwork\Node\Neuron; +use Phpml\ModelManager; use PHPUnit\Framework\TestCase; class MLPClassifierTest extends TestCase @@ -53,7 +53,7 @@ public function testSynapsesGeneration() public function testBackpropagationLearning() { // Single layer 2 classes. - $network = new MLPClassifier(2, [2], ['a', 'b'], 1000); + $network = new MLPClassifier(2, [2], ['a', 'b']); $network->train( [[1, 0], [0, 1], [1, 1], [0, 0]], ['a', 'b', 'a', 'b'] @@ -65,6 +65,50 @@ public function testBackpropagationLearning() $this->assertEquals('b', $network->predict([0, 0])); } + public function testBackpropagationTrainingReset() + { + // Single layer 2 classes. + $network = new MLPClassifier(2, [2], ['a', 'b'], 1000); + $network->train( + [[1, 0], [0, 1]], + ['a', 'b'] + ); + + $this->assertEquals('a', $network->predict([1, 0])); + $this->assertEquals('b', $network->predict([0, 1])); + + $network->train( + [[1, 0], [0, 1]], + ['b', 'a'] + ); + + $this->assertEquals('b', $network->predict([1, 0])); + $this->assertEquals('a', $network->predict([0, 1])); + } + + public function testBackpropagationPartialTraining() + { + // Single layer 2 classes. + $network = new MLPClassifier(2, [2], ['a', 'b'], 1000); + $network->partialTrain( + [[1, 0], [0, 1]], + ['a', 'b'] + ); + + $this->assertEquals('a', $network->predict([1, 0])); + $this->assertEquals('b', $network->predict([0, 1])); + + $network->partialTrain( + [[1, 1], [0, 0]], + ['a', 'b'] + ); + + $this->assertEquals('a', $network->predict([1, 0])); + $this->assertEquals('b', $network->predict([0, 1])); + $this->assertEquals('a', $network->predict([1, 1])); + $this->assertEquals('b', $network->predict([0, 0])); + } + public function testBackpropagationLearningMultilayer() { // Multi-layer 2 classes. @@ -96,6 +140,26 @@ public function testBackpropagationLearningMulticlass() $this->assertEquals(4, $network->predict([0, 0, 0, 0, 0])); } + public function testSaveAndRestore() + { + // Instantinate new Percetron trained for OR problem + $samples = [[0, 0], [1, 0], [0, 1], [1, 1]]; + $targets = [0, 1, 1, 1]; + $classifier = new MLPClassifier(2, [2], [0, 1]); + $classifier->train($samples, $targets); + $testSamples = [[0, 0], [1, 0], [0, 1], [1, 1]]; + $predicted = $classifier->predict($testSamples); + + $filename = 'perceptron-test-'.rand(100, 999).'-'.uniqid(); + $filepath = tempnam(sys_get_temp_dir(), $filename); + $modelManager = new ModelManager(); + $modelManager->saveToFile($classifier, $filepath); + + $restoredClassifier = $modelManager->restoreFromFile($filepath); + $this->assertEquals($classifier, $restoredClassifier); + $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + } + /** * @expectedException \Phpml\Exception\InvalidArgumentException */ @@ -104,6 +168,18 @@ public function testThrowExceptionOnInvalidLayersNumber() new MLPClassifier(2, [], [0, 1]); } + /** + * @expectedException \Phpml\Exception\InvalidArgumentException + */ + public function testThrowExceptionOnInvalidPartialTrainingClasses() + { + $classifier = new MLPClassifier(2, [2], [0, 1]); + $classifier->partialTrain( + [[0, 1], [1, 0]], + [0, 2], + [0, 1, 2] + ); + } /** * @expectedException \Phpml\Exception\InvalidArgumentException */ From 2d3b44f1a048f8625c2ea6896105efe906539ee5 Mon Sep 17 00:00:00 2001 From: Maxime COLIN Date: Wed, 24 May 2017 09:06:54 +0200 Subject: [PATCH 180/328] Fix samples transformation in Pipeline training (#94) --- src/Phpml/Pipeline.php | 17 +++++----------- tests/Phpml/PipelineTest.php | 39 ++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/Phpml/Pipeline.php b/src/Phpml/Pipeline.php index a6b3d562..ca2914ad 100644 --- a/src/Phpml/Pipeline.php +++ b/src/Phpml/Pipeline.php @@ -67,8 +67,11 @@ public function getEstimator() */ public function train(array $samples, array $targets) { - $this->fitTransformers($samples); - $this->transformSamples($samples); + foreach ($this->transformers as $transformer) { + $transformer->fit($samples); + $transformer->transform($samples); + } + $this->estimator->train($samples, $targets); } @@ -84,16 +87,6 @@ public function predict(array $samples) return $this->estimator->predict($samples); } - /** - * @param array $samples - */ - private function fitTransformers(array &$samples) - { - foreach ($this->transformers as $transformer) { - $transformer->fit($samples); - } - } - /** * @param array $samples */ diff --git a/tests/Phpml/PipelineTest.php b/tests/Phpml/PipelineTest.php index 4454dee6..92a62238 100644 --- a/tests/Phpml/PipelineTest.php +++ b/tests/Phpml/PipelineTest.php @@ -6,11 +6,13 @@ use Phpml\Classification\SVC; use Phpml\FeatureExtraction\TfIdfTransformer; +use Phpml\FeatureExtraction\TokenCountVectorizer; use Phpml\Pipeline; use Phpml\Preprocessing\Imputer; use Phpml\Preprocessing\Normalizer; use Phpml\Preprocessing\Imputer\Strategy\MostFrequentStrategy; use Phpml\Regression\SVR; +use Phpml\Tokenization\WordTokenizer; use PHPUnit\Framework\TestCase; class PipelineTest extends TestCase @@ -65,4 +67,41 @@ public function testPipelineWorkflow() $this->assertEquals(4, $predicted[0]); } + + public function testPipelineTransformers() + { + $transformers = [ + new TokenCountVectorizer(new WordTokenizer()), + new TfIdfTransformer() + ]; + + $estimator = new SVC(); + + $samples = [ + 'Hello Paul', + 'Hello Martin', + 'Goodbye Tom', + 'Hello John', + 'Goodbye Alex', + 'Bye Tony', + ]; + + $targets = [ + 'greetings', + 'greetings', + 'farewell', + 'greetings', + 'farewell', + 'farewell', + ]; + + $pipeline = new Pipeline($transformers, $estimator); + $pipeline->train($samples, $targets); + + $expected = ['greetings', 'farewell']; + + $predicted = $pipeline->predict(['Hello Max', 'Goodbye Mark']); + + $this->assertEquals($expected, $predicted); + } } From 3bcba4053e176cfaccc595ee2a5201709ef01192 Mon Sep 17 00:00:00 2001 From: Hiroyuki Miura Date: Mon, 29 May 2017 16:39:08 +0900 Subject: [PATCH 181/328] Update README.md (#95) I tried copying the sample code as it is but did not do what I assumed. Specifically, it is as follows. - It does not work without `require_once` - I can not check the output if it is `return` I modified the README.md. I think that this one is better. Because you can use it as soon as you copy it. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 48b7b48c..9adddb23 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ PHP-ML requires PHP >= 7.0. Simple example of classification: ```php +require_once 'vendor/autoload.php'; + use Phpml\Classification\KNearestNeighbors; $samples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; @@ -24,7 +26,7 @@ $labels = ['a', 'a', 'a', 'b', 'b', 'b']; $classifier = new KNearestNeighbors(); $classifier->train($samples, $labels); -$classifier->predict([3, 2]); +echo $classifier->predict([3, 2]); // return 'b' ``` From 08d974bb4c293d2c03068b99dda16e370d8389de Mon Sep 17 00:00:00 2001 From: Ante Lucic Date: Wed, 26 Jul 2017 02:22:12 -0400 Subject: [PATCH 182/328] Add missing @throws and @param docblocks (#107) --- src/Phpml/Classification/MLPClassifier.php | 5 ++++- src/Phpml/Exception/InvalidArgumentException.php | 2 ++ src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Phpml/Classification/MLPClassifier.php b/src/Phpml/Classification/MLPClassifier.php index bde49a23..7f9043bc 100644 --- a/src/Phpml/Classification/MLPClassifier.php +++ b/src/Phpml/Classification/MLPClassifier.php @@ -11,7 +11,10 @@ class MLPClassifier extends MultilayerPerceptron implements Classifier { /** - * @param mixed $target + * @param mixed $target + * + * @throws InvalidArgumentException + * * @return int */ public function getTargetClass($target): int diff --git a/src/Phpml/Exception/InvalidArgumentException.php b/src/Phpml/Exception/InvalidArgumentException.php index 277aecdd..b618d009 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -67,6 +67,8 @@ public static function invalidClustersNumber() } /** + * @param mixed $target + * * @return InvalidArgumentException */ public static function invalidTarget($target) diff --git a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php index 25037743..b7364b28 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -114,6 +114,9 @@ public function train(array $samples, array $targets) /** * @param array $samples * @param array $targets + * @param array $classes + * + * @throws InvalidArgumentException */ public function partialTrain(array $samples, array $targets, array $classes = []) { From 07041ec60809b80ec871ab344502615293e9e2e9 Mon Sep 17 00:00:00 2001 From: Ante Lucic Date: Wed, 26 Jul 2017 02:24:47 -0400 Subject: [PATCH 183/328] Run newest php-cs-fixer (#108) --- src/Phpml/Classification/DecisionTree.php | 3 ++- src/Phpml/Classification/Linear/Adaline.php | 9 ++++++--- .../Linear/LogisticRegression.php | 11 +++++++---- src/Phpml/Classification/Linear/Perceptron.php | 10 +++++----- src/Phpml/Classification/SVC.php | 10 ++++++++-- src/Phpml/Helper/Optimizer/StochasticGD.php | 4 +++- .../LinearAlgebra/EigenvalueDecomposition.php | 12 ++++++------ src/Phpml/Math/Statistic/Covariance.php | 7 ++++++- src/Phpml/Regression/SVR.php | 12 +++++++++--- .../SupportVectorMachine.php | 18 ++++++++++++++---- tests/Phpml/Preprocessing/NormalizerTest.php | 6 ++++-- 11 files changed, 70 insertions(+), 32 deletions(-) diff --git a/src/Phpml/Classification/DecisionTree.php b/src/Phpml/Classification/DecisionTree.php index da8b81bd..c0c71f3b 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -101,7 +101,8 @@ public function train(array $samples, array $targets) } elseif (count($this->columnNames) > $this->featureCount) { $this->columnNames = array_slice($this->columnNames, 0, $this->featureCount); } elseif (count($this->columnNames) < $this->featureCount) { - $this->columnNames = array_merge($this->columnNames, + $this->columnNames = array_merge( + $this->columnNames, range(count($this->columnNames), $this->featureCount - 1) ); } diff --git a/src/Phpml/Classification/Linear/Adaline.php b/src/Phpml/Classification/Linear/Adaline.php index b94de28d..df648e84 100644 --- a/src/Phpml/Classification/Linear/Adaline.php +++ b/src/Phpml/Classification/Linear/Adaline.php @@ -39,9 +39,12 @@ class Adaline extends Perceptron * * @throws \Exception */ - public function __construct(float $learningRate = 0.001, int $maxIterations = 1000, - bool $normalizeInputs = true, int $trainingType = self::BATCH_TRAINING) - { + public function __construct( + float $learningRate = 0.001, + int $maxIterations = 1000, + bool $normalizeInputs = true, + int $trainingType = self::BATCH_TRAINING + ) { if (!in_array($trainingType, [self::BATCH_TRAINING, self::ONLINE_TRAINING])) { throw new \Exception("Adaline can only be trained with batch and online/stochastic gradient descent algorithm"); } diff --git a/src/Phpml/Classification/Linear/LogisticRegression.php b/src/Phpml/Classification/Linear/LogisticRegression.php index bc6a3c9e..90ef4d1c 100644 --- a/src/Phpml/Classification/Linear/LogisticRegression.php +++ b/src/Phpml/Classification/Linear/LogisticRegression.php @@ -67,10 +67,13 @@ class LogisticRegression extends Adaline * * @throws \Exception */ - public function __construct(int $maxIterations = 500, bool $normalizeInputs = true, - int $trainingType = self::CONJUGATE_GRAD_TRAINING, string $cost = 'sse', - string $penalty = 'L2') - { + public function __construct( + int $maxIterations = 500, + bool $normalizeInputs = true, + int $trainingType = self::CONJUGATE_GRAD_TRAINING, + string $cost = 'sse', + string $penalty = 'L2' + ) { $trainingTypes = range(self::BATCH_TRAINING, self::CONJUGATE_GRAD_TRAINING); if (!in_array($trainingType, $trainingTypes)) { throw new \Exception("Logistic regression can only be trained with " . diff --git a/src/Phpml/Classification/Linear/Perceptron.php b/src/Phpml/Classification/Linear/Perceptron.php index f4a8791f..145a992e 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -99,11 +99,11 @@ public function partialTrain(array $samples, array $targets, array $labels = []) $this->trainByLabel($samples, $targets, $labels); } - /** - * @param array $samples - * @param array $targets - * @param array $labels - */ + /** + * @param array $samples + * @param array $targets + * @param array $labels + */ public function trainBinary(array $samples, array $targets, array $labels) { if ($this->normalizer) { diff --git a/src/Phpml/Classification/SVC.php b/src/Phpml/Classification/SVC.php index 38ae9c45..bba5d09d 100644 --- a/src/Phpml/Classification/SVC.php +++ b/src/Phpml/Classification/SVC.php @@ -22,8 +22,14 @@ class SVC extends SupportVectorMachine implements Classifier * @param bool $probabilityEstimates */ public function __construct( - int $kernel = Kernel::LINEAR, float $cost = 1.0, int $degree = 3, float $gamma = null, float $coef0 = 0.0, - float $tolerance = 0.001, int $cacheSize = 100, bool $shrinking = true, + int $kernel = Kernel::LINEAR, + float $cost = 1.0, + int $degree = 3, + float $gamma = null, + float $coef0 = 0.0, + float $tolerance = 0.001, + int $cacheSize = 100, + bool $shrinking = true, bool $probabilityEstimates = false ) { parent::__construct(Type::C_SVC, $kernel, $cost, 0.5, $degree, $gamma, $coef0, 0.1, $tolerance, $cacheSize, $shrinking, $probabilityEstimates); diff --git a/src/Phpml/Helper/Optimizer/StochasticGD.php b/src/Phpml/Helper/Optimizer/StochasticGD.php index fa2401a4..82e860a8 100644 --- a/src/Phpml/Helper/Optimizer/StochasticGD.php +++ b/src/Phpml/Helper/Optimizer/StochasticGD.php @@ -243,7 +243,9 @@ protected function earlyStop($oldTheta) function ($w1, $w2) { return abs($w1 - $w2) > $this->threshold ? 1 : 0; }, - $oldTheta, $this->theta); + $oldTheta, + $this->theta + ); if (array_sum($diff) == 0) { return true; diff --git a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php index 7f0ec4ba..642e8b39 100644 --- a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -301,7 +301,7 @@ private function tql2() $p = -$s * $s2 * $c3 * $el1 * $this->e[$l] / $dl1; $this->e[$l] = $s * $p; $this->d[$l] = $c * $p; - // Check for convergence. + // Check for convergence. } while (abs($this->e[$l]) > $eps * $tst1); } $this->d[$l] = $this->d[$l] + $f; @@ -493,7 +493,7 @@ private function hqr2() $this->e[$n] = 0.0; --$n; $iter = 0; - // Two roots found + // Two roots found } elseif ($l == $n - 1) { $w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n]; $p = ($this->H[$n - 1][$n - 1] - $this->H[$n][$n]) / 2.0; @@ -541,7 +541,7 @@ private function hqr2() $this->V[$i][$n - 1] = $q * $z + $p * $this->V[$i][$n]; $this->V[$i][$n] = $q * $this->V[$i][$n] - $p * $z; } - // Complex pair + // Complex pair } else { $this->d[$n - 1] = $x + $p; $this->d[$n] = $x + $p; @@ -550,7 +550,7 @@ private function hqr2() } $n = $n - 2; $iter = 0; - // No convergence yet + // No convergence yet } else { // Form shift $x = $this->H[$n][$n]; @@ -715,7 +715,7 @@ private function hqr2() } else { $this->H[$i][$n] = -$r / ($eps * $norm); } - // Solve real equations + // Solve real equations } else { $x = $this->H[$i][$i + 1]; $y = $this->H[$i + 1][$i]; @@ -737,7 +737,7 @@ private function hqr2() } } } - // Complex vector + // Complex vector } elseif ($q < 0) { $l = $n - 1; // Last vector component imaginary so matrix is triangular diff --git a/src/Phpml/Math/Statistic/Covariance.php b/src/Phpml/Math/Statistic/Covariance.php index 8c8781d7..779b895c 100644 --- a/src/Phpml/Math/Statistic/Covariance.php +++ b/src/Phpml/Math/Statistic/Covariance.php @@ -150,7 +150,12 @@ public static function covarianceMatrix(array $data, array $means = null) $cov[$i][$k] = $cov[$k][$i]; } else { $cov[$i][$k] = self::fromDataset( - $data, $i, $k, true, $means[$i], $means[$k] + $data, + $i, + $k, + true, + $means[$i], + $means[$k] ); } } diff --git a/src/Phpml/Regression/SVR.php b/src/Phpml/Regression/SVR.php index e32eeb00..d4e5651f 100644 --- a/src/Phpml/Regression/SVR.php +++ b/src/Phpml/Regression/SVR.php @@ -22,9 +22,15 @@ class SVR extends SupportVectorMachine implements Regression * @param bool $shrinking */ public function __construct( - int $kernel = Kernel::RBF, int $degree = 3, float $epsilon = 0.1, float $cost = 1.0, - float $gamma = null, float $coef0 = 0.0, float $tolerance = 0.001, - int $cacheSize = 100, bool $shrinking = true + int $kernel = Kernel::RBF, + int $degree = 3, + float $epsilon = 0.1, + float $cost = 1.0, + float $gamma = null, + float $coef0 = 0.0, + float $tolerance = 0.001, + int $cacheSize = 100, + bool $shrinking = true ) { parent::__construct(Type::EPSILON_SVR, $kernel, $cost, 0.5, $degree, $gamma, $coef0, $epsilon, $tolerance, $cacheSize, $shrinking, false); } diff --git a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php index c6ec0178..b29bfa53 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -105,9 +105,18 @@ class SupportVectorMachine * @param bool $probabilityEstimates */ public function __construct( - int $type, int $kernel, float $cost = 1.0, float $nu = 0.5, int $degree = 3, - float $gamma = null, float $coef0 = 0.0, float $epsilon = 0.1, float $tolerance = 0.001, - int $cacheSize = 100, bool $shrinking = true, bool $probabilityEstimates = false + int $type, + int $kernel, + float $cost = 1.0, + float $nu = 0.5, + int $degree = 3, + float $gamma = null, + float $coef0 = 0.0, + float $epsilon = 0.1, + float $tolerance = 0.001, + int $cacheSize = 100, + bool $shrinking = true, + bool $probabilityEstimates = false ) { $this->type = $type; $this->kernel = $kernel; @@ -241,7 +250,8 @@ private function getOSExtension() */ private function buildTrainCommand(string $trainingSetFileName, string $modelFileName): string { - return sprintf('%ssvm-train%s -s %s -t %s -c %s -n %s -d %s%s -r %s -p %s -m %s -e %s -h %d -b %d %s %s', + return sprintf( + '%ssvm-train%s -s %s -t %s -c %s -n %s -d %s%s -r %s -p %s -m %s -e %s -h %d -b %d %s %s', $this->binPath, $this->getOSExtension(), $this->type, diff --git a/tests/Phpml/Preprocessing/NormalizerTest.php b/tests/Phpml/Preprocessing/NormalizerTest.php index 2492faef..a8a8826c 100644 --- a/tests/Phpml/Preprocessing/NormalizerTest.php +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -124,10 +124,12 @@ public function testStandardNorm() // Values in the vector should be some value between -3 and +3 $this->assertCount(10, $samples); foreach ($samples as $sample) { - $errors = array_filter($sample, + $errors = array_filter( + $sample, function ($element) { return $element < -3 || $element > 3; - }); + } + ); $this->assertCount(0, $errors); $this->assertEquals(0, $sample[3]); } From 65b8a136128f6791e056c3e7ceb2a46a97a69cc9 Mon Sep 17 00:00:00 2001 From: Ante Lucic Date: Wed, 26 Jul 2017 02:26:22 -0400 Subject: [PATCH 184/328] Change Optimizer::runOptimization visibility from protected to public (#109) --- src/Phpml/Helper/Optimizer/Optimizer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Phpml/Helper/Optimizer/Optimizer.php b/src/Phpml/Helper/Optimizer/Optimizer.php index 09668a95..24787e16 100644 --- a/src/Phpml/Helper/Optimizer/Optimizer.php +++ b/src/Phpml/Helper/Optimizer/Optimizer.php @@ -64,5 +64,5 @@ public function setInitialTheta(array $theta) * @param array $targets * @param \Closure $gradientCb */ - abstract protected function runOptimization(array $samples, array $targets, \Closure $gradientCb); + abstract public function runOptimization(array $samples, array $targets, \Closure $gradientCb); } From 47cdff0481626a4fa0696e5ecf47fe981628dddd Mon Sep 17 00:00:00 2001 From: Ante Lucic Date: Wed, 26 Jul 2017 02:36:34 -0400 Subject: [PATCH 185/328] fix invalid typehint for subs method (#110) --- src/Phpml/Helper/Optimizer/ConjugateGradient.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Phpml/Helper/Optimizer/ConjugateGradient.php b/src/Phpml/Helper/Optimizer/ConjugateGradient.php index 44bcd14c..67a65a46 100644 --- a/src/Phpml/Helper/Optimizer/ConjugateGradient.php +++ b/src/Phpml/Helper/Optimizer/ConjugateGradient.php @@ -352,11 +352,11 @@ public static function adds(array $m1, float $m2, int $mag = 1) * Element-wise subtraction of a vector with a scalar * * @param array $m1 - * @param array $m2 + * @param float $m2 * * @return array */ - public static function subs(array $m1, array $m2) + public static function subs(array $m1, float $m2) { return self::adds($m1, $m2, -1); } From ed5fc8996c19bb0f563101646e23940eda697de4 Mon Sep 17 00:00:00 2001 From: Ante Lucic Date: Fri, 28 Jul 2017 06:29:09 -0400 Subject: [PATCH 186/328] Require php-cs-fixer as dev dependency (#111) * require friendsofphp/php-cs-fixer as dev dependency * update contributing with php-cs-fixer example --- .gitignore | 1 + CONTRIBUTING.md | 2 +- composer.json | 3 +- composer.lock | 952 +++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 955 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 79b5610c..bac27473 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ humbuglog.* /bin/phpunit .coverage .php_cs.cache +/bin/php-cs-fixer diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 889f720f..7d2f4512 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,7 +28,7 @@ Please allow me time to review your pull requests. I will give my best to review ## Coding Standards -When contributing code to PHP-ML, you must follow its coding standards. It's as easy as executing `php-cs-fixer` (v2) in root directory. +When contributing code to PHP-ML, you must follow its coding standards. It's as easy as executing `./bin/php-cs-fixer fix` in root directory. More about PHP-CS-Fixer: [http://cs.sensiolabs.org/](http://cs.sensiolabs.org/) diff --git a/composer.json b/composer.json index 88043ddf..18ae9943 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,8 @@ "php": ">=7.0.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^6.0", + "friendsofphp/php-cs-fixer": "^2.4" }, "config": { "bin-dir": "bin" diff --git a/composer.lock b/composer.lock index 84f4b389..69794979 100644 --- a/composer.lock +++ b/composer.lock @@ -4,9 +4,77 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "626dba6a3c956ec66be5958c37001a67", + "content-hash": "c3243c3586715dde5b7c8fc237d91a4a", "packages": [], "packages-dev": [ + { + "name": "doctrine/annotations", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "5beebb01b025c94e93686b7a0ed3edae81fe3e7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/5beebb01b025c94e93686b7a0ed3edae81fe3e7f", + "reference": "5beebb01b025c94e93686b7a0ed3edae81fe3e7f", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "php": "^7.1" + }, + "require-dev": { + "doctrine/cache": "1.*", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "time": "2017-07-22T10:58:02+00:00" + }, { "name": "doctrine/instantiator", "version": "1.0.5", @@ -61,6 +129,182 @@ ], "time": "2015-06-14T21:17:01+00:00" }, + { + "name": "doctrine/lexer", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Lexer\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "lexer", + "parser" + ], + "time": "2014-09-09T13:34:57+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", + "reference": "63661f3add3609e90e4ab8115113e189ae547bb4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/63661f3add3609e90e4ab8115113e189ae547bb4", + "reference": "63661f3add3609e90e4ab8115113e189ae547bb4", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.2", + "ext-json": "*", + "ext-tokenizer": "*", + "gecko-packages/gecko-php-unit": "^2.0", + "php": "^5.6 || >=7.0 <7.2", + "sebastian/diff": "^1.4", + "symfony/console": "^3.0", + "symfony/event-dispatcher": "^3.0", + "symfony/filesystem": "^3.0", + "symfony/finder": "^3.0", + "symfony/options-resolver": "^3.0", + "symfony/polyfill-php70": "^1.0", + "symfony/polyfill-php72": "^1.4", + "symfony/process": "^3.0", + "symfony/stopwatch": "^3.0" + }, + "conflict": { + "hhvm": "*" + }, + "require-dev": { + "johnkary/phpunit-speedtrap": "^1.1", + "justinrainbow/json-schema": "^5.0", + "phpunit/phpunit": "^4.8.35 || ^5.4.3", + "satooshi/php-coveralls": "^1.0", + "symfony/phpunit-bridge": "^3.2.2" + }, + "suggest": { + "ext-mbstring": "For handling non-UTF8 characters in cache signature.", + "symfony/polyfill-mbstring": "When enabling `ext-mbstring` is not possible." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + }, + "classmap": [ + "tests/Test/Assert/AssertTokensTrait.php", + "tests/Test/AbstractFixerTestCase.php", + "tests/Test/AbstractIntegrationTestCase.php", + "tests/Test/IntegrationCase.php", + "tests/Test/IntegrationCaseFactory.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "time": "2017-07-18T15:35:40+00:00" + }, + { + "name": "gecko-packages/gecko-php-unit", + "version": "v2.1", + "source": { + "type": "git", + "url": "https://github.com/GeckoPackages/GeckoPHPUnit.git", + "reference": "5b9e9622c7efd3b22655270b80c03f9e52878a6e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GeckoPackages/GeckoPHPUnit/zipball/5b9e9622c7efd3b22655270b80c03f9e52878a6e", + "reference": "5b9e9622c7efd3b22655270b80c03f9e52878a6e", + "shasum": "" + }, + "require": { + "php": "^5.3.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.4.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "GeckoPackages\\PHPUnit\\": "src\\PHPUnit" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Additional PHPUnit tests.", + "homepage": "https://github.com/GeckoPackages", + "keywords": [ + "extension", + "filesystem", + "phpunit" + ], + "time": "2017-06-20T11:22:48+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.6.0", @@ -103,6 +347,54 @@ ], "time": "2017-01-26T22:05:40+00:00" }, + { + "name": "paragonie/random_compat", + "version": "v2.0.10", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/634bae8e911eefa89c1abfbf1b66da679ac8f54d", + "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "pseudorandom", + "random" + ], + "time": "2017-03-13T16:27:32+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "1.0", @@ -702,6 +994,53 @@ ], "time": "2017-03-03T06:30:20+00:00" }, + { + "name": "psr/log", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2016-10-10T12:19:37+00:00" + }, { "name": "sebastian/code-unit-reverse-lookup", "version": "1.0.1", @@ -1215,6 +1554,617 @@ "homepage": "https://github.com/sebastianbergmann/version", "time": "2016-10-03T07:35:21+00:00" }, + { + "name": "symfony/console", + "version": "v3.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "a97e45d98c59510f085fa05225a1acb74dfe0546" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/a97e45d98c59510f085fa05225a1acb74dfe0546", + "reference": "a97e45d98c59510f085fa05225a1acb74dfe0546", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/debug": "~2.8|~3.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.3", + "symfony/dependency-injection": "~3.3", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/filesystem": "~2.8|~3.0", + "symfony/http-kernel": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/filesystem": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2017-07-03T13:19:36+00:00" + }, + { + "name": "symfony/debug", + "version": "v3.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "63b85a968486d95ff9542228dc2e4247f16f9743" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/63b85a968486d95ff9542228dc2e4247f16f9743", + "reference": "63b85a968486d95ff9542228dc2e4247f16f9743", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/http-kernel": "~2.8|~3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "time": "2017-07-05T13:02:37+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v3.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "67535f1e3fd662bdc68d7ba317c93eecd973617e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/67535f1e3fd662bdc68d7ba317c93eecd973617e", + "reference": "67535f1e3fd662bdc68d7ba317c93eecd973617e", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2017-06-09T14:53:08+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v3.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "427987eb4eed764c3b6e38d52a0f87989e010676" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/427987eb4eed764c3b6e38d52a0f87989e010676", + "reference": "427987eb4eed764c3b6e38d52a0f87989e010676", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2017-07-11T07:17:58+00:00" + }, + { + "name": "symfony/finder", + "version": "v3.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/baea7f66d30854ad32988c11a09d7ffd485810c4", + "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2017-06-01T21:01:25+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v3.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "ff48982d295bcac1fd861f934f041ebc73ae40f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/ff48982d295bcac1fd861f934f041ebc73ae40f0", + "reference": "ff48982d295bcac1fd861f934f041ebc73ae40f0", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony OptionsResolver Component", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "time": "2017-04-12T14:14:56+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "f29dca382a6485c3cbe6379f0c61230167681937" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f29dca382a6485c3cbe6379f0c61230167681937", + "reference": "f29dca382a6485c3cbe6379f0c61230167681937", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2017-06-09T14:24:12+00:00" + }, + { + "name": "symfony/polyfill-php70", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php70.git", + "reference": "032fd647d5c11a9ceab8ee8747e13b5448e93874" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/032fd647d5c11a9ceab8ee8747e13b5448e93874", + "reference": "032fd647d5c11a9ceab8ee8747e13b5448e93874", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "~1.0|~2.0", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php70\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2017-06-09T14:24:12+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "d3a71580c1e2cab33b6d705f0ec40e9015e14d5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/d3a71580c1e2cab33b6d705f0ec40e9015e14d5c", + "reference": "d3a71580c1e2cab33b6d705f0ec40e9015e14d5c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2017-06-09T08:25:21+00:00" + }, + { + "name": "symfony/process", + "version": "v3.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "07432804942b9f6dd7b7377faf9920af5f95d70a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/07432804942b9f6dd7b7377faf9920af5f95d70a", + "reference": "07432804942b9f6dd7b7377faf9920af5f95d70a", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2017-07-13T13:05:09+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v3.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "602a15299dc01556013b07167d4f5d3a60e90d15" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/602a15299dc01556013b07167d4f5d3a60e90d15", + "reference": "602a15299dc01556013b07167d4f5d3a60e90d15", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Stopwatch Component", + "homepage": "https://symfony.com", + "time": "2017-04-12T14:14:56+00:00" + }, { "name": "webmozart/assert", "version": "1.2.0", From 3ac658c397d7e2e73090c62bb7b4544054a516d8 Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Thu, 17 Aug 2017 08:50:37 +0200 Subject: [PATCH 187/328] php-cs-fixer - more rules (#118) * Add new cs-fixer rules and run them * Do not align double arrows/equals --- .php_cs | 18 ++++++- src/Phpml/Classification/DecisionTree.php | 6 +-- .../DecisionTree/DecisionTreeLeaf.php | 38 +++++++++------ .../Classification/Ensemble/AdaBoost.php | 18 ++++--- src/Phpml/Classification/Ensemble/Bagging.php | 9 +++- .../Classification/Ensemble/RandomForest.php | 12 +++-- src/Phpml/Classification/Linear/Adaline.php | 6 +-- .../Classification/Linear/DecisionStump.php | 29 ++++++----- .../Linear/LogisticRegression.php | 15 +++--- .../Classification/Linear/Perceptron.php | 9 ++-- src/Phpml/Classification/MLPClassifier.php | 3 +- src/Phpml/Classification/NaiveBayes.php | 22 +++++---- src/Phpml/Clustering/FuzzyCMeans.php | 9 ++-- src/Phpml/Clustering/KMeans/Space.php | 2 +- .../EigenTransformerBase.php | 2 +- src/Phpml/DimensionReduction/KernelPCA.php | 17 ++++--- src/Phpml/DimensionReduction/LDA.php | 16 +++---- src/Phpml/DimensionReduction/PCA.php | 12 ++--- .../Exception/InvalidArgumentException.php | 2 +- .../FeatureExtraction/TfIdfTransformer.php | 2 +- src/Phpml/Helper/OneVsRest.php | 5 +- src/Phpml/Helper/Optimizer/GD.php | 4 +- src/Phpml/Helper/Optimizer/StochasticGD.php | 6 +-- src/Phpml/Math/Distance/Minkowski.php | 2 +- .../LinearAlgebra/EigenvalueDecomposition.php | 48 +++++++++++-------- .../Math/LinearAlgebra/LUDecomposition.php | 43 +++++++++-------- src/Phpml/Math/Matrix.php | 1 - src/Phpml/Math/Statistic/Covariance.php | 2 +- src/Phpml/Math/Statistic/Gaussian.php | 4 +- src/Phpml/Math/Statistic/Mean.php | 2 +- .../Network/MultilayerPerceptron.php | 1 + src/Phpml/Preprocessing/Normalizer.php | 6 +-- .../Phpml/Classification/DecisionTreeTest.php | 30 ++++++------ .../Classification/Ensemble/BaggingTest.php | 38 ++++++++------- .../Ensemble/RandomForestTest.php | 3 +- .../Classification/MLPClassifierTest.php | 1 + tests/Phpml/Clustering/FuzzyCMeansTest.php | 1 + tests/Phpml/DimensionReduction/LDATest.php | 2 +- tests/Phpml/DimensionReduction/PCATest.php | 2 +- .../LinearAlgebra/EigenDecompositionTest.php | 10 ++-- tests/Phpml/Math/Statistic/GaussianTest.php | 4 +- tests/Phpml/ModelManagerTest.php | 4 +- tests/Phpml/Preprocessing/NormalizerTest.php | 4 +- 43 files changed, 269 insertions(+), 201 deletions(-) diff --git a/.php_cs b/.php_cs index 0ea0c2ae..9fb8baad 100644 --- a/.php_cs +++ b/.php_cs @@ -3,11 +3,25 @@ return PhpCsFixer\Config::create() ->setRules([ '@PSR2' => true, - 'declare_strict_types' => true, 'array_syntax' => ['syntax' => 'short'], + 'binary_operator_spaces' => ['align_double_arrow' => false, 'align_equals' => false], 'blank_line_after_opening_tag' => true, + 'blank_line_before_return' => true, + 'cast_spaces' => true, + 'concat_space' => ['spacing' => 'none'], + 'declare_strict_types' => true, + 'method_separation' => true, + 'no_blank_lines_after_class_opening' => true, + 'no_spaces_around_offset' => ['positions' => ['inside', 'outside']], + 'no_unneeded_control_parentheses' => true, + 'no_unused_imports' => true, + 'phpdoc_align' => true, + 'phpdoc_no_access' => true, + 'phpdoc_separation' => true, + 'pre_increment' => true, + 'single_quote' => true, + 'trim_array_spaces' => true, 'single_blank_line_before_namespace' => true, - 'no_unused_imports' => true ]) ->setFinder( PhpCsFixer\Finder::create() diff --git a/src/Phpml/Classification/DecisionTree.php b/src/Phpml/Classification/DecisionTree.php index c0c71f3b..6cf6870f 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -144,7 +144,7 @@ protected function getSplitLeaf(array $records, int $depth = 0) : DecisionTreeLe // otherwise group the records so that we can classify the leaf // in case maximum depth is reached $leftRecords = []; - $rightRecords= []; + $rightRecords = []; $remainingTargets = []; $prevRecord = null; $allSame = true; @@ -162,7 +162,7 @@ protected function getSplitLeaf(array $records, int $depth = 0) : DecisionTreeLe if ($split->evaluate($record)) { $leftRecords[] = $recordNo; } else { - $rightRecords[]= $recordNo; + $rightRecords[] = $recordNo; } // Group remaining targets @@ -183,7 +183,7 @@ protected function getSplitLeaf(array $records, int $depth = 0) : DecisionTreeLe $split->leftLeaf = $this->getSplitLeaf($leftRecords, $depth + 1); } if ($rightRecords) { - $split->rightLeaf= $this->getSplitLeaf($rightRecords, $depth + 1); + $split->rightLeaf = $this->getSplitLeaf($rightRecords, $depth + 1); } } diff --git a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php index 787108f8..53c3386d 100644 --- a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -34,7 +34,7 @@ class DecisionTreeLeaf /** * @var DecisionTreeLeaf */ - public $rightLeaf= null; + public $rightLeaf = null; /** * @var array @@ -71,6 +71,7 @@ class DecisionTreeLeaf /** * @param array $record + * * @return bool */ public function evaluate($record) @@ -79,9 +80,10 @@ public function evaluate($record) if ($this->isContinuous) { $op = $this->operator; - $value= $this->numericValue; + $value = $this->numericValue; $recordField = strval($recordField); eval("\$result = $recordField $op $value;"); + return $result; } @@ -102,16 +104,16 @@ public function getNodeImpurityDecrease(int $parentRecordCount) return 0.0; } - $nodeSampleCount = (float)count($this->records); + $nodeSampleCount = (float) count($this->records); $iT = $this->giniIndex; if ($this->leftLeaf) { - $pL = count($this->leftLeaf->records)/$nodeSampleCount; + $pL = count($this->leftLeaf->records) / $nodeSampleCount; $iT -= $pL * $this->leftLeaf->giniIndex; } if ($this->rightLeaf) { - $pR = count($this->rightLeaf->records)/$nodeSampleCount; + $pR = count($this->rightLeaf->records) / $nodeSampleCount; $iT -= $pR * $this->rightLeaf->giniIndex; } @@ -122,6 +124,7 @@ public function getNodeImpurityDecrease(int $parentRecordCount) * Returns HTML representation of the node including children nodes * * @param $columnNames + * * @return string */ public function getHTML($columnNames = null) @@ -135,29 +138,34 @@ public function getHTML($columnNames = null) } else { $col = "col_$this->columnIndex"; } - if (!preg_match("/^[<>=]{1,2}/", $value)) { + if (!preg_match('/^[<>=]{1,2}/', $value)) { $value = "=$value"; } - $value = "$col $value
Gini: ". number_format($this->giniIndex, 2); + $value = "$col $value
Gini: ".number_format($this->giniIndex, 2); } - $str = ""; + + $str = "
- $value
"; + if ($this->leftLeaf || $this->rightLeaf) { - $str .=''; + $str .= ''; if ($this->leftLeaf) { - $str .=""; + $str .= ''; } else { - $str .=''; + $str .= ''; } - $str .=''; + + $str .= ''; if ($this->rightLeaf) { - $str .=""; + $str .= ''; } else { - $str .=''; + $str .= ''; } + $str .= ''; } + $str .= '
$value
| Yes
" . $this->leftLeaf->getHTML($columnNames) . "
| Yes
'.$this->leftLeaf->getHTML($columnNames).'
  No |
" . $this->rightLeaf->getHTML($columnNames) . "
No |
'.$this->rightLeaf->getHTML($columnNames).'
'; + return $str; } diff --git a/src/Phpml/Classification/Ensemble/AdaBoost.php b/src/Phpml/Classification/Ensemble/AdaBoost.php index 38571da1..95daf490 100644 --- a/src/Phpml/Classification/Ensemble/AdaBoost.php +++ b/src/Phpml/Classification/Ensemble/AdaBoost.php @@ -18,6 +18,7 @@ class AdaBoost implements Classifier /** * Actual labels given in the targets array + * * @var array */ protected $labels = []; @@ -86,7 +87,7 @@ public function __construct(int $maxIterations = 50) * Sets the base classifier that will be used for boosting (default = DecisionStump) * * @param string $baseClassifier - * @param array $classifierOptions + * @param array $classifierOptions */ public function setBaseClassifier(string $baseClassifier = DecisionStump::class, array $classifierOptions = []) { @@ -105,7 +106,7 @@ public function train(array $samples, array $targets) // Initialize usual variables $this->labels = array_keys(array_count_values($targets)); if (count($this->labels) != 2) { - throw new \Exception("AdaBoost is a binary classifier and can classify between two classes only"); + throw new \Exception('AdaBoost is a binary classifier and can classify between two classes only'); } // Set all target values to either -1 or 1 @@ -175,14 +176,14 @@ protected function resample() { $weights = $this->weights; $std = StandardDeviation::population($weights); - $mean= Mean::arithmetic($weights); + $mean = Mean::arithmetic($weights); $min = min($weights); - $minZ= (int)round(($min - $mean) / $std); + $minZ = (int) round(($min - $mean) / $std); $samples = []; $targets = []; foreach ($weights as $index => $weight) { - $z = (int)round(($weight - $mean) / $std) - $minZ + 1; + $z = (int) round(($weight - $mean) / $std) - $minZ + 1; for ($i = 0; $i < $z; ++$i) { if (rand(0, 1) == 0) { continue; @@ -220,6 +221,7 @@ protected function evaluateClassifier(Classifier $classifier) * Calculates alpha of a classifier * * @param float $errorRate + * * @return float */ protected function calculateAlpha(float $errorRate) @@ -227,6 +229,7 @@ protected function calculateAlpha(float $errorRate) if ($errorRate == 0) { $errorRate = 1e-10; } + return 0.5 * log((1 - $errorRate) / $errorRate); } @@ -234,7 +237,7 @@ protected function calculateAlpha(float $errorRate) * Updates the sample weights * * @param Classifier $classifier - * @param float $alpha + * @param float $alpha */ protected function updateWeights(Classifier $classifier, float $alpha) { @@ -254,6 +257,7 @@ protected function updateWeights(Classifier $classifier, float $alpha) /** * @param array $sample + * * @return mixed */ public function predictSample(array $sample) @@ -264,6 +268,6 @@ public function predictSample(array $sample) $sum += $h * $alpha; } - return $this->labels[ $sum > 0 ? 1 : -1]; + return $this->labels[$sum > 0 ? 1 : -1]; } } diff --git a/src/Phpml/Classification/Ensemble/Bagging.php b/src/Phpml/Classification/Ensemble/Bagging.php index 1af155d9..716a6bc9 100644 --- a/src/Phpml/Classification/Ensemble/Bagging.php +++ b/src/Phpml/Classification/Ensemble/Bagging.php @@ -84,10 +84,11 @@ public function __construct(int $numClassifier = 50) public function setSubsetRatio(float $ratio) { if ($ratio < 0.1 || $ratio > 1.0) { - throw new \Exception("Subset ratio should be between 0.1 and 1.0"); + throw new \Exception('Subset ratio should be between 0.1 and 1.0'); } $this->subsetRatio = $ratio; + return $this; } @@ -100,7 +101,7 @@ public function setSubsetRatio(float $ratio) * names are neglected. * * @param string $classifier - * @param array $classifierOptions + * @param array $classifierOptions * * @return $this */ @@ -135,6 +136,7 @@ public function train(array $samples, array $targets) /** * @param int $index + * * @return array */ protected function getRandomSubset(int $index) @@ -168,6 +170,7 @@ protected function initClassifiers() $classifiers[] = $this->initSingleClassifier($obj); } + return $classifiers; } @@ -183,6 +186,7 @@ protected function initSingleClassifier($classifier) /** * @param array $sample + * * @return mixed */ protected function predictSample(array $sample) @@ -196,6 +200,7 @@ protected function predictSample(array $sample) $counts = array_count_values($predictions); arsort($counts); reset($counts); + return key($counts); } } diff --git a/src/Phpml/Classification/Ensemble/RandomForest.php b/src/Phpml/Classification/Ensemble/RandomForest.php index 7849cd8b..e6677cb9 100644 --- a/src/Phpml/Classification/Ensemble/RandomForest.php +++ b/src/Phpml/Classification/Ensemble/RandomForest.php @@ -50,7 +50,7 @@ public function __construct(int $numClassifier = 50) public function setFeatureSubsetRatio($ratio) { if (is_float($ratio) && ($ratio < 0.1 || $ratio > 1.0)) { - throw new \Exception("When a float given, feature subset ratio should be between 0.1 and 1.0"); + throw new \Exception('When a float given, feature subset ratio should be between 0.1 and 1.0'); } if (is_string($ratio) && $ratio != 'sqrt' && $ratio != 'log') { @@ -58,6 +58,7 @@ public function setFeatureSubsetRatio($ratio) } $this->featureSubsetRatio = $ratio; + return $this; } @@ -74,7 +75,7 @@ public function setFeatureSubsetRatio($ratio) public function setClassifer(string $classifier, array $classifierOptions = []) { if ($classifier != DecisionTree::class) { - throw new \Exception("RandomForest can only use DecisionTree as base classifier"); + throw new \Exception('RandomForest can only use DecisionTree as base classifier'); } return parent::setClassifer($classifier, $classifierOptions); @@ -120,6 +121,7 @@ public function getFeatureImportances() * when trying to print some information about the trees such as feature importances * * @param array $names + * * @return $this */ public function setColumnNames(array $names) @@ -137,11 +139,11 @@ public function setColumnNames(array $names) protected function initSingleClassifier($classifier) { if (is_float($this->featureSubsetRatio)) { - $featureCount = (int)($this->featureSubsetRatio * $this->featureCount); + $featureCount = (int) ($this->featureSubsetRatio * $this->featureCount); } elseif ($this->featureCount == 'sqrt') { - $featureCount = (int)sqrt($this->featureCount) + 1; + $featureCount = (int) sqrt($this->featureCount) + 1; } else { - $featureCount = (int)log($this->featureCount, 2) + 1; + $featureCount = (int) log($this->featureCount, 2) + 1; } if ($featureCount >= $this->featureCount) { diff --git a/src/Phpml/Classification/Linear/Adaline.php b/src/Phpml/Classification/Linear/Adaline.php index df648e84..d10fff48 100644 --- a/src/Phpml/Classification/Linear/Adaline.php +++ b/src/Phpml/Classification/Linear/Adaline.php @@ -9,12 +9,12 @@ class Adaline extends Perceptron /** * Batch training is the default Adaline training algorithm */ - const BATCH_TRAINING = 1; + const BATCH_TRAINING = 1; /** * Online training: Stochastic gradient descent learning */ - const ONLINE_TRAINING = 2; + const ONLINE_TRAINING = 2; /** * Training type may be either 'Batch' or 'Online' learning @@ -46,7 +46,7 @@ public function __construct( int $trainingType = self::BATCH_TRAINING ) { if (!in_array($trainingType, [self::BATCH_TRAINING, self::ONLINE_TRAINING])) { - throw new \Exception("Adaline can only be trained with batch and online/stochastic gradient descent algorithm"); + throw new \Exception('Adaline can only be trained with batch and online/stochastic gradient descent algorithm'); } $this->trainingType = $trainingType; diff --git a/src/Phpml/Classification/Linear/DecisionStump.php b/src/Phpml/Classification/Linear/DecisionStump.php index 5a3247fe..776a6a2b 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -106,7 +106,7 @@ protected function trainBinary(array $samples, array $targets, array $labels) if ($this->weights) { $numWeights = count($this->weights); if ($numWeights != count($samples)) { - throw new \Exception("Number of sample weights does not match with number of samples"); + throw new \Exception('Number of sample weights does not match with number of samples'); } } else { $this->weights = array_fill(0, count($samples), 1); @@ -163,7 +163,7 @@ public function setNumericalSplitCount(float $count) * * @param array $samples * @param array $targets - * @param int $col + * @param int $col * * @return array */ @@ -192,8 +192,8 @@ protected function getBestNumericalSplit(array $samples, array $targets, int $co } // Try other possible points one by one - for ($step = $minValue; $step <= $maxValue; $step+= $stepSize) { - $threshold = (float)$step; + for ($step = $minValue; $step <= $maxValue; $step += $stepSize) { + $threshold = (float) $step; list($errorRate, $prob) = $this->calculateErrorRate($targets, $threshold, $operator, $values); if ($errorRate < $split['trainingErrorRate']) { $split = ['value' => $threshold, 'operator' => $operator, @@ -209,7 +209,7 @@ protected function getBestNumericalSplit(array $samples, array $targets, int $co /** * @param array $samples * @param array $targets - * @param int $col + * @param int $col * * @return array */ @@ -217,7 +217,7 @@ protected function getBestNominalSplit(array $samples, array $targets, int $col) { $values = array_column($samples, $col); $valueCounts = array_count_values($values); - $distinctVals= array_keys($valueCounts); + $distinctVals = array_keys($valueCounts); $split = null; @@ -236,7 +236,6 @@ protected function getBestNominalSplit(array $samples, array $targets, int $col) return $split; } - /** * * @param mixed $leftValue @@ -264,10 +263,10 @@ protected function evaluate($leftValue, string $operator, $rightValue) * Calculates the ratio of wrong predictions based on the new threshold * value given as the parameter * - * @param array $targets - * @param float $threshold + * @param array $targets + * @param float $threshold * @param string $operator - * @param array $values + * @param array $values * * @return array */ @@ -276,7 +275,7 @@ protected function calculateErrorRate(array $targets, float $threshold, string $ $wrong = 0.0; $prob = []; $leftLabel = $this->binaryLabels[0]; - $rightLabel= $this->binaryLabels[1]; + $rightLabel = $this->binaryLabels[1]; foreach ($values as $index => $value) { if ($this->evaluate($value, $operator, $threshold)) { @@ -299,7 +298,7 @@ protected function calculateErrorRate(array $targets, float $threshold, string $ // Calculate probabilities: Proportion of labels in each leaf $dist = array_combine($this->binaryLabels, array_fill(0, 2, 0.0)); foreach ($prob as $leaf => $counts) { - $leafTotal = (float)array_sum($prob[$leaf]); + $leafTotal = (float) array_sum($prob[$leaf]); foreach ($counts as $label => $count) { if (strval($leaf) == strval($label)) { $dist[$leaf] = $count / $leafTotal; @@ -357,8 +356,8 @@ protected function resetBinary() */ public function __toString() { - return "IF $this->column $this->operator $this->value " . - "THEN " . $this->binaryLabels[0] . " ". - "ELSE " . $this->binaryLabels[1]; + return "IF $this->column $this->operator $this->value ". + 'THEN '.$this->binaryLabels[0].' '. + 'ELSE '.$this->binaryLabels[1]; } } diff --git a/src/Phpml/Classification/Linear/LogisticRegression.php b/src/Phpml/Classification/Linear/LogisticRegression.php index 90ef4d1c..0447ef82 100644 --- a/src/Phpml/Classification/Linear/LogisticRegression.php +++ b/src/Phpml/Classification/Linear/LogisticRegression.php @@ -59,9 +59,9 @@ class LogisticRegression extends Adaline * * Penalty (Regularization term) can be 'L2' or empty string to cancel penalty term * - * @param int $maxIterations - * @param bool $normalizeInputs - * @param int $trainingType + * @param int $maxIterations + * @param bool $normalizeInputs + * @param int $trainingType * @param string $cost * @param string $penalty * @@ -76,13 +76,13 @@ public function __construct( ) { $trainingTypes = range(self::BATCH_TRAINING, self::CONJUGATE_GRAD_TRAINING); if (!in_array($trainingType, $trainingTypes)) { - throw new \Exception("Logistic regression can only be trained with " . - "batch (gradient descent), online (stochastic gradient descent) " . - "or conjugate batch (conjugate gradients) algorithms"); + throw new \Exception('Logistic regression can only be trained with '. + 'batch (gradient descent), online (stochastic gradient descent) '. + 'or conjugate batch (conjugate gradients) algorithms'); } if (!in_array($cost, ['log', 'sse'])) { - throw new \Exception("Logistic regression cost function can be one of the following: \n" . + throw new \Exception("Logistic regression cost function can be one of the following: \n". "'log' for log-likelihood and 'sse' for sum of squared errors"); } @@ -290,6 +290,7 @@ protected function predictProbability(array $sample, $label) if (strval($predicted) == strval($label)) { $sample = $this->checkNormalizedSample($sample); + return abs($this->output($sample) - 0.5); } diff --git a/src/Phpml/Classification/Linear/Perceptron.php b/src/Phpml/Classification/Linear/Perceptron.php index 145a992e..000059fa 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -74,11 +74,11 @@ class Perceptron implements Classifier, IncrementalEstimator public function __construct(float $learningRate = 0.001, int $maxIterations = 1000, bool $normalizeInputs = true) { if ($learningRate <= 0.0 || $learningRate > 1.0) { - throw new \Exception("Learning rate should be a float value between 0.0(exclusive) and 1.0(inclusive)"); + throw new \Exception('Learning rate should be a float value between 0.0(exclusive) and 1.0(inclusive)'); } if ($maxIterations <= 0) { - throw new \Exception("Maximum number of iterations must be an integer greater than 0"); + throw new \Exception('Maximum number of iterations must be an integer greater than 0'); } if ($normalizeInputs) { @@ -175,7 +175,7 @@ protected function runTraining(array $samples, array $targets) $prediction = $this->outputClass($sample); $gradient = $prediction - $target; - $error = $gradient**2; + $error = $gradient ** 2; return [$error, $gradient]; }; @@ -231,6 +231,7 @@ protected function checkNormalizedSample(array $sample) * Calculates net output of the network as a float value for the given input * * @param array $sample + * * @return int */ protected function output(array $sample) @@ -251,6 +252,7 @@ protected function output(array $sample) * Returns the class value (either -1 or 1) for the given input * * @param array $sample + * * @return int */ protected function outputClass(array $sample) @@ -275,6 +277,7 @@ protected function predictProbability(array $sample, $label) if (strval($predicted) == strval($label)) { $sample = $this->checkNormalizedSample($sample); + return abs($this->output($sample)); } diff --git a/src/Phpml/Classification/MLPClassifier.php b/src/Phpml/Classification/MLPClassifier.php index 7f9043bc..dfb53944 100644 --- a/src/Phpml/Classification/MLPClassifier.php +++ b/src/Phpml/Classification/MLPClassifier.php @@ -9,7 +9,6 @@ class MLPClassifier extends MultilayerPerceptron implements Classifier { - /** * @param mixed $target * @@ -22,6 +21,7 @@ public function getTargetClass($target): int if (!in_array($target, $this->classes)) { throw InvalidArgumentException::invalidTarget($target); } + return array_search($target, $this->classes); } @@ -42,6 +42,7 @@ protected function predictSample(array $sample) $max = $value; } } + return $this->classes[$predictedClass]; } diff --git a/src/Phpml/Classification/NaiveBayes.php b/src/Phpml/Classification/NaiveBayes.php index 1a634da6..8daaf860 100644 --- a/src/Phpml/Classification/NaiveBayes.php +++ b/src/Phpml/Classification/NaiveBayes.php @@ -13,8 +13,8 @@ class NaiveBayes implements Classifier { use Trainable, Predictable; - const CONTINUOS = 1; - const NOMINAL = 2; + const CONTINUOS = 1; + const NOMINAL = 2; const EPSILON = 1e-10; /** @@ -25,7 +25,7 @@ class NaiveBayes implements Classifier /** * @var array */ - private $mean= []; + private $mean = []; /** * @var array @@ -80,13 +80,14 @@ public function train(array $samples, array $targets) /** * Calculates vital statistics for each label & feature. Stores these * values in private array in order to avoid repeated calculation + * * @param string $label - * @param array $samples + * @param array $samples */ private function calculateStatistics($label, $samples) { $this->std[$label] = array_fill(0, $this->featureCount, 0); - $this->mean[$label]= array_fill(0, $this->featureCount, 0); + $this->mean[$label] = array_fill(0, $this->featureCount, 0); $this->dataType[$label] = array_fill(0, $this->featureCount, self::CONTINUOS); $this->discreteProb[$label] = array_fill(0, $this->featureCount, self::CONTINUOS); for ($i = 0; $i < $this->featureCount; ++$i) { @@ -128,10 +129,11 @@ private function sampleProbability($sample, $feature, $label) $this->discreteProb[$label][$feature][$value] == 0) { return self::EPSILON; } + return $this->discreteProb[$label][$feature][$value]; } $std = $this->std[$label][$feature] ; - $mean= $this->mean[$label][$feature]; + $mean = $this->mean[$label][$feature]; // Calculate the probability density by use of normal/Gaussian distribution // Ref: https://en.wikipedia.org/wiki/Normal_distribution // @@ -139,8 +141,9 @@ private function sampleProbability($sample, $feature, $label) // some libraries adopt taking log of calculations such as // scikit-learn did. // (See : https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/naive_bayes.py) - $pdf = -0.5 * log(2.0 * pi() * $std * $std); + $pdf = -0.5 * log(2.0 * pi() * $std * $std); $pdf -= 0.5 * pow($value - $mean, 2) / ($std * $std); + return $pdf; } @@ -159,11 +162,13 @@ private function getSamplesByLabel($label) $samples[] = $this->samples[$i]; } } + return $samples; } /** * @param array $sample + * * @return mixed */ protected function predictSample(array $sample) @@ -174,7 +179,7 @@ protected function predictSample(array $sample) $predictions = []; foreach ($this->labels as $label) { $p = $this->p[$label]; - for ($i = 0; $i<$this->featureCount; ++$i) { + for ($i = 0; $i < $this->featureCount; ++$i) { $Plf = $this->sampleProbability($sample, $i, $label); $p += $Plf; } @@ -183,6 +188,7 @@ protected function predictSample(array $sample) arsort($predictions, SORT_NUMERIC); reset($predictions); + return key($predictions); } } diff --git a/src/Phpml/Clustering/FuzzyCMeans.php b/src/Phpml/Clustering/FuzzyCMeans.php index c6a3c464..da1398ee 100644 --- a/src/Phpml/Clustering/FuzzyCMeans.php +++ b/src/Phpml/Clustering/FuzzyCMeans.php @@ -58,10 +58,10 @@ class FuzzyCMeans implements Clusterer private $samples; /** - * @param int $clustersNumber + * @param int $clustersNumber * @param float $fuzziness * @param float $epsilon - * @param int $maxIterations + * @param int $maxIterations * * @throws InvalidArgumentException */ @@ -159,6 +159,7 @@ protected function updateMembershipMatrix() * * @param int $row * @param int $col + * * @return float */ protected function getDistanceCalc(int $row, int $col) @@ -179,6 +180,7 @@ protected function getDistanceCalc(int $row, int $col) $val = pow($dist1 / $dist2, 2.0 / ($this->fuzziness - 1)); $sum += $val; } + return $sum; } @@ -212,13 +214,14 @@ public function getMembershipMatrix() /** * @param array|Point[] $samples + * * @return array */ public function cluster(array $samples) { // Initialize variables, clusters and membership matrix $this->sampleCount = count($samples); - $this->samples =& $samples; + $this->samples = &$samples; $this->space = new Space(count($samples[0])); $this->initClusters(); diff --git a/src/Phpml/Clustering/KMeans/Space.php b/src/Phpml/Clustering/KMeans/Space.php index 0276880d..14c17601 100644 --- a/src/Phpml/Clustering/KMeans/Space.php +++ b/src/Phpml/Clustering/KMeans/Space.php @@ -66,7 +66,7 @@ public function addPoint(array $coordinates, $data = null) /** * @param Point $point - * @param null $data + * @param null $data */ public function attach($point, $data = null) { diff --git a/src/Phpml/DimensionReduction/EigenTransformerBase.php b/src/Phpml/DimensionReduction/EigenTransformerBase.php index 6c0ef05f..5e27a139 100644 --- a/src/Phpml/DimensionReduction/EigenTransformerBase.php +++ b/src/Phpml/DimensionReduction/EigenTransformerBase.php @@ -54,7 +54,7 @@ protected function eigenDecomposition(array $matrix) { $eig = new EigenvalueDecomposition($matrix); $eigVals = $eig->getRealEigenvalues(); - $eigVects= $eig->getEigenvectors(); + $eigVects = $eig->getEigenvectors(); $totalEigVal = array_sum($eigVals); // Sort eigenvalues in descending order diff --git a/src/Phpml/DimensionReduction/KernelPCA.php b/src/Phpml/DimensionReduction/KernelPCA.php index 94e18c92..908c441a 100644 --- a/src/Phpml/DimensionReduction/KernelPCA.php +++ b/src/Phpml/DimensionReduction/KernelPCA.php @@ -44,10 +44,10 @@ class KernelPCA extends PCA * will initialize the algorithm with an RBF kernel having the gamma parameter as 15,0.
* This transformation will return the same number of rows with only 2 columns. * - * @param int $kernel + * @param int $kernel * @param float $totalVariance Total variance to be preserved if numFeatures is not given - * @param int $numFeatures Number of columns to be returned - * @param float $gamma Gamma parameter is used with RBF and Sigmoid kernels + * @param int $numFeatures Number of columns to be returned + * @param float $gamma Gamma parameter is used with RBF and Sigmoid kernels * * @throws \Exception */ @@ -55,7 +55,7 @@ public function __construct(int $kernel = self::KERNEL_RBF, $totalVariance = nul { $availableKernels = [self::KERNEL_RBF, self::KERNEL_SIGMOID, self::KERNEL_LAPLACIAN, self::KERNEL_LINEAR]; if (!in_array($kernel, $availableKernels)) { - throw new \Exception("KernelPCA can be initialized with the following kernels only: Linear, RBF, Sigmoid and Laplacian"); + throw new \Exception('KernelPCA can be initialized with the following kernels only: Linear, RBF, Sigmoid and Laplacian'); } parent::__construct($totalVariance, $numFeatures); @@ -133,7 +133,7 @@ protected function calculateKernelMatrix(array $data, int $numRows) */ protected function centerMatrix(array $matrix, int $n) { - $N = array_fill(0, $n, array_fill(0, $n, 1.0/$n)); + $N = array_fill(0, $n, array_fill(0, $n, 1.0 / $n)); $N = new Matrix($N, false); $K = new Matrix($matrix, false); @@ -168,6 +168,7 @@ protected function getKernel() case self::KERNEL_RBF: // k(x,y)=exp(-γ.|x-y|) where |..| is Euclidean distance $dist = new Euclidean(); + return function ($x, $y) use ($dist) { return exp(-$this->gamma * $dist->sqDistance($x, $y)); }; @@ -176,12 +177,14 @@ protected function getKernel() // k(x,y)=tanh(γ.xT.y+c0) where c0=1 return function ($x, $y) { $res = Matrix::dot($x, $y)[0] + 1.0; + return tanh($this->gamma * $res); }; case self::KERNEL_LAPLACIAN: // k(x,y)=exp(-γ.|x-y|) where |..| is Manhattan distance $dist = new Manhattan(); + return function ($x, $y) use ($dist) { return exp(-$this->gamma * $dist->distance($x, $y)); }; @@ -241,11 +244,11 @@ protected function projectSample(array $pairs) public function transform(array $sample) { if (!$this->fit) { - throw new \Exception("KernelPCA has not been fitted with respect to original dataset, please run KernelPCA::fit() first"); + throw new \Exception('KernelPCA has not been fitted with respect to original dataset, please run KernelPCA::fit() first'); } if (is_array($sample[0])) { - throw new \Exception("KernelPCA::transform() accepts only one-dimensional arrays"); + throw new \Exception('KernelPCA::transform() accepts only one-dimensional arrays'); } $pairs = $this->getDistancePairs($sample); diff --git a/src/Phpml/DimensionReduction/LDA.php b/src/Phpml/DimensionReduction/LDA.php index e094c357..a2df627d 100644 --- a/src/Phpml/DimensionReduction/LDA.php +++ b/src/Phpml/DimensionReduction/LDA.php @@ -43,20 +43,20 @@ class LDA extends EigenTransformerBase * or numFeatures (number of features in the dataset) to be preserved. * * @param float|null $totalVariance Total explained variance to be preserved - * @param int|null $numFeatures Number of features to be preserved + * @param int|null $numFeatures Number of features to be preserved * * @throws \Exception */ public function __construct($totalVariance = null, $numFeatures = null) { if ($totalVariance !== null && ($totalVariance < 0.1 || $totalVariance > 0.99)) { - throw new \Exception("Total variance can be a value between 0.1 and 0.99"); + throw new \Exception('Total variance can be a value between 0.1 and 0.99'); } if ($numFeatures !== null && $numFeatures <= 0) { - throw new \Exception("Number of features to be preserved should be greater than 0"); + throw new \Exception('Number of features to be preserved should be greater than 0'); } if ($totalVariance !== null && $numFeatures !== null) { - throw new \Exception("Either totalVariance or numFeatures should be specified in order to run the algorithm"); + throw new \Exception('Either totalVariance or numFeatures should be specified in order to run the algorithm'); } if ($numFeatures !== null) { @@ -78,7 +78,7 @@ public function __construct($totalVariance = null, $numFeatures = null) public function fit(array $data, array $classes) : array { $this->labels = $this->getLabels($classes); - $this->means = $this->calculateMeans($data, $classes); + $this->means = $this->calculateMeans($data, $classes); $sW = $this->calculateClassVar($data, $classes); $sB = $this->calculateClassCov(); @@ -105,7 +105,6 @@ protected function getLabels(array $classes): array return array_keys($counts); } - /** * Calculates mean of each column for each class and returns * n by m matrix where n is number of labels and m is number of columns @@ -118,7 +117,7 @@ protected function getLabels(array $classes): array protected function calculateMeans(array $data, array $classes) : array { $means = []; - $counts= []; + $counts = []; $overallMean = array_fill(0, count($data[0]), 0.0); foreach ($data as $index => $row) { @@ -156,7 +155,6 @@ protected function calculateMeans(array $data, array $classes) : array return $means; } - /** * Returns in-class scatter matrix for each class, which * is a n by m matrix where n is number of classes and @@ -237,7 +235,7 @@ protected function calculateVar(array $row, array $means) public function transform(array $sample) { if (!$this->fit) { - throw new \Exception("LDA has not been fitted with respect to original dataset, please run LDA::fit() first"); + throw new \Exception('LDA has not been fitted with respect to original dataset, please run LDA::fit() first'); } if (!is_array($sample[0])) { diff --git a/src/Phpml/DimensionReduction/PCA.php b/src/Phpml/DimensionReduction/PCA.php index acaa8e01..7d3fd4ff 100644 --- a/src/Phpml/DimensionReduction/PCA.php +++ b/src/Phpml/DimensionReduction/PCA.php @@ -28,20 +28,20 @@ class PCA extends EigenTransformerBase * within the data. It is a lossy data compression technique.
* * @param float $totalVariance Total explained variance to be preserved - * @param int $numFeatures Number of features to be preserved + * @param int $numFeatures Number of features to be preserved * * @throws \Exception */ public function __construct($totalVariance = null, $numFeatures = null) { if ($totalVariance !== null && ($totalVariance < 0.1 || $totalVariance > 0.99)) { - throw new \Exception("Total variance can be a value between 0.1 and 0.99"); + throw new \Exception('Total variance can be a value between 0.1 and 0.99'); } if ($numFeatures !== null && $numFeatures <= 0) { - throw new \Exception("Number of features to be preserved should be greater than 0"); + throw new \Exception('Number of features to be preserved should be greater than 0'); } if ($totalVariance !== null && $numFeatures !== null) { - throw new \Exception("Either totalVariance or numFeatures should be specified in order to run the algorithm"); + throw new \Exception('Either totalVariance or numFeatures should be specified in order to run the algorithm'); } if ($numFeatures !== null) { @@ -79,7 +79,7 @@ public function fit(array $data) /** * @param array $data - * @param int $n + * @param int $n */ protected function calculateMeans(array $data, int $n) { @@ -129,7 +129,7 @@ protected function normalize(array $data, int $n) public function transform(array $sample) { if (!$this->fit) { - throw new \Exception("PCA has not been fitted with respect to original dataset, please run PCA::fit() first"); + throw new \Exception('PCA has not been fitted with respect to original dataset, please run PCA::fit() first'); } if (!is_array($sample[0])) { diff --git a/src/Phpml/Exception/InvalidArgumentException.php b/src/Phpml/Exception/InvalidArgumentException.php index b618d009..313ca793 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -73,7 +73,7 @@ public static function invalidClustersNumber() */ public static function invalidTarget($target) { - return new self('Target with value ' . $target . ' is not part of the accepted classes'); + return new self('Target with value '.$target.' is not part of the accepted classes'); } /** diff --git a/src/Phpml/FeatureExtraction/TfIdfTransformer.php b/src/Phpml/FeatureExtraction/TfIdfTransformer.php index 93357752..61f7e65b 100644 --- a/src/Phpml/FeatureExtraction/TfIdfTransformer.php +++ b/src/Phpml/FeatureExtraction/TfIdfTransformer.php @@ -32,7 +32,7 @@ public function fit(array $samples) $count = count($samples); foreach ($this->idf as &$value) { - $value = log((float)($count / $value), 10.0); + $value = log((float) ($count / $value), 10.0); } } diff --git a/src/Phpml/Helper/OneVsRest.php b/src/Phpml/Helper/OneVsRest.php index 8d71fbcb..72757df8 100644 --- a/src/Phpml/Helper/OneVsRest.php +++ b/src/Phpml/Helper/OneVsRest.php @@ -109,6 +109,7 @@ protected function getClassifierCopy() // multiple instances of this classifier $classifier = clone $this; $classifier->reset(); + return $classifier; } @@ -121,6 +122,7 @@ protected function getClassifierCopy() * * @param array $targets * @param mixed $label + * * @return array Binarized targets and target's labels */ private function binarizeTargets($targets, $label) @@ -131,10 +133,10 @@ private function binarizeTargets($targets, $label) } $labels = [$label, $notLabel]; + return [$targets, $labels]; } - /** * @param array $sample * @@ -153,6 +155,7 @@ protected function predictSample(array $sample) } arsort($probs, SORT_NUMERIC); + return key($probs); } diff --git a/src/Phpml/Helper/Optimizer/GD.php b/src/Phpml/Helper/Optimizer/GD.php index b88b0c7c..8babc7d9 100644 --- a/src/Phpml/Helper/Optimizer/GD.php +++ b/src/Phpml/Helper/Optimizer/GD.php @@ -42,7 +42,7 @@ public function runOptimization(array $samples, array $targets, \Closure $gradie $this->updateWeightsWithUpdates($updates, $totalPenalty); - $this->costValues[] = array_sum($errors)/$this->sampleCount; + $this->costValues[] = array_sum($errors) / $this->sampleCount; if ($this->earlyStop($theta)) { break; @@ -65,7 +65,7 @@ public function runOptimization(array $samples, array $targets, \Closure $gradie protected function gradient(array $theta) { $costs = []; - $gradient= []; + $gradient = []; $totalPenalty = 0; foreach ($this->samples as $index => $sample) { diff --git a/src/Phpml/Helper/Optimizer/StochasticGD.php b/src/Phpml/Helper/Optimizer/StochasticGD.php index 82e860a8..df292616 100644 --- a/src/Phpml/Helper/Optimizer/StochasticGD.php +++ b/src/Phpml/Helper/Optimizer/StochasticGD.php @@ -72,7 +72,7 @@ class StochasticGD extends Optimizer * * @var array */ - protected $costValues= []; + protected $costValues = []; /** * Initializes the SGD optimizer for the given number of dimensions @@ -151,8 +151,8 @@ public function setMaxIterations(int $maxIterations) * The cost function to minimize and the gradient of the function are to be * handled by the callback function provided as the third parameter of the method. * - * @param array $samples - * @param array $targets + * @param array $samples + * @param array $targets * @param \Closure $gradientCb * * @return array diff --git a/src/Phpml/Math/Distance/Minkowski.php b/src/Phpml/Math/Distance/Minkowski.php index 07881936..2af835e9 100644 --- a/src/Phpml/Math/Distance/Minkowski.php +++ b/src/Phpml/Math/Distance/Minkowski.php @@ -43,6 +43,6 @@ public function distance(array $a, array $b): float $distance += pow(abs($a[$i] - $b[$i]), $this->lambda); } - return (float)pow($distance, 1 / $this->lambda); + return (float) pow($distance, 1 / $this->lambda); } } diff --git a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php index 642e8b39..c67673b9 100644 --- a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -20,10 +20,12 @@ * * @author Paul Meagher * @license PHP v3.0 + * * @version 1.1 * * Slightly changed to adapt the original code to PHP-ML library * @date 2017/04/11 + * * @author Mustafa Karabulut */ @@ -35,18 +37,21 @@ class EigenvalueDecomposition { /** * Row and column dimension (square matrix). + * * @var int */ private $n; /** * Internal symmetry flag. + * * @var bool */ private $symmetric; /** * Arrays for internal storage of eigenvalues. + * * @var array */ private $d = []; @@ -54,24 +59,28 @@ class EigenvalueDecomposition /** * Array for internal storage of eigenvectors. + * * @var array */ private $V = []; /** * Array for internal storage of nonsymmetric Hessenberg form. + * * @var array */ private $H = []; /** * Working storage for nonsymmetric algorithm. + * * @var array */ private $ort; /** * Used for complex scalar division. + * * @var float */ private $cdivr; @@ -222,7 +231,6 @@ private function tred2() $this->e[0] = 0.0; } - /** * Symmetric tridiagonal QL algorithm. * @@ -239,7 +247,7 @@ private function tql2() $this->e[$this->n - 1] = 0.0; $f = 0.0; $tst1 = 0.0; - $eps = pow(2.0, -52.0); + $eps = pow(2.0, -52.0); for ($l = 0; $l < $this->n; ++$l) { // Find small subdiagonal element @@ -283,9 +291,9 @@ private function tql2() $c3 = $c2; $c2 = $c; $s2 = $s; - $g = $c * $this->e[$i]; - $h = $c * $p; - $r = hypot($p, $this->e[$i]); + $g = $c * $this->e[$i]; + $h = $c * $p; + $r = hypot($p, $this->e[$i]); $this->e[$i + 1] = $s * $r; $s = $this->e[$i] / $r; $c = $p / $r; @@ -295,7 +303,7 @@ private function tql2() for ($k = 0; $k < $this->n; ++$k) { $h = $this->V[$k][$i + 1]; $this->V[$k][$i + 1] = $s * $this->V[$k][$i] + $c * $h; - $this->V[$k][$i] = $c * $this->V[$k][$i] - $s * $h; + $this->V[$k][$i] = $c * $this->V[$k][$i] - $s * $h; } } $p = -$s * $s2 * $c3 * $el1 * $this->e[$l] / $dl1; @@ -330,7 +338,6 @@ private function tql2() } } - /** * Nonsymmetric reduction to Hessenberg form. * @@ -341,7 +348,7 @@ private function tql2() */ private function orthes() { - $low = 0; + $low = 0; $high = $this->n - 1; for ($m = $low + 1; $m <= $high - 1; ++$m) { @@ -451,7 +458,7 @@ private function hqr2() { // Initialize $nn = $this->n; - $n = $nn - 1; + $n = $nn - 1; $low = 0; $high = $nn - 1; $eps = pow(2.0, -52.0); @@ -544,9 +551,9 @@ private function hqr2() // Complex pair } else { $this->d[$n - 1] = $x + $p; - $this->d[$n] = $x + $p; + $this->d[$n] = $x + $p; $this->e[$n - 1] = $z; - $this->e[$n] = -$z; + $this->e[$n] = -$z; } $n = $n - 2; $iter = 0; @@ -747,10 +754,10 @@ private function hqr2() } else { $this->cdiv(0.0, -$this->H[$n - 1][$n], $this->H[$n - 1][$n - 1] - $p, $q); $this->H[$n - 1][$n - 1] = $this->cdivr; - $this->H[$n - 1][$n] = $this->cdivi; + $this->H[$n - 1][$n] = $this->cdivi; } $this->H[$n][$n - 1] = 0.0; - $this->H[$n][$n] = 1.0; + $this->H[$n][$n] = 1.0; for ($i = $n - 2; $i >= 0; --$i) { // double ra,sa,vr,vi; $ra = 0.0; @@ -769,7 +776,7 @@ private function hqr2() if ($this->e[$i] == 0) { $this->cdiv(-$ra, -$sa, $w, $q); $this->H[$i][$n - 1] = $this->cdivr; - $this->H[$i][$n] = $this->cdivi; + $this->H[$i][$n] = $this->cdivi; } else { // Solve complex equations $x = $this->H[$i][$i + 1]; @@ -781,14 +788,14 @@ private function hqr2() } $this->cdiv($x * $r - $z * $ra + $q * $sa, $x * $s - $z * $sa - $q * $ra, $vr, $vi); $this->H[$i][$n - 1] = $this->cdivr; - $this->H[$i][$n] = $this->cdivi; + $this->H[$i][$n] = $this->cdivi; if (abs($x) > (abs($z) + abs($q))) { $this->H[$i + 1][$n - 1] = (-$ra - $w * $this->H[$i][$n - 1] + $q * $this->H[$i][$n]) / $x; - $this->H[$i + 1][$n] = (-$sa - $w * $this->H[$i][$n] - $q * $this->H[$i][$n - 1]) / $x; + $this->H[$i + 1][$n] = (-$sa - $w * $this->H[$i][$n] - $q * $this->H[$i][$n - 1]) / $x; } else { $this->cdiv(-$r - $y * $this->H[$i][$n - 1], -$s - $y * $this->H[$i][$n], $z, $q); $this->H[$i + 1][$n - 1] = $this->cdivr; - $this->H[$i + 1][$n] = $this->cdivi; + $this->H[$i + 1][$n] = $this->cdivi; } } // Overflow control @@ -796,7 +803,7 @@ private function hqr2() if (($eps * $t) * $t > 1) { for ($j = $i; $j <= $n; ++$j) { $this->H[$j][$n - 1] = $this->H[$j][$n - 1] / $t; - $this->H[$j][$n] = $this->H[$j][$n] / $t; + $this->H[$j][$n] = $this->H[$j][$n] / $t; } } } // end else @@ -823,12 +830,11 @@ private function hqr2() $this->V[$i][$j] = $z; } } - } // end hqr2 + } /** * Return the eigenvector matrix * - * @access public * * @return array */ @@ -899,4 +905,4 @@ public function getDiagonalEigenvalues() return $D; } -} // class EigenvalueDecomposition +} diff --git a/src/Phpml/Math/LinearAlgebra/LUDecomposition.php b/src/Phpml/Math/LinearAlgebra/LUDecomposition.php index de6a15da..7a143f1d 100644 --- a/src/Phpml/Math/LinearAlgebra/LUDecomposition.php +++ b/src/Phpml/Math/LinearAlgebra/LUDecomposition.php @@ -17,11 +17,14 @@ * @author Paul Meagher * @author Bartosz Matosiuk * @author Michael Bommarito + * * @version 1.1 + * * @license PHP v3.0 * * Slightly changed to adapt the original code to PHP-ML library * @date 2017/04/24 + * * @author Mustafa Karabulut */ @@ -34,35 +37,39 @@ class LUDecomposition { /** * Decomposition storage + * * @var array */ private $LU = []; /** * Row dimension. + * * @var int */ private $m; /** * Column dimension. + * * @var int */ private $n; /** * Pivot sign. + * * @var int */ private $pivsign; /** * Internal storage of pivot vector. + * * @var array */ private $piv = []; - /** * Constructs Structure to access L, U and piv. * @@ -78,8 +85,8 @@ public function __construct(Matrix $A) // Use a "left-looking", dot-product, Crout/Doolittle algorithm. $this->LU = $A->toArray(); - $this->m = $A->getRows(); - $this->n = $A->getColumns(); + $this->m = $A->getRows(); + $this->n = $A->getColumns(); for ($i = 0; $i < $this->m; ++$i) { $this->piv[$i] = $i; } @@ -128,8 +135,7 @@ public function __construct(Matrix $A) } } } - } // function __construct() - + } /** * Get lower triangular factor. @@ -150,9 +156,9 @@ public function getL() } } } - return new Matrix($L); - } // function getL() + return new Matrix($L); + } /** * Get upper triangular factor. @@ -171,9 +177,9 @@ public function getU() } } } - return new Matrix($U); - } // function getU() + return new Matrix($U); + } /** * Return pivot permutation vector. @@ -183,8 +189,7 @@ public function getU() public function getPivot() { return $this->piv; - } // function getPivot() - + } /** * Alias for getPivot @@ -194,8 +199,7 @@ public function getPivot() public function getDoublePivot() { return $this->getPivot(); - } // function getDoublePivot() - + } /** * Is the matrix nonsingular? @@ -211,8 +215,7 @@ public function isNonsingular() } return true; - } // function isNonsingular() - + } /** * Count determinants @@ -233,8 +236,7 @@ public function det() } return $d; - } // function det() - + } /** * Solve A*X = B @@ -257,7 +259,7 @@ public function solve(Matrix $B) // Copy right hand side with pivoting $nx = $B->getColumns(); - $X = $this->getSubMatrix($B->toArray(), $this->piv, 0, $nx - 1); + $X = $this->getSubMatrix($B->toArray(), $this->piv, 0, $nx - 1); // Solve L*Y = B(piv,:) for ($k = 0; $k < $this->n; ++$k) { for ($i = $k + 1; $i < $this->n; ++$i) { @@ -277,8 +279,9 @@ public function solve(Matrix $B) } } } + return $X; - } // function solve() + } /** * @param array $matrix @@ -302,4 +305,4 @@ protected function getSubMatrix(array $matrix, array $RL, int $j0, int $jF) return $R; } -} // class LUDecomposition +} diff --git a/src/Phpml/Math/Matrix.php b/src/Phpml/Math/Matrix.php index 3c310528..fd91234f 100644 --- a/src/Phpml/Math/Matrix.php +++ b/src/Phpml/Math/Matrix.php @@ -122,7 +122,6 @@ public function getColumnValues($column) return array_column($this->matrix, $column); } - /** * @return float|int * diff --git a/src/Phpml/Math/Statistic/Covariance.php b/src/Phpml/Math/Statistic/Covariance.php index 779b895c..e0a239dc 100644 --- a/src/Phpml/Math/Statistic/Covariance.php +++ b/src/Phpml/Math/Statistic/Covariance.php @@ -80,7 +80,7 @@ public static function fromDataset(array $data, int $i, int $k, bool $sample = t } if ($i < 0 || $k < 0 || $i >= $n || $k >= $n) { - throw new \Exception("Given indices i and k do not match with the dimensionality of data"); + throw new \Exception('Given indices i and k do not match with the dimensionality of data'); } if ($meanX === null || $meanY === null) { diff --git a/src/Phpml/Math/Statistic/Gaussian.php b/src/Phpml/Math/Statistic/Gaussian.php index d09edba3..ae4c9a67 100644 --- a/src/Phpml/Math/Statistic/Gaussian.php +++ b/src/Phpml/Math/Statistic/Gaussian.php @@ -39,7 +39,8 @@ public function pdf(float $value) // Ref: https://en.wikipedia.org/wiki/Normal_distribution $std2 = $this->std ** 2; $mean = $this->mean; - return exp(- (($value - $mean) ** 2) / (2 * $std2)) / sqrt(2 * $std2 * pi()); + + return exp(-(($value - $mean) ** 2) / (2 * $std2)) / sqrt(2 * $std2 * pi()); } /** @@ -55,6 +56,7 @@ public function pdf(float $value) public static function distributionPdf(float $mean, float $std, float $value) { $normal = new self($mean, $std); + return $normal->pdf($value); } } diff --git a/src/Phpml/Math/Statistic/Mean.php b/src/Phpml/Math/Statistic/Mean.php index bd9657ed..6dd9853b 100644 --- a/src/Phpml/Math/Statistic/Mean.php +++ b/src/Phpml/Math/Statistic/Mean.php @@ -34,7 +34,7 @@ public static function median(array $numbers) self::checkArrayLength($numbers); $count = count($numbers); - $middleIndex = (int)floor($count / 2); + $middleIndex = (int) floor($count / 2); sort($numbers, SORT_NUMERIC); $median = $numbers[$middleIndex]; diff --git a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php index b7364b28..21510c4e 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -138,6 +138,7 @@ abstract protected function trainSample(array $sample, $target); /** * @param array $sample + * * @return mixed */ abstract protected function predictSample(array $sample); diff --git a/src/Phpml/Preprocessing/Normalizer.php b/src/Phpml/Preprocessing/Normalizer.php index c61b4478..fc00030b 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -12,7 +12,7 @@ class Normalizer implements Preprocessor { const NORM_L1 = 1; const NORM_L2 = 2; - const NORM_STD= 3; + const NORM_STD = 3; /** * @var int @@ -77,7 +77,7 @@ public function transform(array &$samples) $methods = [ self::NORM_L1 => 'normalizeL1', self::NORM_L2 => 'normalizeL2', - self::NORM_STD=> 'normalizeSTD' + self::NORM_STD => 'normalizeSTD' ]; $method = $methods[$this->norm]; @@ -117,7 +117,7 @@ private function normalizeL2(array &$sample) foreach ($sample as $feature) { $norm2 += $feature * $feature; } - $norm2 = sqrt((float)$norm2); + $norm2 = sqrt((float) $norm2); if (0 == $norm2) { $sample = array_fill(0, count($sample), 1); diff --git a/tests/Phpml/Classification/DecisionTreeTest.php b/tests/Phpml/Classification/DecisionTreeTest.php index db2d8106..3b61166d 100644 --- a/tests/Phpml/Classification/DecisionTreeTest.php +++ b/tests/Phpml/Classification/DecisionTreeTest.php @@ -11,20 +11,20 @@ class DecisionTreeTest extends TestCase { private $data = [ - ['sunny', 85, 85, 'false', 'Dont_play' ], - ['sunny', 80, 90, 'true', 'Dont_play' ], - ['overcast', 83, 78, 'false', 'Play' ], - ['rain', 70, 96, 'false', 'Play' ], - ['rain', 68, 80, 'false', 'Play' ], - ['rain', 65, 70, 'true', 'Dont_play' ], - ['overcast', 64, 65, 'true', 'Play' ], - ['sunny', 72, 95, 'false', 'Dont_play' ], - ['sunny', 69, 70, 'false', 'Play' ], - ['rain', 75, 80, 'false', 'Play' ], - ['sunny', 75, 70, 'true', 'Play' ], - ['overcast', 72, 90, 'true', 'Play' ], - ['overcast', 81, 75, 'false', 'Play' ], - ['rain', 71, 80, 'true', 'Dont_play' ] + ['sunny', 85, 85, 'false', 'Dont_play'], + ['sunny', 80, 90, 'true', 'Dont_play'], + ['overcast', 83, 78, 'false', 'Play'], + ['rain', 70, 96, 'false', 'Play'], + ['rain', 68, 80, 'false', 'Play'], + ['rain', 65, 70, 'true', 'Dont_play'], + ['overcast', 64, 65, 'true', 'Play'], + ['sunny', 72, 95, 'false', 'Dont_play'], + ['sunny', 69, 70, 'false', 'Play'], + ['rain', 75, 80, 'false', 'Play'], + ['sunny', 75, 70, 'true', 'Play'], + ['overcast', 72, 90, 'true', 'Play'], + ['overcast', 81, 75, 'false', 'Play'], + ['rain', 71, 80, 'true', 'Dont_play'] ]; private $extraData = [ @@ -38,6 +38,7 @@ private function getData($input) array_walk($input, function (&$v) { array_splice($v, 4, 1); }); + return [$input, $targets]; } @@ -54,6 +55,7 @@ public function testPredictSingleSample() $classifier->train($data, $targets); $this->assertEquals('Dont_play', $classifier->predict(['scorching', 95, 90, 'true'])); $this->assertEquals('Play', $classifier->predict(['overcast', 60, 60, 'false'])); + return $classifier; } diff --git a/tests/Phpml/Classification/Ensemble/BaggingTest.php b/tests/Phpml/Classification/Ensemble/BaggingTest.php index 002697e0..a00b1762 100644 --- a/tests/Phpml/Classification/Ensemble/BaggingTest.php +++ b/tests/Phpml/Classification/Ensemble/BaggingTest.php @@ -13,25 +13,25 @@ class BaggingTest extends TestCase { private $data = [ - ['sunny', 85, 85, 'false', 'Dont_play' ], - ['sunny', 80, 90, 'true', 'Dont_play' ], - ['overcast', 83, 78, 'false', 'Play' ], - ['rain', 70, 96, 'false', 'Play' ], - ['rain', 68, 80, 'false', 'Play' ], - ['rain', 65, 70, 'true', 'Dont_play' ], - ['overcast', 64, 65, 'true', 'Play' ], - ['sunny', 72, 95, 'false', 'Dont_play' ], - ['sunny', 69, 70, 'false', 'Play' ], - ['rain', 75, 80, 'false', 'Play' ], - ['sunny', 75, 70, 'true', 'Play' ], - ['overcast', 72, 90, 'true', 'Play' ], - ['overcast', 81, 75, 'false', 'Play' ], - ['rain', 71, 80, 'true', 'Dont_play' ] + ['sunny', 85, 85, 'false', 'Dont_play'], + ['sunny', 80, 90, 'true', 'Dont_play'], + ['overcast', 83, 78, 'false', 'Play'], + ['rain', 70, 96, 'false', 'Play'], + ['rain', 68, 80, 'false', 'Play'], + ['rain', 65, 70, 'true', 'Dont_play'], + ['overcast', 64, 65, 'true', 'Play'], + ['sunny', 72, 95, 'false', 'Dont_play'], + ['sunny', 69, 70, 'false', 'Play'], + ['rain', 75, 80, 'false', 'Play'], + ['sunny', 75, 70, 'true', 'Play'], + ['overcast', 72, 90, 'true', 'Play'], + ['overcast', 81, 75, 'false', 'Play'], + ['rain', 71, 80, 'true', 'Dont_play'] ]; private $extraData = [ - ['scorching', 90, 95, 'false', 'Dont_play'], - ['scorching', 0, 0, 'false', 'Dont_play'], + ['scorching', 90, 95, 'false', 'Dont_play'], + ['scorching', 0, 0, 'false', 'Dont_play'], ]; public function testPredictSingleSample() @@ -97,6 +97,7 @@ protected function getClassifier($numBaseClassifiers = 50) $classifier = new Bagging($numBaseClassifiers); $classifier->setSubsetRatio(1.0); $classifier->setClassifer(DecisionTree::class, ['depth' => 10]); + return $classifier; } @@ -104,7 +105,7 @@ protected function getAvailableBaseClassifiers() { return [ DecisionTree::class => ['depth' => 5], - NaiveBayes::class => [] + NaiveBayes::class => [] ]; } @@ -113,7 +114,7 @@ private function getData($input) // Populating input data to a size large enough // for base classifiers that they can work with a subset of it $populated = []; - for ($i=0; $i<20; $i++) { + for ($i = 0; $i < 20; ++$i) { $populated = array_merge($populated, $input); } shuffle($populated); @@ -121,6 +122,7 @@ private function getData($input) array_walk($populated, function (&$v) { array_splice($v, 4, 1); }); + return [$populated, $targets]; } } diff --git a/tests/Phpml/Classification/Ensemble/RandomForestTest.php b/tests/Phpml/Classification/Ensemble/RandomForestTest.php index be587efe..84688936 100644 --- a/tests/Phpml/Classification/Ensemble/RandomForestTest.php +++ b/tests/Phpml/Classification/Ensemble/RandomForestTest.php @@ -14,12 +14,13 @@ protected function getClassifier($numBaseClassifiers = 50) { $classifier = new RandomForest($numBaseClassifiers); $classifier->setFeatureSubsetRatio('log'); + return $classifier; } protected function getAvailableBaseClassifiers() { - return [ DecisionTree::class => ['depth' => 5] ]; + return [DecisionTree::class => ['depth' => 5]]; } public function testOtherBaseClassifier() diff --git a/tests/Phpml/Classification/MLPClassifierTest.php b/tests/Phpml/Classification/MLPClassifierTest.php index 3a009c38..db30afd2 100644 --- a/tests/Phpml/Classification/MLPClassifierTest.php +++ b/tests/Phpml/Classification/MLPClassifierTest.php @@ -180,6 +180,7 @@ public function testThrowExceptionOnInvalidPartialTrainingClasses() [0, 1, 2] ); } + /** * @expectedException \Phpml\Exception\InvalidArgumentException */ diff --git a/tests/Phpml/Clustering/FuzzyCMeansTest.php b/tests/Phpml/Clustering/FuzzyCMeansTest.php index 85285b27..68cc0dbb 100644 --- a/tests/Phpml/Clustering/FuzzyCMeansTest.php +++ b/tests/Phpml/Clustering/FuzzyCMeansTest.php @@ -21,6 +21,7 @@ public function testFCMSamplesClustering() } } $this->assertCount(0, $samples); + return $fcm; } diff --git a/tests/Phpml/DimensionReduction/LDATest.php b/tests/Phpml/DimensionReduction/LDATest.php index 5ebe0187..713e205e 100644 --- a/tests/Phpml/DimensionReduction/LDATest.php +++ b/tests/Phpml/DimensionReduction/LDATest.php @@ -57,7 +57,7 @@ public function testLDA() // for each projected row foreach ($data as $i => $row) { $newRow = [$transformed2[$i]]; - $newRow2= $lda->transform($row); + $newRow2 = $lda->transform($row); array_map($check, $newRow, $newRow2); } diff --git a/tests/Phpml/DimensionReduction/PCATest.php b/tests/Phpml/DimensionReduction/PCATest.php index 8f65e98d..a4784f9b 100644 --- a/tests/Phpml/DimensionReduction/PCATest.php +++ b/tests/Phpml/DimensionReduction/PCATest.php @@ -47,7 +47,7 @@ public function testPCA() // same dimensionality with the original dataset foreach ($data as $i => $row) { $newRow = [[$transformed[$i]]]; - $newRow2= $pca->transform($row); + $newRow2 = $pca->transform($row); array_map(function ($val1, $val2) use ($epsilon) { $this->assertEquals(abs($val1), abs($val2), '', $epsilon); diff --git a/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php b/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php index 4bca1bda..e2c615ef 100644 --- a/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php +++ b/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php @@ -22,7 +22,7 @@ public function testSymmetricMatrixEigenPairs() [0.614444444, 0.716555556] ]; $knownEigvalues = [0.0490833989, 1.28402771]; - $knownEigvectors= [[-0.735178656, 0.677873399], [-0.677873399, -0.735178656]]; + $knownEigvectors = [[-0.735178656, 0.677873399], [-0.677873399, -0.735178656]]; $decomp = new EigenvalueDecomposition($matrix); $eigVectors = $decomp->getEigenvectors(); @@ -37,8 +37,8 @@ public function testSymmetricMatrixEigenPairs() $len = 3; $A = array_fill(0, $len, array_fill(0, $len, 0.0)); srand(intval(microtime(true) * 1000)); - for ($i=0; $i < $len; $i++) { - for ($k=0; $k < $len; $k++) { + for ($i = 0; $i < $len; ++$i) { + for ($k = 0; $k < $len; ++$k) { if ($i > $k) { $A[$i][$k] = $A[$k][$i]; } else { @@ -49,7 +49,7 @@ public function testSymmetricMatrixEigenPairs() $decomp = new EigenvalueDecomposition($A); $eigValues = $decomp->getRealEigenvalues(); - $eigVectors= $decomp->getEigenvectors(); + $eigVectors = $decomp->getEigenvectors(); foreach ($eigValues as $index => $lambda) { $m1 = new Matrix($A); @@ -57,7 +57,7 @@ public function testSymmetricMatrixEigenPairs() // A.v=λ.v $leftSide = $m1->multiply($m2)->toArray(); - $rightSide= $m2->multiplyByScalar($lambda)->toArray(); + $rightSide = $m2->multiplyByScalar($lambda)->toArray(); $this->assertEquals($leftSide, $rightSide, '', $epsilon); } diff --git a/tests/Phpml/Math/Statistic/GaussianTest.php b/tests/Phpml/Math/Statistic/GaussianTest.php index 6bbf63b8..a0c9700f 100644 --- a/tests/Phpml/Math/Statistic/GaussianTest.php +++ b/tests/Phpml/Math/Statistic/GaussianTest.php @@ -12,12 +12,12 @@ class GaussianTest extends TestCase public function testPdf() { $std = 1.0; - $mean= 0.0; + $mean = 0.0; $g = new Gaussian($mean, $std); // Allowable error $delta = 0.001; - $x = [0, 0.1, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0]; + $x = [0, 0.1, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0]; $pdf = [0.3989, 0.3969, 0.3520, 0.2419, 0.1295, 0.0539, 0.0175, 0.0044]; foreach ($x as $i => $v) { $this->assertEquals($pdf[$i], $g->pdf($v), '', $delta); diff --git a/tests/Phpml/ModelManagerTest.php b/tests/Phpml/ModelManagerTest.php index 066aad14..400b6d1b 100644 --- a/tests/Phpml/ModelManagerTest.php +++ b/tests/Phpml/ModelManagerTest.php @@ -13,7 +13,7 @@ class ModelManagerTest extends TestCase public function testSaveAndRestore() { $filename = uniqid(); - $filepath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $filename; + $filepath = sys_get_temp_dir().DIRECTORY_SEPARATOR.$filename; $estimator = new LeastSquares(); $modelManager = new ModelManager(); @@ -28,7 +28,7 @@ public function testSaveAndRestore() */ public function testRestoreWrongFile() { - $filepath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'unexisting'; + $filepath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'unexisting'; $modelManager = new ModelManager(); $modelManager->restoreFromFile($filepath); } diff --git a/tests/Phpml/Preprocessing/NormalizerTest.php b/tests/Phpml/Preprocessing/NormalizerTest.php index a8a8826c..5c8efb18 100644 --- a/tests/Phpml/Preprocessing/NormalizerTest.php +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -106,9 +106,9 @@ public function testStandardNorm() // Generate 10 random vectors of length 3 $samples = []; srand(time()); - for ($i=0; $i<10; $i++) { + for ($i = 0; $i < 10; ++$i) { $sample = array_fill(0, 3, 0); - for ($k=0; $k<3; $k++) { + for ($k = 0; $k < 3; ++$k) { $sample[$k] = rand(1, 100); } // Last feature's value shared across samples. From 136a92c82b402a6378b5edd2c748e8484508e1a2 Mon Sep 17 00:00:00 2001 From: Yuji Uchiyama Date: Mon, 21 Aug 2017 15:08:54 +0900 Subject: [PATCH 188/328] Support CSV with long lines (#119) --- src/Phpml/Dataset/CsvDataset.php | 7 ++++--- tests/Phpml/Dataset/CsvDatasetTest.php | 10 ++++++++++ tests/Phpml/Dataset/Resources/longdataset.csv | 1 + 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 tests/Phpml/Dataset/Resources/longdataset.csv diff --git a/src/Phpml/Dataset/CsvDataset.php b/src/Phpml/Dataset/CsvDataset.php index b2e94077..65ff5159 100644 --- a/src/Phpml/Dataset/CsvDataset.php +++ b/src/Phpml/Dataset/CsvDataset.php @@ -18,10 +18,11 @@ class CsvDataset extends ArrayDataset * @param int $features * @param bool $headingRow * @param string $delimiter + * @param int $maxLineLength * * @throws FileException */ - public function __construct(string $filepath, int $features, bool $headingRow = true, string $delimiter = ',') + public function __construct(string $filepath, int $features, bool $headingRow = true, string $delimiter = ',', int $maxLineLength = 0) { if (!file_exists($filepath)) { throw FileException::missingFile(basename($filepath)); @@ -32,14 +33,14 @@ public function __construct(string $filepath, int $features, bool $headingRow = } if ($headingRow) { - $data = fgetcsv($handle, 1000, $delimiter); + $data = fgetcsv($handle, $maxLineLength, $delimiter); $this->columnNames = array_slice($data, 0, $features); } else { $this->columnNames = range(0, $features - 1); } $samples = $targets = []; - while (($data = fgetcsv($handle, 1000, $delimiter)) !== false) { + while (($data = fgetcsv($handle, $maxLineLength, $delimiter)) !== false) { $samples[] = array_slice($data, 0, $features); $targets[] = $data[$features]; } diff --git a/tests/Phpml/Dataset/CsvDatasetTest.php b/tests/Phpml/Dataset/CsvDatasetTest.php index 44a112b2..a3a377e2 100644 --- a/tests/Phpml/Dataset/CsvDatasetTest.php +++ b/tests/Phpml/Dataset/CsvDatasetTest.php @@ -36,4 +36,14 @@ public function testSampleCsvDatasetWithoutHeaderRow() $this->assertCount(11, $dataset->getSamples()); $this->assertCount(11, $dataset->getTargets()); } + + public function testLongCsvDataset() + { + $filePath = dirname(__FILE__).'/Resources/longdataset.csv'; + + $dataset = new CsvDataset($filePath, 1000, false); + + $this->assertCount(1000, $dataset->getSamples()[0]); + $this->assertEquals('label', $dataset->getTargets()[0]); + } } diff --git a/tests/Phpml/Dataset/Resources/longdataset.csv b/tests/Phpml/Dataset/Resources/longdataset.csv new file mode 100644 index 00000000..f8f3c40e --- /dev/null +++ b/tests/Phpml/Dataset/Resources/longdataset.csv @@ -0,0 +1 @@ +1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754,755,756,757,758,759,760,761,762,763,764,765,766,767,768,769,770,771,772,773,774,775,776,777,778,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854,855,856,857,858,859,860,861,862,863,864,865,866,867,868,869,870,871,872,873,874,875,876,877,878,879,880,881,882,883,884,885,886,887,888,889,890,891,892,893,894,895,896,897,898,899,900,901,902,903,904,905,906,907,908,909,910,911,912,913,914,915,916,917,918,919,920,921,922,923,924,925,926,927,928,929,930,931,932,933,934,935,936,937,938,939,940,941,942,943,944,945,946,947,948,949,950,951,952,953,954,955,956,957,958,959,960,961,962,963,964,965,966,967,968,969,970,971,972,973,974,975,976,977,978,979,980,981,982,983,984,985,986,987,988,989,990,991,992,993,994,995,996,997,998,999,1000,label From 3e2708de178c2a5c278036c7f63b81eff9bd4b0b Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Mon, 28 Aug 2017 13:00:24 +0200 Subject: [PATCH 189/328] Fix #120 (#121) * Fix #120 * Add DecisionTreeLeafTest --- .../DecisionTree/DecisionTreeLeaf.php | 4 ++- .../DecisionTree/DecisionTreeLeafTest.php | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 tests/Phpml/Classification/DecisionTree/DecisionTreeLeafTest.php diff --git a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php index 53c3386d..9002458e 100644 --- a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -138,9 +138,11 @@ public function getHTML($columnNames = null) } else { $col = "col_$this->columnIndex"; } - if (!preg_match('/^[<>=]{1,2}/', $value)) { + + if (!preg_match('/^[<>=]{1,2}/', strval($value))) { $value = "=$value"; } + $value = "$col $value
Gini: ".number_format($this->giniIndex, 2); } diff --git a/tests/Phpml/Classification/DecisionTree/DecisionTreeLeafTest.php b/tests/Phpml/Classification/DecisionTree/DecisionTreeLeafTest.php new file mode 100644 index 00000000..72f1b956 --- /dev/null +++ b/tests/Phpml/Classification/DecisionTree/DecisionTreeLeafTest.php @@ -0,0 +1,26 @@ +value = 1; + $leaf->columnIndex = 0; + + $rightLeaf = new DecisionTreeLeaf(); + $rightLeaf->value = '<= 2'; + $rightLeaf->columnIndex = 1; + + $leaf->rightLeaf = $rightLeaf; + + $this->assertEquals('
col_0 =1
Gini: 0.00
 No |
col_1 <= 2
Gini: 0.00
', $leaf->getHTML()); + } +} From cacfd64a6f147edd2057eea238b0c5a6e6f11563 Mon Sep 17 00:00:00 2001 From: Gary Fuller Date: Sat, 2 Sep 2017 20:24:51 +0100 Subject: [PATCH 190/328] Update README.md (#99) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9adddb23..9d649c53 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ To find out how to use PHP-ML follow [Documentation](http://php-ml.readthedocs.o ## Installation -Currently this library is in the process of developing, but You can install it with Composer: +Currently this library is in the process of being developed, but You can install it with Composer: ``` composer require php-ai/php-ml From 0e59cfb174facf1e085d4ab9a91d82a749b55816 Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Sat, 2 Sep 2017 21:30:35 +0200 Subject: [PATCH 191/328] Add ThresholdedReLU activation function (#129) --- .../ActivationFunction/ThresholdedReLU.php | 33 ++++++++++++++++ .../ThresholdedReLUTest.php | 38 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php create mode 100644 tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php b/src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php new file mode 100644 index 00000000..0963e093 --- /dev/null +++ b/src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php @@ -0,0 +1,33 @@ +theta = $theta; + } + + /** + * @param float|int $value + * + * @return float + */ + public function compute($value): float + { + return $value > $this->theta ? $value : 0.0; + } +} diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php new file mode 100644 index 00000000..b0c6ecf8 --- /dev/null +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php @@ -0,0 +1,38 @@ +assertEquals($expected, $thresholdedReLU->compute($value)); + } + + /** + * @return array + */ + public function thresholdProvider() + { + return [ + [1.0, 0, 1.0], + [0.5, 3.75, 3.75], + [0.0, 0.5, 0.5], + [0.9, 0, 0.1] + ]; + } +} From b1be0574d8b4f0870b195fb51ae823f613b8b0cd Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Sat, 2 Sep 2017 21:31:14 +0200 Subject: [PATCH 192/328] Add PReLU activation function (#128) * Implement RELU activation functions * Add PReLUTest --- .../ActivationFunction/PReLU.php | 33 ++++++++++++++++ .../ActivationFunction/PReLUTest.php | 39 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php create mode 100644 tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php b/src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php new file mode 100644 index 00000000..76674a08 --- /dev/null +++ b/src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php @@ -0,0 +1,33 @@ +beta = $beta; + } + + /** + * @param float|int $value + * + * @return float + */ + public function compute($value): float + { + return $value >= 0 ? $value : $this->beta * $value; + } +} diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php new file mode 100644 index 00000000..a390bf07 --- /dev/null +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php @@ -0,0 +1,39 @@ +assertEquals($expected, $prelu->compute($value), '', 0.001); + } + + /** + * @return array + */ + public function preluProvider() + { + return [ + [0.01, 0.367, 0.367], + [0.0, 1, 1], + [0.3, -0.3, -1], + [0.9, 3, 3], + [0.02, -0.06, -3], + ]; + } +} From 03751f51ede45425ce0b47e11eab56bc8fdb7523 Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Sat, 2 Sep 2017 21:38:02 +0200 Subject: [PATCH 193/328] Speed up DataTransformer (#122) --- src/Phpml/SupportVectorMachine/DataTransformer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Phpml/SupportVectorMachine/DataTransformer.php b/src/Phpml/SupportVectorMachine/DataTransformer.php index 238a7d2e..ad5e180f 100644 --- a/src/Phpml/SupportVectorMachine/DataTransformer.php +++ b/src/Phpml/SupportVectorMachine/DataTransformer.php @@ -57,7 +57,7 @@ public static function predictions(string $rawPredictions, array $labels): array $numericLabels = self::numericLabels($labels); $results = []; foreach (explode(PHP_EOL, $rawPredictions) as $result) { - if (strlen($result) > 0) { + if (isset($result[0])) { $results[] = array_search($result, $numericLabels); } } From 8c06a55a162fe96c2d594ef3b2d9b6fbf4c3a7e9 Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Sat, 2 Sep 2017 21:39:59 +0200 Subject: [PATCH 194/328] Make tests namespace consistent (#125) --- tests/Phpml/Association/AprioriTest.php | 2 +- .../Phpml/Classification/DecisionTree/DecisionTreeLeafTest.php | 2 +- tests/Phpml/Classification/DecisionTreeTest.php | 2 +- tests/Phpml/Classification/Ensemble/AdaBoostTest.php | 2 +- tests/Phpml/Classification/Ensemble/BaggingTest.php | 2 +- tests/Phpml/Classification/Ensemble/RandomForestTest.php | 2 +- tests/Phpml/Classification/KNearestNeighborsTest.php | 2 +- tests/Phpml/Classification/Linear/AdalineTest.php | 2 +- tests/Phpml/Classification/Linear/DecisionStumpTest.php | 2 +- tests/Phpml/Classification/Linear/PerceptronTest.php | 2 +- tests/Phpml/Classification/NaiveBayesTest.php | 2 +- tests/Phpml/Classification/SVCTest.php | 2 +- tests/Phpml/Clustering/DBSCANTest.php | 2 +- tests/Phpml/Clustering/FuzzyCMeansTest.php | 2 +- tests/Phpml/Clustering/KMeansTest.php | 2 +- tests/Phpml/DimensionReduction/KernelPCATest.php | 2 +- tests/Phpml/DimensionReduction/LDATest.php | 2 +- tests/Phpml/DimensionReduction/PCATest.php | 2 +- tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php | 2 +- tests/Phpml/Math/Statistic/CovarianceTest.php | 2 +- tests/Phpml/Preprocessing/ImputerTest.php | 2 +- tests/Phpml/Preprocessing/NormalizerTest.php | 2 +- tests/Phpml/Regression/LeastSquaresTest.php | 2 +- tests/Phpml/Regression/SVRTest.php | 2 +- tests/Phpml/SupportVectorMachine/DataTransformerTest.php | 2 +- tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php | 2 +- tests/Phpml/Tokenization/WhitespaceTokenizerTest.php | 2 +- tests/Phpml/Tokenization/WordTokenizerTest.php | 2 +- 28 files changed, 28 insertions(+), 28 deletions(-) diff --git a/tests/Phpml/Association/AprioriTest.php b/tests/Phpml/Association/AprioriTest.php index 65d5ea5f..c715d6f9 100644 --- a/tests/Phpml/Association/AprioriTest.php +++ b/tests/Phpml/Association/AprioriTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Classification; +namespace tests\Phpml\Classification; use Phpml\Association\Apriori; use Phpml\ModelManager; diff --git a/tests/Phpml/Classification/DecisionTree/DecisionTreeLeafTest.php b/tests/Phpml/Classification/DecisionTree/DecisionTreeLeafTest.php index 72f1b956..854e4e5f 100644 --- a/tests/Phpml/Classification/DecisionTree/DecisionTreeLeafTest.php +++ b/tests/Phpml/Classification/DecisionTree/DecisionTreeLeafTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Classification\DecisionTree; +namespace tests\Phpml\Classification\DecisionTree; use Phpml\Classification\DecisionTree\DecisionTreeLeaf; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/Classification/DecisionTreeTest.php b/tests/Phpml/Classification/DecisionTreeTest.php index 3b61166d..f0ebc04b 100644 --- a/tests/Phpml/Classification/DecisionTreeTest.php +++ b/tests/Phpml/Classification/DecisionTreeTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Classification; +namespace tests\Phpml\Classification; use Phpml\Classification\DecisionTree; use Phpml\ModelManager; diff --git a/tests/Phpml/Classification/Ensemble/AdaBoostTest.php b/tests/Phpml/Classification/Ensemble/AdaBoostTest.php index 8c177520..8b8d0db2 100644 --- a/tests/Phpml/Classification/Ensemble/AdaBoostTest.php +++ b/tests/Phpml/Classification/Ensemble/AdaBoostTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Classification\Ensemble; +namespace tests\Phpml\Classification\Ensemble; use Phpml\Classification\Ensemble\AdaBoost; use Phpml\ModelManager; diff --git a/tests/Phpml/Classification/Ensemble/BaggingTest.php b/tests/Phpml/Classification/Ensemble/BaggingTest.php index a00b1762..a158e3e3 100644 --- a/tests/Phpml/Classification/Ensemble/BaggingTest.php +++ b/tests/Phpml/Classification/Ensemble/BaggingTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Classification\Ensemble; +namespace tests\Phpml\Classification\Ensemble; use Phpml\Classification\Ensemble\Bagging; use Phpml\Classification\DecisionTree; diff --git a/tests/Phpml/Classification/Ensemble/RandomForestTest.php b/tests/Phpml/Classification/Ensemble/RandomForestTest.php index 84688936..cc1cd0fd 100644 --- a/tests/Phpml/Classification/Ensemble/RandomForestTest.php +++ b/tests/Phpml/Classification/Ensemble/RandomForestTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Classification\Ensemble; +namespace tests\Phpml\Classification\Ensemble; use Phpml\Classification\Ensemble\RandomForest; use Phpml\Classification\DecisionTree; diff --git a/tests/Phpml/Classification/KNearestNeighborsTest.php b/tests/Phpml/Classification/KNearestNeighborsTest.php index ea9db77a..aeee5ef8 100644 --- a/tests/Phpml/Classification/KNearestNeighborsTest.php +++ b/tests/Phpml/Classification/KNearestNeighborsTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Classification; +namespace tests\Phpml\Classification; use Phpml\Classification\KNearestNeighbors; use Phpml\Math\Distance\Chebyshev; diff --git a/tests/Phpml/Classification/Linear/AdalineTest.php b/tests/Phpml/Classification/Linear/AdalineTest.php index da695987..59894626 100644 --- a/tests/Phpml/Classification/Linear/AdalineTest.php +++ b/tests/Phpml/Classification/Linear/AdalineTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Classification\Linear; +namespace tests\Phpml\Classification\Linear; use Phpml\Classification\Linear\Adaline; use Phpml\ModelManager; diff --git a/tests/Phpml/Classification/Linear/DecisionStumpTest.php b/tests/Phpml/Classification/Linear/DecisionStumpTest.php index 4060ce33..678544ea 100644 --- a/tests/Phpml/Classification/Linear/DecisionStumpTest.php +++ b/tests/Phpml/Classification/Linear/DecisionStumpTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Classification\Linear; +namespace tests\Phpml\Classification\Linear; use Phpml\Classification\Linear\DecisionStump; use Phpml\ModelManager; diff --git a/tests/Phpml/Classification/Linear/PerceptronTest.php b/tests/Phpml/Classification/Linear/PerceptronTest.php index 132a6d79..c01da336 100644 --- a/tests/Phpml/Classification/Linear/PerceptronTest.php +++ b/tests/Phpml/Classification/Linear/PerceptronTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Classification\Linear; +namespace tests\Phpml\Classification\Linear; use Phpml\Classification\Linear\Perceptron; use Phpml\ModelManager; diff --git a/tests/Phpml/Classification/NaiveBayesTest.php b/tests/Phpml/Classification/NaiveBayesTest.php index 9b2171af..82c69eb5 100644 --- a/tests/Phpml/Classification/NaiveBayesTest.php +++ b/tests/Phpml/Classification/NaiveBayesTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Classification; +namespace tests\Phpml\Classification; use Phpml\Classification\NaiveBayes; use Phpml\ModelManager; diff --git a/tests/Phpml/Classification/SVCTest.php b/tests/Phpml/Classification/SVCTest.php index 89c27e3e..f143eef1 100644 --- a/tests/Phpml/Classification/SVCTest.php +++ b/tests/Phpml/Classification/SVCTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Classification; +namespace tests\Phpml\Classification; use Phpml\Classification\SVC; use Phpml\SupportVectorMachine\Kernel; diff --git a/tests/Phpml/Clustering/DBSCANTest.php b/tests/Phpml/Clustering/DBSCANTest.php index 2d959acd..31fc1e60 100644 --- a/tests/Phpml/Clustering/DBSCANTest.php +++ b/tests/Phpml/Clustering/DBSCANTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Clustering; +namespace tests\Phpml\Clustering; use Phpml\Clustering\DBSCAN; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/Clustering/FuzzyCMeansTest.php b/tests/Phpml/Clustering/FuzzyCMeansTest.php index 68cc0dbb..39f26438 100644 --- a/tests/Phpml/Clustering/FuzzyCMeansTest.php +++ b/tests/Phpml/Clustering/FuzzyCMeansTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Clustering; +namespace tests\Phpml\Clustering; use Phpml\Clustering\FuzzyCMeans; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/Clustering/KMeansTest.php b/tests/Phpml/Clustering/KMeansTest.php index c25306b9..43d41ee8 100644 --- a/tests/Phpml/Clustering/KMeansTest.php +++ b/tests/Phpml/Clustering/KMeansTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Clustering; +namespace tests\Phpml\Clustering; use Phpml\Clustering\KMeans; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/DimensionReduction/KernelPCATest.php b/tests/Phpml/DimensionReduction/KernelPCATest.php index 14b2d7d2..e586fc88 100644 --- a/tests/Phpml/DimensionReduction/KernelPCATest.php +++ b/tests/Phpml/DimensionReduction/KernelPCATest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\DimensionReduction; +namespace tests\Phpml\DimensionReduction; use Phpml\DimensionReduction\KernelPCA; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/DimensionReduction/LDATest.php b/tests/Phpml/DimensionReduction/LDATest.php index 713e205e..637fb1e3 100644 --- a/tests/Phpml/DimensionReduction/LDATest.php +++ b/tests/Phpml/DimensionReduction/LDATest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\DimensionReduction; +namespace tests\Phpml\DimensionReduction; use Phpml\DimensionReduction\LDA; use Phpml\Dataset\Demo\IrisDataset; diff --git a/tests/Phpml/DimensionReduction/PCATest.php b/tests/Phpml/DimensionReduction/PCATest.php index a4784f9b..c26782af 100644 --- a/tests/Phpml/DimensionReduction/PCATest.php +++ b/tests/Phpml/DimensionReduction/PCATest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\DimensionReduction; +namespace tests\Phpml\DimensionReduction; use Phpml\DimensionReduction\PCA; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php b/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php index e2c615ef..8293d5e0 100644 --- a/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php +++ b/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Math\LinearAlgebra; +namespace tests\Phpml\Math\LinearAlgebra; use Phpml\Math\LinearAlgebra\EigenvalueDecomposition; use Phpml\Math\Matrix; diff --git a/tests/Phpml/Math/Statistic/CovarianceTest.php b/tests/Phpml/Math/Statistic/CovarianceTest.php index 4b025a3a..d2abe7b5 100644 --- a/tests/Phpml/Math/Statistic/CovarianceTest.php +++ b/tests/Phpml/Math/Statistic/CovarianceTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Math\Statistic; +namespace tests\Phpml\Math\Statistic; use Phpml\Math\Statistic\Covariance; use Phpml\Math\Statistic\Mean; diff --git a/tests/Phpml/Preprocessing/ImputerTest.php b/tests/Phpml/Preprocessing/ImputerTest.php index e3e08527..2f5c31b2 100644 --- a/tests/Phpml/Preprocessing/ImputerTest.php +++ b/tests/Phpml/Preprocessing/ImputerTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Preprocessing; +namespace tests\Phpml\Preprocessing; use Phpml\Preprocessing\Imputer; use Phpml\Preprocessing\Imputer\Strategy\MeanStrategy; diff --git a/tests/Phpml/Preprocessing/NormalizerTest.php b/tests/Phpml/Preprocessing/NormalizerTest.php index 5c8efb18..542eef57 100644 --- a/tests/Phpml/Preprocessing/NormalizerTest.php +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Preprocessing; +namespace tests\Phpml\Preprocessing; use Phpml\Preprocessing\Normalizer; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/Regression/LeastSquaresTest.php b/tests/Phpml/Regression/LeastSquaresTest.php index 96c97dc0..f4405f42 100644 --- a/tests/Phpml/Regression/LeastSquaresTest.php +++ b/tests/Phpml/Regression/LeastSquaresTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Regression; +namespace tests\Phpml\Regression; use Phpml\Regression\LeastSquares; use Phpml\ModelManager; diff --git a/tests/Phpml/Regression/SVRTest.php b/tests/Phpml/Regression/SVRTest.php index d3349737..e8f77def 100644 --- a/tests/Phpml/Regression/SVRTest.php +++ b/tests/Phpml/Regression/SVRTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Regression; +namespace tests\Phpml\Regression; use Phpml\Regression\SVR; use Phpml\SupportVectorMachine\Kernel; diff --git a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php b/tests/Phpml/SupportVectorMachine/DataTransformerTest.php index 33d52c0b..9aa15584 100644 --- a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php +++ b/tests/Phpml/SupportVectorMachine/DataTransformerTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\SupportVectorMachine; +namespace tests\Phpml\SupportVectorMachine; use Phpml\SupportVectorMachine\DataTransformer; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php index ca9f299b..19538298 100644 --- a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\SupportVectorMachine; +namespace tests\Phpml\SupportVectorMachine; use Phpml\SupportVectorMachine\Kernel; use Phpml\SupportVectorMachine\SupportVectorMachine; diff --git a/tests/Phpml/Tokenization/WhitespaceTokenizerTest.php b/tests/Phpml/Tokenization/WhitespaceTokenizerTest.php index 1133835c..97fa8330 100644 --- a/tests/Phpml/Tokenization/WhitespaceTokenizerTest.php +++ b/tests/Phpml/Tokenization/WhitespaceTokenizerTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Tokenization; +namespace tests\Phpml\Tokenization; use Phpml\Tokenization\WhitespaceTokenizer; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/Tokenization/WordTokenizerTest.php b/tests/Phpml/Tokenization/WordTokenizerTest.php index 3a6abba3..09db2225 100644 --- a/tests/Phpml/Tokenization/WordTokenizerTest.php +++ b/tests/Phpml/Tokenization/WordTokenizerTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Tokenization; +namespace tests\Phpml\Tokenization; use Phpml\Tokenization\WordTokenizer; use PHPUnit\Framework\TestCase; From ba2b8c8a9ccb1ce072d49d835b0d4b12cc0a4a62 Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Sat, 2 Sep 2017 21:41:06 +0200 Subject: [PATCH 195/328] Use C-style casts (#124) --- src/Phpml/Classification/DecisionTree.php | 6 +++--- src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php | 4 ++-- src/Phpml/Classification/Linear/DecisionStump.php | 6 +++--- src/Phpml/Classification/Linear/LogisticRegression.php | 2 +- src/Phpml/Classification/Linear/Perceptron.php | 4 ++-- tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php | 3 ++- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Phpml/Classification/DecisionTree.php b/src/Phpml/Classification/DecisionTree.php index 6cf6870f..feab32e4 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -225,9 +225,9 @@ protected function getBestSplit(array $records) : DecisionTreeLeaf // will also be saved into the leaf for future access if ($this->columnTypes[$i] == self::CONTINUOUS) { $matches = []; - preg_match("/^([<>=]{1,2})\s*(.*)/", strval($split->value), $matches); + preg_match("/^([<>=]{1,2})\s*(.*)/", (string) $split->value, $matches); $split->operator = $matches[1]; - $split->numericValue = floatval($matches[2]); + $split->numericValue = (float) $matches[2]; } $bestSplit = $split; @@ -301,7 +301,7 @@ public function getGiniIndex($baseValue, array $colValues, array $targets) : flo $sum = array_sum(array_column($countMatrix, $i)); if ($sum > 0) { foreach ($this->labels as $label) { - $part += pow($countMatrix[$label][$i] / floatval($sum), 2); + $part += pow($countMatrix[$label][$i] / (float) $sum, 2); } } diff --git a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php index 9002458e..b658c213 100644 --- a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -81,7 +81,7 @@ public function evaluate($record) if ($this->isContinuous) { $op = $this->operator; $value = $this->numericValue; - $recordField = strval($recordField); + $recordField = (string) $recordField; eval("\$result = $recordField $op $value;"); return $result; @@ -139,7 +139,7 @@ public function getHTML($columnNames = null) $col = "col_$this->columnIndex"; } - if (!preg_match('/^[<>=]{1,2}/', strval($value))) { + if (!preg_match('/^[<>=]{1,2}/', (string) $value)) { $value = "=$value"; } diff --git a/src/Phpml/Classification/Linear/DecisionStump.php b/src/Phpml/Classification/Linear/DecisionStump.php index 776a6a2b..2780638f 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -285,7 +285,7 @@ protected function calculateErrorRate(array $targets, float $threshold, string $ } $target = $targets[$index]; - if (strval($predicted) != strval($targets[$index])) { + if ((string) $predicted != (string) $targets[$index]) { $wrong += $this->weights[$index]; } @@ -300,7 +300,7 @@ protected function calculateErrorRate(array $targets, float $threshold, string $ foreach ($prob as $leaf => $counts) { $leafTotal = (float) array_sum($prob[$leaf]); foreach ($counts as $label => $count) { - if (strval($leaf) == strval($label)) { + if ((string) $leaf == (string) $label) { $dist[$leaf] = $count / $leafTotal; } } @@ -323,7 +323,7 @@ protected function calculateErrorRate(array $targets, float $threshold, string $ protected function predictProbability(array $sample, $label) : float { $predicted = $this->predictSampleBinary($sample); - if (strval($predicted) == strval($label)) { + if ((string) $predicted == (string) $label) { return $this->prob[$label]; } diff --git a/src/Phpml/Classification/Linear/LogisticRegression.php b/src/Phpml/Classification/Linear/LogisticRegression.php index 0447ef82..e3f0482a 100644 --- a/src/Phpml/Classification/Linear/LogisticRegression.php +++ b/src/Phpml/Classification/Linear/LogisticRegression.php @@ -288,7 +288,7 @@ protected function predictProbability(array $sample, $label) { $predicted = $this->predictSampleBinary($sample); - if (strval($predicted) == strval($label)) { + if ((string) $predicted == (string) $label) { $sample = $this->checkNormalizedSample($sample); return abs($this->output($sample) - 0.5); diff --git a/src/Phpml/Classification/Linear/Perceptron.php b/src/Phpml/Classification/Linear/Perceptron.php index 000059fa..1c534a24 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -113,7 +113,7 @@ public function trainBinary(array $samples, array $targets, array $labels) // Set all target values to either -1 or 1 $this->labels = [1 => $labels[0], -1 => $labels[1]]; foreach ($targets as $key => $target) { - $targets[$key] = strval($target) == strval($this->labels[1]) ? 1 : -1; + $targets[$key] = (string) $target == (string) $this->labels[1] ? 1 : -1; } // Set samples and feature count vars @@ -275,7 +275,7 @@ protected function predictProbability(array $sample, $label) { $predicted = $this->predictSampleBinary($sample); - if (strval($predicted) == strval($label)) { + if ((string) $predicted == (string) $label) { $sample = $this->checkNormalizedSample($sample); return abs($this->output($sample)); diff --git a/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php b/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php index 8293d5e0..1db2c66b 100644 --- a/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php +++ b/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php @@ -36,7 +36,8 @@ public function testSymmetricMatrixEigenPairs() // (We, for now, omit non-symmetric matrices whose eigenvalues can be complex numbers) $len = 3; $A = array_fill(0, $len, array_fill(0, $len, 0.0)); - srand(intval(microtime(true) * 1000)); + $seed = microtime(true) * 1000; + srand((int) $seed); for ($i = 0; $i < $len; ++$i) { for ($k = 0; $k < $len; ++$k) { if ($i > $k) { From 61d2b7d115242e57bed28c3b4e5f77a46df42401 Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Sat, 2 Sep 2017 22:44:19 +0200 Subject: [PATCH 196/328] Ensure user-provided SupportVectorMachine paths are valid (#126) --- .../Exception/InvalidArgumentException.php | 49 ++++++++++++++++-- .../SupportVectorMachine.php | 51 ++++++++++++++++--- .../SupportVectorMachineTest.php | 30 +++++++++++ 3 files changed, 121 insertions(+), 9 deletions(-) diff --git a/src/Phpml/Exception/InvalidArgumentException.php b/src/Phpml/Exception/InvalidArgumentException.php index 313ca793..2c32a6a2 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -39,7 +39,7 @@ public static function arrayCantBeEmpty() */ public static function arraySizeToSmall($minimumSize = 2) { - return new self(sprintf('The array must have at least %s elements', $minimumSize)); + return new self(sprintf('The array must have at least %d elements', $minimumSize)); } /** @@ -73,7 +73,7 @@ public static function invalidClustersNumber() */ public static function invalidTarget($target) { - return new self('Target with value '.$target.' is not part of the accepted classes'); + return new self(sprintf('Target with value "%s" is not part of the accepted classes', $target)); } /** @@ -83,7 +83,7 @@ public static function invalidTarget($target) */ public static function invalidStopWordsLanguage(string $language) { - return new self(sprintf('Can\'t find %s language for StopWords', $language)); + return new self(sprintf('Can\'t find "%s" language for StopWords', $language)); } /** @@ -110,8 +110,51 @@ public static function invalidClassesNumber() return new self('Provide at least 2 different classes'); } + /** + * @return InvalidArgumentException + */ public static function inconsistentClasses() { return new self('The provided classes don\'t match the classes provided in the constructor'); } + + /** + * @param string $file + * + * @return InvalidArgumentException + */ + public static function fileNotFound(string $file) + { + return new self(sprintf('File "%s" not found', $file)); + } + + /** + * @param string $file + * + * @return InvalidArgumentException + */ + public static function fileNotExecutable(string $file) + { + return new self(sprintf('File "%s" is not executable', $file)); + } + + /** + * @param string $path + * + * @return InvalidArgumentException + */ + public static function pathNotFound(string $path) + { + return new self(sprintf('The specified path "%s" does not exist', $path)); + } + + /** + * @param string $path + * + * @return InvalidArgumentException + */ + public static function pathNotWritable(string $path) + { + return new self(sprintf('The specified path "%s" is not writable', $path)); + } } diff --git a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php index b29bfa53..9ee3c3b2 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -4,6 +4,7 @@ namespace Phpml\SupportVectorMachine; +use Phpml\Exception\InvalidArgumentException; use Phpml\Helper\Trainable; class SupportVectorMachine @@ -140,25 +141,29 @@ public function __construct( /** * @param string $binPath * - * @return $this + * @throws InvalidArgumentException */ public function setBinPath(string $binPath) { - $this->binPath = $binPath; + $this->ensureDirectorySeparator($binPath); + $this->verifyBinPath($binPath); - return $this; + $this->binPath = $binPath; } /** * @param string $varPath * - * @return $this + * @throws InvalidArgumentException */ public function setVarPath(string $varPath) { - $this->varPath = $varPath; + if (!is_writable($varPath)) { + throw InvalidArgumentException::pathNotWritable($varPath); + } - return $this; + $this->ensureDirectorySeparator($varPath); + $this->varPath = $varPath; } /** @@ -270,4 +275,38 @@ private function buildTrainCommand(string $trainingSetFileName, string $modelFil escapeshellarg($modelFileName) ); } + + /** + * @param string $path + */ + private function ensureDirectorySeparator(string &$path) + { + if (substr($path, -1) !== DIRECTORY_SEPARATOR) { + $path .= DIRECTORY_SEPARATOR; + } + } + + /** + * @param string $path + * + * @throws InvalidArgumentException + */ + private function verifyBinPath(string $path) + { + if (!is_dir($path)) { + throw InvalidArgumentException::pathNotFound($path); + } + + $osExtension = $this->getOSExtension(); + foreach (['svm-predict', 'svm-scale', 'svm-train'] as $filename) { + $filePath = $path.$filename.$osExtension; + if (!file_exists($filePath)) { + throw InvalidArgumentException::fileNotFound($filePath); + } + + if (!is_executable($filePath)) { + throw InvalidArgumentException::fileNotExecutable($filePath); + } + } + } } diff --git a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php index 19538298..4cc6d577 100644 --- a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php @@ -80,4 +80,34 @@ public function testPredictSampleFromMultipleClassWithRbfKernel() $this->assertEquals('b', $predictions[1]); $this->assertEquals('c', $predictions[2]); } + + /** + * @expectedException \Phpml\Exception\InvalidArgumentException + * @expectedExceptionMessage is not writable + */ + public function testThrowExceptionWhenVarPathIsNotWritable() + { + $svm = new SupportVectorMachine(Type::C_SVC, Kernel::RBF); + $svm->setVarPath('var-path'); + } + + /** + * @expectedException \Phpml\Exception\InvalidArgumentException + * @expectedExceptionMessage does not exist + */ + public function testThrowExceptionWhenBinPathDoesNotExist() + { + $svm = new SupportVectorMachine(Type::C_SVC, Kernel::RBF); + $svm->setBinPath('bin-path'); + } + + /** + * @expectedException \Phpml\Exception\InvalidArgumentException + * @expectedExceptionMessage not found + */ + public function testThrowExceptionWhenFileIsNotFoundInBinPath() + { + $svm = new SupportVectorMachine(Type::C_SVC, Kernel::RBF); + $svm->setBinPath('var'); + } } From b48b82bd343301308c3d658b3fb6f95133d786a2 Mon Sep 17 00:00:00 2001 From: Maxim Kasatkin Date: Wed, 18 Oct 2017 15:59:37 +0700 Subject: [PATCH 197/328] DBSCAN fix for associative keys and array_merge performance optimization (#139) --- src/Phpml/Clustering/DBSCAN.php | 6 ++++-- tests/Phpml/Clustering/DBSCANTest.php | 13 +++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Phpml/Clustering/DBSCAN.php b/src/Phpml/Clustering/DBSCAN.php index ebb1f5d9..9e65063e 100644 --- a/src/Phpml/Clustering/DBSCAN.php +++ b/src/Phpml/Clustering/DBSCAN.php @@ -94,17 +94,19 @@ private function expandCluster($samples, &$visited) { $cluster = []; + $clusterMerge = [[]]; foreach ($samples as $index => $sample) { if (!isset($visited[$index])) { $visited[$index] = true; $regionSamples = $this->getSamplesInRegion($sample, $samples); if (count($regionSamples) > $this->minSamples) { - $cluster = array_merge($regionSamples, $cluster); + $clusterMerge[] = $regionSamples; } } - $cluster[] = $sample; + $cluster[$index] = $sample; } + $cluster = \array_merge($cluster, ...$clusterMerge); return $cluster; } diff --git a/tests/Phpml/Clustering/DBSCANTest.php b/tests/Phpml/Clustering/DBSCANTest.php index 31fc1e60..a093b202 100644 --- a/tests/Phpml/Clustering/DBSCANTest.php +++ b/tests/Phpml/Clustering/DBSCANTest.php @@ -31,4 +31,17 @@ public function testDBSCANSamplesClustering() $this->assertEquals($clustered, $dbscan->cluster($samples)); } + + public function testDBSCANSamplesClusteringAssociative() + { + $samples = ['a' => [1, 1], 'b' => [9, 9], 'c' => [1, 2], 'd' => [9, 8], 'e' => [7, 7], 'f' => [8, 7]]; + $clustered = [ + ['a' => [1, 1], 'c' => [1, 2]], + ['b' => [9, 9], 'd' => [9, 8], 'e' => [7, 7], 'f' => [8, 7]], + ]; + + $dbscan = new DBSCAN($epsilon = 3, $minSamples = 2); + + $this->assertEquals($clustered, $dbscan->cluster($samples)); + } } From dda9e16b4cc71a2021fd1364cb416a0814cac373 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 24 Oct 2017 08:31:29 +0200 Subject: [PATCH 198/328] Add software quaility awards 2017 badge by @yegor256 --- README.md | 8 ++++++-- docs/index.md | 14 ++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9d649c53..4c8fd855 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,15 @@ [![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.0-8892BF.svg)](https://php.net/) [![Latest Stable Version](https://img.shields.io/packagist/v/php-ai/php-ml.svg)](https://packagist.org/packages/php-ai/php-ml) -[![Build Status](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/build.png?b=develop)](https://scrutinizer-ci.com/g/php-ai/php-ml/build-status/develop) +[![Build Status](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/build.png?b=master)](https://scrutinizer-ci.com/g/php-ai/php-ml/build-status/master) [![Documentation Status](https://readthedocs.org/projects/php-ml/badge/?version=master)](http://php-ml.readthedocs.org/) [![Total Downloads](https://poser.pugx.org/php-ai/php-ml/downloads.svg)](https://packagist.org/packages/php-ai/php-ml) [![License](https://poser.pugx.org/php-ai/php-ml/license.svg)](https://packagist.org/packages/php-ai/php-ml) -[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/quality-score.png?b=develop)](https://scrutinizer-ci.com/g/php-ai/php-ml/?branch=develop) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/php-ai/php-ml/?branch=master) + + + ![PHP-ML - Machine Learning library for PHP](docs/assets/php-ml-logo.png) diff --git a/docs/index.md b/docs/index.md index 8d284afd..0798c2e0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,20 +2,26 @@ [![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.0-8892BF.svg)](https://php.net/) [![Latest Stable Version](https://img.shields.io/packagist/v/php-ai/php-ml.svg)](https://packagist.org/packages/php-ai/php-ml) -[![Build Status](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/build.png?b=develop)](https://scrutinizer-ci.com/g/php-ai/php-ml/build-status/develop) -[![Documentation Status](https://readthedocs.org/projects/php-ml/badge/?version=develop)](http://php-ml.readthedocs.org/en/develop/?badge=develop) +[![Build Status](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/build.png?b=master)](https://scrutinizer-ci.com/g/php-ai/php-ml/build-status/master) +[![Documentation Status](https://readthedocs.org/projects/php-ml/badge/?version=master)](http://php-ml.readthedocs.org/) [![Total Downloads](https://poser.pugx.org/php-ai/php-ml/downloads.svg)](https://packagist.org/packages/php-ai/php-ml) [![License](https://poser.pugx.org/php-ai/php-ml/license.svg)](https://packagist.org/packages/php-ai/php-ml) -[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/quality-score.png?b=develop)](https://scrutinizer-ci.com/g/php-ai/php-ml/?branch=develop) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/php-ai/php-ml/?branch=master) + + + ![PHP-ML - Machine Learning library for PHP](assets/php-ml-logo.png) -Fresh approach to Machine Learning in PHP. Algorithms, Cross Validation, Preprocessing, Feature Extraction and much more in one library. +Fresh approach to Machine Learning in PHP. Algorithms, Cross Validation, Neural Network, Preprocessing, Feature Extraction and much more in one library. PHP-ML requires PHP >= 7.0. Simple example of classification: ```php +require_once 'vendor/autoload.php'; + use Phpml\Classification\KNearestNeighbors; $samples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; From 11d05ce89d4f6cca8f89e8aa8390d6194f96832e Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Tue, 24 Oct 2017 18:59:12 +0200 Subject: [PATCH 199/328] Comparison - replace eval (#130) * Replace eval with strategy * Use Factory Pattern, add tests * Add missing dockblocks * Replace strategy with simple object --- .../DecisionTree/DecisionTreeLeaf.php | 9 +-- .../Classification/Linear/DecisionStump.php | 28 +------ .../Exception/InvalidArgumentException.php | 10 +++ src/Phpml/Helper/OneVsRest.php | 4 +- src/Phpml/Math/Comparison.php | 45 +++++++++++ tests/Phpml/Math/ComparisonTest.php | 80 +++++++++++++++++++ 6 files changed, 144 insertions(+), 32 deletions(-) create mode 100644 src/Phpml/Math/Comparison.php create mode 100644 tests/Phpml/Math/ComparisonTest.php diff --git a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php index b658c213..d88c8c95 100644 --- a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -4,6 +4,8 @@ namespace Phpml\Classification\DecisionTree; +use Phpml\Math\Comparison; + class DecisionTreeLeaf { /** @@ -79,12 +81,7 @@ public function evaluate($record) $recordField = $record[$this->columnIndex]; if ($this->isContinuous) { - $op = $this->operator; - $value = $this->numericValue; - $recordField = (string) $recordField; - eval("\$result = $recordField $op $value;"); - - return $result; + return Comparison::compare((string) $recordField, $this->numericValue, $this->operator); } return $recordField == $this->value; diff --git a/src/Phpml/Classification/Linear/DecisionStump.php b/src/Phpml/Classification/Linear/DecisionStump.php index 2780638f..179c117d 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -8,6 +8,7 @@ use Phpml\Helper\OneVsRest; use Phpml\Classification\WeightedClassifier; use Phpml\Classification\DecisionTree; +use Phpml\Math\Comparison; class DecisionStump extends WeightedClassifier { @@ -236,29 +237,6 @@ protected function getBestNominalSplit(array $samples, array $targets, int $col) return $split; } - /** - * - * @param mixed $leftValue - * @param string $operator - * @param mixed $rightValue - * - * @return boolean - */ - protected function evaluate($leftValue, string $operator, $rightValue) - { - switch ($operator) { - case '>': return $leftValue > $rightValue; - case '>=': return $leftValue >= $rightValue; - case '<': return $leftValue < $rightValue; - case '<=': return $leftValue <= $rightValue; - case '=': return $leftValue === $rightValue; - case '!=': - case '<>': return $leftValue !== $rightValue; - } - - return false; - } - /** * Calculates the ratio of wrong predictions based on the new threshold * value given as the parameter @@ -278,7 +256,7 @@ protected function calculateErrorRate(array $targets, float $threshold, string $ $rightLabel = $this->binaryLabels[1]; foreach ($values as $index => $value) { - if ($this->evaluate($value, $operator, $threshold)) { + if (Comparison::compare($value, $threshold, $operator)) { $predicted = $leftLabel; } else { $predicted = $rightLabel; @@ -337,7 +315,7 @@ protected function predictProbability(array $sample, $label) : float */ protected function predictSampleBinary(array $sample) { - if ($this->evaluate($sample[$this->column], $this->operator, $this->value)) { + if (Comparison::compare($sample[$this->column], $this->value, $this->operator)) { return $this->binaryLabels[0]; } diff --git a/src/Phpml/Exception/InvalidArgumentException.php b/src/Phpml/Exception/InvalidArgumentException.php index 2c32a6a2..8dcbd03c 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -157,4 +157,14 @@ public static function pathNotWritable(string $path) { return new self(sprintf('The specified path "%s" is not writable', $path)); } + + /** + * @param string $operator + * + * @return InvalidArgumentException + */ + public static function invalidOperator(string $operator) + { + return new self(sprintf('Invalid operator "%s" provided', $operator)); + } } diff --git a/src/Phpml/Helper/OneVsRest.php b/src/Phpml/Helper/OneVsRest.php index 72757df8..7776ccdc 100644 --- a/src/Phpml/Helper/OneVsRest.php +++ b/src/Phpml/Helper/OneVsRest.php @@ -4,6 +4,8 @@ namespace Phpml\Helper; +use Phpml\Classification\Classifier; + trait OneVsRest { /** @@ -100,7 +102,7 @@ public function reset() /** * Returns an instance of the current class after cleaning up OneVsRest stuff. * - * @return \Phpml\Estimator + * @return Classifier|OneVsRest */ protected function getClassifierCopy() { diff --git a/src/Phpml/Math/Comparison.php b/src/Phpml/Math/Comparison.php new file mode 100644 index 00000000..1c8b6aae --- /dev/null +++ b/src/Phpml/Math/Comparison.php @@ -0,0 +1,45 @@ +': + return $a > $b; + case '>=': + return $a >= $b; + case '=': + case '==': + return $a == $b; + case '===': + return $a === $b; + case '<=': + return $a <= $b; + case '<': + return $a < $b; + case '!=': + case '<>': + return $a != $b; + case '!==': + return $a !== $b; + default: + throw InvalidArgumentException::invalidOperator($operator); + } + } +} diff --git a/tests/Phpml/Math/ComparisonTest.php b/tests/Phpml/Math/ComparisonTest.php new file mode 100644 index 00000000..2d41273e --- /dev/null +++ b/tests/Phpml/Math/ComparisonTest.php @@ -0,0 +1,80 @@ +assertEquals($expected, $result); + } + + /** + * @expectedException \Phpml\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid operator "~=" provided + */ + public function testThrowExceptionWhenOperatorIsInvalid() + { + Comparison::compare(1, 1, '~='); + } + + /** + * @return array + */ + public function provideData() + { + return [ + // Greater + [1, 0, '>', true], + [1, 1, '>', false], + [0, 1, '>', false], + // Greater or equal + [1, 0, '>=', true], + [1, 1, '>=', true], + [0, 1, '>=', false], + // Equal + [1, 0, '=', false], + [1, 1, '==', true], + [1, '1', '=', true], + [1, '0', '==', false], + // Identical + [1, 0, '===', false], + [1, 1, '===', true], + [1, '1', '===', false], + ['a', 'a', '===', true], + // Not equal + [1, 0, '!=', true], + [1, 1, '<>', false], + [1, '1', '!=', false], + [1, '0', '<>', true], + // Not identical + [1, 0, '!==', true], + [1, 1, '!==', false], + [1, '1', '!==', true], + [1, '0', '!==', true], + // Less or equal + [1, 0, '<=', false], + [1, 1, '<=', true], + [0, 1, '<=', true], + // Less + [1, 0, '<', false], + [1, 1, '<', false], + [0, 1, '<', true], + ]; + } +} From a0772658bfb393ebdcd5908f72fffbe625edf432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Votruba?= Date: Wed, 25 Oct 2017 08:09:23 +0200 Subject: [PATCH 200/328] README: require absolute composer (#141) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c8fd855..f3ee24c4 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ PHP-ML requires PHP >= 7.0. Simple example of classification: ```php -require_once 'vendor/autoload.php'; +require_once __DIR__ . '/vendor/autoload.php'; use Phpml\Classification\KNearestNeighbors; From f4650c696c94b0a0fb55f11bd4f6422278d876a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Votruba?= Date: Mon, 6 Nov 2017 08:56:37 +0100 Subject: [PATCH 201/328] [coding standard] fix imports order and drop unused docs typehints (#145) * fix imports order * drop unused docs typehints, make use of return types where possible --- docs/math/distance.md | 2 +- src/Phpml/Association/Apriori.php | 15 --- src/Phpml/Classification/DecisionTree.php | 41 +----- .../DecisionTree/DecisionTreeLeaf.php | 25 +--- .../Classification/Ensemble/AdaBoost.php | 39 +----- src/Phpml/Classification/Ensemble/Bagging.php | 29 +--- .../Classification/Ensemble/RandomForest.php | 11 +- .../Classification/KNearestNeighbors.php | 9 +- src/Phpml/Classification/Linear/Adaline.php | 7 - .../Classification/Linear/DecisionStump.php | 49 +------ .../Linear/LogisticRegression.php | 35 +---- .../Classification/Linear/Perceptron.php | 56 ++------ src/Phpml/Classification/MLPClassifier.php | 7 +- src/Phpml/Classification/NaiveBayes.php | 25 +--- src/Phpml/Classification/SVC.php | 11 -- src/Phpml/Clustering/Clusterer.php | 7 +- src/Phpml/Clustering/DBSCAN.php | 30 +---- src/Phpml/Clustering/FuzzyCMeans.php | 29 +--- src/Phpml/Clustering/KMeans.php | 13 +- src/Phpml/Clustering/KMeans/Cluster.php | 45 +------ src/Phpml/Clustering/KMeans/Point.php | 24 +--- src/Phpml/Clustering/KMeans/Space.php | 64 ++------- src/Phpml/CrossValidation/RandomSplit.php | 4 - src/Phpml/CrossValidation/Split.php | 30 +---- .../CrossValidation/StratifiedRandomSplit.php | 16 +-- src/Phpml/Dataset/ArrayDataset.php | 4 +- src/Phpml/Dataset/CsvDataset.php | 11 +- src/Phpml/Dataset/Dataset.php | 4 +- src/Phpml/Dataset/FilesDataset.php | 11 -- .../EigenTransformerBase.php | 2 +- src/Phpml/DimensionReduction/KernelPCA.php | 47 ++----- src/Phpml/DimensionReduction/LDA.php | 42 +----- src/Phpml/DimensionReduction/PCA.php | 25 +--- src/Phpml/Exception/DatasetException.php | 7 +- src/Phpml/Exception/FileException.php | 21 +-- .../Exception/InvalidArgumentException.php | 105 +++------------ src/Phpml/Exception/MatrixException.php | 15 +-- src/Phpml/Exception/NormalizerException.php | 5 +- src/Phpml/Exception/SerializeException.php | 14 +- src/Phpml/FeatureExtraction/StopWords.php | 19 +-- .../TokenCountVectorizer.php | 49 +------ src/Phpml/Helper/OneVsRest.php | 24 +--- .../Helper/Optimizer/ConjugateGradient.php | 100 +++----------- src/Phpml/Helper/Optimizer/GD.php | 21 +-- src/Phpml/Helper/Optimizer/Optimizer.php | 8 -- src/Phpml/Helper/Optimizer/StochasticGD.php | 35 +---- src/Phpml/Math/Comparison.php | 6 - src/Phpml/Math/Distance.php | 4 +- src/Phpml/Math/Distance/Chebyshev.php | 7 +- src/Phpml/Math/Distance/Euclidean.php | 14 +- src/Phpml/Math/Distance/Manhattan.php | 7 +- src/Phpml/Math/Distance/Minkowski.php | 10 +- src/Phpml/Math/Kernel/RBF.php | 5 - .../LinearAlgebra/EigenvalueDecomposition.php | 1 - .../Math/LinearAlgebra/LUDecomposition.php | 68 +++++----- src/Phpml/Math/Matrix.php | 125 +++--------------- src/Phpml/Math/Set.php | 41 ------ src/Phpml/Math/Statistic/Correlation.php | 4 +- src/Phpml/Math/Statistic/Covariance.php | 26 +--- src/Phpml/Math/Statistic/Gaussian.php | 14 +- src/Phpml/Math/Statistic/Mean.php | 4 +- .../Math/Statistic/StandardDeviation.php | 5 +- src/Phpml/Metric/ClassificationReport.php | 50 +------ src/Phpml/Metric/ConfusionMatrix.php | 6 +- src/Phpml/ModelManager.php | 17 +-- .../NeuralNetwork/ActivationFunction.php | 4 +- .../ActivationFunction/BinaryStep.php | 4 +- .../ActivationFunction/Gaussian.php | 4 +- .../ActivationFunction/HyperbolicTangent.php | 9 +- .../ActivationFunction/PReLU.php | 9 +- .../ActivationFunction/Sigmoid.php | 9 +- .../ActivationFunction/ThresholdedReLU.php | 9 +- src/Phpml/NeuralNetwork/Layer.php | 10 +- src/Phpml/NeuralNetwork/Network.php | 7 +- .../NeuralNetwork/Network/LayeredNetwork.php | 13 +- .../Network/MultilayerPerceptron.php | 49 +------ src/Phpml/NeuralNetwork/Node.php | 5 +- src/Phpml/NeuralNetwork/Node/Bias.php | 5 +- src/Phpml/NeuralNetwork/Node/Input.php | 11 +- src/Phpml/NeuralNetwork/Node/Neuron.php | 13 +- .../NeuralNetwork/Node/Neuron/Synapse.php | 25 +--- .../Training/Backpropagation.php | 21 +-- .../Training/Backpropagation/Sigma.php | 23 +--- src/Phpml/Pipeline.php | 13 +- src/Phpml/Preprocessing/Imputer.php | 2 +- .../Imputer/Strategy/MeanStrategy.php | 6 +- .../Imputer/Strategy/MedianStrategy.php | 6 +- .../Imputer/Strategy/MostFrequentStrategy.php | 2 +- src/Phpml/Preprocessing/Normalizer.php | 4 +- src/Phpml/Regression/LeastSquares.php | 25 +--- src/Phpml/Regression/SVR.php | 11 -- .../SupportVectorMachine/DataTransformer.php | 32 +---- .../SupportVectorMachine.php | 54 +------- src/Phpml/Tokenization/Tokenizer.php | 7 +- .../Tokenization/WhitespaceTokenizer.php | 7 +- src/Phpml/Tokenization/WordTokenizer.php | 7 +- .../Classification/Ensemble/BaggingTest.php | 2 +- .../Ensemble/RandomForestTest.php | 2 +- .../Classification/MLPClassifierTest.php | 4 +- tests/Phpml/Classification/SVCTest.php | 2 +- tests/Phpml/DimensionReduction/LDATest.php | 2 +- tests/Phpml/NeuralNetwork/LayerTest.php | 2 +- .../NeuralNetwork/Node/Neuron/SynapseTest.php | 2 +- tests/Phpml/PipelineTest.php | 2 +- tests/Phpml/Regression/LeastSquaresTest.php | 2 +- tests/Phpml/Regression/SVRTest.php | 2 +- 106 files changed, 301 insertions(+), 1733 deletions(-) diff --git a/docs/math/distance.md b/docs/math/distance.md index fd491ea6..69707425 100644 --- a/docs/math/distance.md +++ b/docs/math/distance.md @@ -94,7 +94,7 @@ class CustomDistance implements Distance * * @return float */ - public function distance(array $a, array $b): float + public function distance(array $a, array $b) : float { $distance = []; $count = count($a); diff --git a/src/Phpml/Association/Apriori.php b/src/Phpml/Association/Apriori.php index 362f25a6..ee9c3833 100644 --- a/src/Phpml/Association/Apriori.php +++ b/src/Phpml/Association/Apriori.php @@ -49,9 +49,6 @@ class Apriori implements Associator /** * Apriori constructor. - * - * @param float $support - * @param float $confidence */ public function __construct(float $support = 0.0, float $confidence = 0.0) { @@ -261,8 +258,6 @@ private function candidates(array $samples) : array * * @param mixed[] $set * @param mixed[] $subset - * - * @return float */ private function confidence(array $set, array $subset) : float { @@ -276,8 +271,6 @@ private function confidence(array $set, array $subset) : float * @see \Phpml\Association\Apriori::samples * * @param mixed[] $sample - * - * @return float */ private function support(array $sample) : float { @@ -290,8 +283,6 @@ private function support(array $sample) : float * @see \Phpml\Association\Apriori::samples * * @param mixed[] $sample - * - * @return int */ private function frequency(array $sample) : int { @@ -307,8 +298,6 @@ private function frequency(array $sample) : int * * @param mixed[][] $system * @param mixed[] $set - * - * @return bool */ private function contains(array $system, array $set) : bool { @@ -322,8 +311,6 @@ private function contains(array $system, array $set) : bool * * @param mixed[] $set * @param mixed[] $subset - * - * @return bool */ private function subset(array $set, array $subset) : bool { @@ -335,8 +322,6 @@ private function subset(array $set, array $subset) : bool * * @param mixed[] $set1 * @param mixed[] $set2 - * - * @return bool */ private function equals(array $set1, array $set2) : bool { diff --git a/src/Phpml/Classification/DecisionTree.php b/src/Phpml/Classification/DecisionTree.php index feab32e4..c8e8674c 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -4,11 +4,11 @@ namespace Phpml\Classification; +use Phpml\Classification\DecisionTree\DecisionTreeLeaf; use Phpml\Exception\InvalidArgumentException; use Phpml\Helper\Predictable; use Phpml\Helper\Trainable; use Phpml\Math\Statistic\Mean; -use Phpml\Classification\DecisionTree\DecisionTreeLeaf; class DecisionTree implements Classifier { @@ -63,14 +63,10 @@ class DecisionTree implements Classifier private $featureImportances = null; /** - * * @var array */ private $columnNames = null; - /** - * @param int $maxDepth - */ public function __construct(int $maxDepth = 10) { $this->maxDepth = $maxDepth; @@ -129,8 +125,6 @@ public static function getColumnTypes(array $samples) : array /** * @param array $records * @param int $depth - * - * @return DecisionTreeLeaf */ protected function getSplitLeaf(array $records, int $depth = 0) : DecisionTreeLeaf { @@ -190,11 +184,6 @@ protected function getSplitLeaf(array $records, int $depth = 0) : DecisionTreeLe return $split; } - /** - * @param array $records - * - * @return DecisionTreeLeaf - */ protected function getBestSplit(array $records) : DecisionTreeLeaf { $targets = array_intersect_key($this->targets, array_flip($records)); @@ -277,10 +266,6 @@ protected function getSelectedFeatures() : array /** * @param mixed $baseValue - * @param array $colValues - * @param array $targets - * - * @return float */ public function getGiniIndex($baseValue, array $colValues, array $targets) : float { @@ -342,8 +327,6 @@ protected function preprocess(array $samples) : array /** * @param array $columnValues - * - * @return bool */ protected static function isCategoricalColumn(array $columnValues) : bool { @@ -376,8 +359,6 @@ protected static function isCategoricalColumn(array $columnValues) : bool * otherwise the given value will be used as a maximum for number of columns * randomly selected for each split operation. * - * @param int $numFeatures - * * @return $this * * @throws InvalidArgumentException @@ -395,8 +376,6 @@ public function setNumFeatures(int $numFeatures) /** * Used to set predefined features to consider while deciding which column to use for a split - * - * @param array $selectedFeatures */ protected function setSelectedFeatures(array $selectedFeatures) { @@ -407,8 +386,6 @@ protected function setSelectedFeatures(array $selectedFeatures) * A string array to represent columns. Useful when HTML output or * column importances are desired to be inspected. * - * @param array $names - * * @return $this * * @throws InvalidArgumentException @@ -424,10 +401,7 @@ public function setColumnNames(array $names) return $this; } - /** - * @return string - */ - public function getHtml() + public function getHtml() : string { return $this->tree->getHTML($this->columnNames); } @@ -436,10 +410,8 @@ public function getHtml() * This will return an array including an importance value for * each column in the given dataset. The importance values are * normalized and their total makes 1.
- * - * @return array */ - public function getFeatureImportances() + public function getFeatureImportances() : array { if ($this->featureImportances !== null) { return $this->featureImportances; @@ -473,11 +445,6 @@ public function getFeatureImportances() /** * Collects and returns an array of internal nodes that use the given * column as a split criterion - * - * @param int $column - * @param DecisionTreeLeaf $node - * - * @return array */ protected function getSplitNodesByColumn(int $column, DecisionTreeLeaf $node) : array { @@ -506,8 +473,6 @@ protected function getSplitNodesByColumn(int $column, DecisionTreeLeaf $node) : } /** - * @param array $sample - * * @return mixed */ protected function predictSample(array $sample) diff --git a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php index d88c8c95..2bcc3ac1 100644 --- a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -71,31 +71,22 @@ class DecisionTreeLeaf */ public $level = 0; - /** - * @param array $record - * - * @return bool - */ - public function evaluate($record) + public function evaluate(array $record) : bool { $recordField = $record[$this->columnIndex]; if ($this->isContinuous) { return Comparison::compare((string) $recordField, $this->numericValue, $this->operator); } - + return $recordField == $this->value; } /** * Returns Mean Decrease Impurity (MDI) in the node. * For terminal nodes, this value is equal to 0 - * - * @param int $parentRecordCount - * - * @return float */ - public function getNodeImpurityDecrease(int $parentRecordCount) + public function getNodeImpurityDecrease(int $parentRecordCount) : float { if ($this->isTerminal) { return 0.0; @@ -119,12 +110,8 @@ public function getNodeImpurityDecrease(int $parentRecordCount) /** * Returns HTML representation of the node including children nodes - * - * @param $columnNames - * - * @return string */ - public function getHTML($columnNames = null) + public function getHTML($columnNames = null) : string { if ($this->isTerminal) { $value = "$this->classValue"; @@ -170,10 +157,8 @@ public function getHTML($columnNames = null) /** * HTML representation of the tree without column names - * - * @return string */ - public function __toString() + public function __toString() : string { return $this->getHTML(); } diff --git a/src/Phpml/Classification/Ensemble/AdaBoost.php b/src/Phpml/Classification/Ensemble/AdaBoost.php index 95daf490..9fdd65b3 100644 --- a/src/Phpml/Classification/Ensemble/AdaBoost.php +++ b/src/Phpml/Classification/Ensemble/AdaBoost.php @@ -4,13 +4,13 @@ namespace Phpml\Classification\Ensemble; +use Phpml\Classification\Classifier; use Phpml\Classification\Linear\DecisionStump; use Phpml\Classification\WeightedClassifier; -use Phpml\Math\Statistic\Mean; -use Phpml\Math\Statistic\StandardDeviation; -use Phpml\Classification\Classifier; use Phpml\Helper\Predictable; use Phpml\Helper\Trainable; +use Phpml\Math\Statistic\Mean; +use Phpml\Math\Statistic\StandardDeviation; class AdaBoost implements Classifier { @@ -75,8 +75,6 @@ class AdaBoost implements Classifier * ADAptive BOOSTing (AdaBoost) is an ensemble algorithm to * improve classification performance of 'weak' classifiers such as * DecisionStump (default base classifier of AdaBoost). - * - * @param int $maxIterations */ public function __construct(int $maxIterations = 50) { @@ -85,9 +83,6 @@ public function __construct(int $maxIterations = 50) /** * Sets the base classifier that will be used for boosting (default = DecisionStump) - * - * @param string $baseClassifier - * @param array $classifierOptions */ public function setBaseClassifier(string $baseClassifier = DecisionStump::class, array $classifierOptions = []) { @@ -96,9 +91,6 @@ public function setBaseClassifier(string $baseClassifier = DecisionStump::class, } /** - * @param array $samples - * @param array $targets - * * @throws \Exception */ public function train(array $samples, array $targets) @@ -143,10 +135,8 @@ public function train(array $samples, array $targets) /** * Returns the classifier with the lowest error rate with the * consideration of current sample weights - * - * @return Classifier */ - protected function getBestClassifier() + protected function getBestClassifier() : Classifier { $ref = new \ReflectionClass($this->baseClassifier); if ($this->classifierOptions) { @@ -169,10 +159,8 @@ protected function getBestClassifier() /** * Resamples the dataset in accordance with the weights and * returns the new dataset - * - * @return array */ - protected function resample() + protected function resample() : array { $weights = $this->weights; $std = StandardDeviation::population($weights); @@ -198,12 +186,8 @@ protected function resample() /** * Evaluates the classifier and returns the classification error rate - * - * @param Classifier $classifier - * - * @return float */ - protected function evaluateClassifier(Classifier $classifier) + protected function evaluateClassifier(Classifier $classifier) : float { $total = (float) array_sum($this->weights); $wrong = 0; @@ -219,12 +203,8 @@ protected function evaluateClassifier(Classifier $classifier) /** * Calculates alpha of a classifier - * - * @param float $errorRate - * - * @return float */ - protected function calculateAlpha(float $errorRate) + protected function calculateAlpha(float $errorRate) : float { if ($errorRate == 0) { $errorRate = 1e-10; @@ -235,9 +215,6 @@ protected function calculateAlpha(float $errorRate) /** * Updates the sample weights - * - * @param Classifier $classifier - * @param float $alpha */ protected function updateWeights(Classifier $classifier, float $alpha) { @@ -256,8 +233,6 @@ protected function updateWeights(Classifier $classifier, float $alpha) } /** - * @param array $sample - * * @return mixed */ public function predictSample(array $sample) diff --git a/src/Phpml/Classification/Ensemble/Bagging.php b/src/Phpml/Classification/Ensemble/Bagging.php index 716a6bc9..ebc75286 100644 --- a/src/Phpml/Classification/Ensemble/Bagging.php +++ b/src/Phpml/Classification/Ensemble/Bagging.php @@ -4,10 +4,10 @@ namespace Phpml\Classification\Ensemble; -use Phpml\Helper\Predictable; -use Phpml\Helper\Trainable; use Phpml\Classification\Classifier; use Phpml\Classification\DecisionTree; +use Phpml\Helper\Predictable; +use Phpml\Helper\Trainable; class Bagging implements Classifier { @@ -62,8 +62,6 @@ class Bagging implements Classifier * Creates an ensemble classifier with given number of base classifiers * Default number of base classifiers is 50. * The more number of base classifiers, the better performance but at the cost of procesing time - * - * @param int $numClassifier */ public function __construct(int $numClassifier = 50) { @@ -75,8 +73,6 @@ public function __construct(int $numClassifier = 50) * e.g., random samples drawn from the original dataset with replacement (allow repeats), * to train each base classifier. * - * @param float $ratio - * * @return $this * * @throws \Exception @@ -100,9 +96,6 @@ public function setSubsetRatio(float $ratio) * given in the order they are in the constructor of the classifier and parameter * names are neglected. * - * @param string $classifier - * @param array $classifierOptions - * * @return $this */ public function setClassifer(string $classifier, array $classifierOptions = []) @@ -113,10 +106,6 @@ public function setClassifer(string $classifier, array $classifierOptions = []) return $this; } - /** - * @param array $samples - * @param array $targets - */ public function train(array $samples, array $targets) { $this->samples = array_merge($this->samples, $samples); @@ -134,12 +123,7 @@ public function train(array $samples, array $targets) } } - /** - * @param int $index - * - * @return array - */ - protected function getRandomSubset(int $index) + protected function getRandomSubset(int $index) : array { $samples = []; $targets = []; @@ -154,10 +138,7 @@ protected function getRandomSubset(int $index) return [$samples, $targets]; } - /** - * @return array - */ - protected function initClassifiers() + protected function initClassifiers() : array { $classifiers = []; for ($i = 0; $i < $this->numClassifier; ++$i) { @@ -185,8 +166,6 @@ protected function initSingleClassifier($classifier) } /** - * @param array $sample - * * @return mixed */ protected function predictSample(array $sample) diff --git a/src/Phpml/Classification/Ensemble/RandomForest.php b/src/Phpml/Classification/Ensemble/RandomForest.php index e6677cb9..4928ea54 100644 --- a/src/Phpml/Classification/Ensemble/RandomForest.php +++ b/src/Phpml/Classification/Ensemble/RandomForest.php @@ -22,8 +22,6 @@ class RandomForest extends Bagging * Initializes RandomForest with the given number of trees. More trees * may increase the prediction performance while it will also substantially * increase the processing time and the required memory - * - * @param int $numClassifier */ public function __construct(int $numClassifier = 50) { @@ -65,9 +63,6 @@ public function setFeatureSubsetRatio($ratio) /** * RandomForest algorithm is usable *only* with DecisionTree * - * @param string $classifier - * @param array $classifierOptions - * * @return $this * * @throws \Exception @@ -85,10 +80,8 @@ public function setClassifer(string $classifier, array $classifierOptions = []) * This will return an array including an importance value for * each column in the given dataset. Importance values for a column * is the average importance of that column in all trees in the forest - * - * @return array */ - public function getFeatureImportances() + public function getFeatureImportances() : array { // Traverse each tree and sum importance of the columns $sum = []; @@ -120,8 +113,6 @@ public function getFeatureImportances() * A string array to represent the columns is given. They are useful * when trying to print some information about the trees such as feature importances * - * @param array $names - * * @return $this */ public function setColumnNames(array $names) diff --git a/src/Phpml/Classification/KNearestNeighbors.php b/src/Phpml/Classification/KNearestNeighbors.php index b52c95bd..c7783d8c 100644 --- a/src/Phpml/Classification/KNearestNeighbors.php +++ b/src/Phpml/Classification/KNearestNeighbors.php @@ -24,7 +24,6 @@ class KNearestNeighbors implements Classifier private $distanceMetric; /** - * @param int $k * @param Distance|null $distanceMetric (if null then Euclidean distance as default) */ public function __construct(int $k = 3, Distance $distanceMetric = null) @@ -40,8 +39,6 @@ public function __construct(int $k = 3, Distance $distanceMetric = null) } /** - * @param array $sample - * * @return mixed */ protected function predictSample(array $sample) @@ -61,13 +58,9 @@ protected function predictSample(array $sample) } /** - * @param array $sample - * - * @return array - * * @throws \Phpml\Exception\InvalidArgumentException */ - private function kNeighborsDistances(array $sample) + private function kNeighborsDistances(array $sample) : array { $distances = []; diff --git a/src/Phpml/Classification/Linear/Adaline.php b/src/Phpml/Classification/Linear/Adaline.php index d10fff48..bcc014ef 100644 --- a/src/Phpml/Classification/Linear/Adaline.php +++ b/src/Phpml/Classification/Linear/Adaline.php @@ -32,11 +32,6 @@ class Adaline extends Perceptron * If normalizeInputs is set to true, then every input given to the algorithm will be standardized * by use of standard deviation and mean calculation * - * @param float $learningRate - * @param int $maxIterations - * @param bool $normalizeInputs - * @param int $trainingType - * * @throws \Exception */ public function __construct( @@ -57,8 +52,6 @@ public function __construct( /** * Adapts the weights with respect to given samples and targets * by use of gradient descent learning rule - * - * @param array $samples * @param array $targets */ protected function runTraining(array $samples, array $targets) diff --git a/src/Phpml/Classification/Linear/DecisionStump.php b/src/Phpml/Classification/Linear/DecisionStump.php index 179c117d..014dceb5 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -4,10 +4,10 @@ namespace Phpml\Classification\Linear; -use Phpml\Helper\Predictable; -use Phpml\Helper\OneVsRest; -use Phpml\Classification\WeightedClassifier; use Phpml\Classification\DecisionTree; +use Phpml\Classification\WeightedClassifier; +use Phpml\Helper\OneVsRest; +use Phpml\Helper\Predictable; use Phpml\Math\Comparison; class DecisionStump extends WeightedClassifier @@ -77,8 +77,6 @@ class DecisionStump extends WeightedClassifier * If columnIndex is given, then the stump tries to produce a decision node * on this column, otherwise in cases given the value of -1, the stump itself * decides which column to take for the decision (Default DecisionTree behaviour) - * - * @param int $columnIndex */ public function __construct(int $columnIndex = self::AUTO_SELECT) { @@ -86,10 +84,6 @@ public function __construct(int $columnIndex = self::AUTO_SELECT) } /** - * @param array $samples - * @param array $targets - * @param array $labels - * * @throws \Exception */ protected function trainBinary(array $samples, array $targets, array $labels) @@ -151,8 +145,6 @@ protected function trainBinary(array $samples, array $targets, array $labels) * values in the column. Given $count value determines how many split * points to be probed. The more split counts, the better performance but * worse processing time (Default value is 10.0) - * - * @param float $count */ public function setNumericalSplitCount(float $count) { @@ -161,14 +153,8 @@ public function setNumericalSplitCount(float $count) /** * Determines best split point for the given column - * - * @param array $samples - * @param array $targets - * @param int $col - * - * @return array */ - protected function getBestNumericalSplit(array $samples, array $targets, int $col) + protected function getBestNumericalSplit(array $samples, array $targets, int $col) : array { $values = array_column($samples, $col); // Trying all possible points may be accomplished in two general ways: @@ -207,13 +193,6 @@ protected function getBestNumericalSplit(array $samples, array $targets, int $co return $split; } - /** - * @param array $samples - * @param array $targets - * @param int $col - * - * @return array - */ protected function getBestNominalSplit(array $samples, array $targets, int $col) : array { $values = array_column($samples, $col); @@ -240,13 +219,6 @@ protected function getBestNominalSplit(array $samples, array $targets, int $col) /** * Calculates the ratio of wrong predictions based on the new threshold * value given as the parameter - * - * @param array $targets - * @param float $threshold - * @param string $operator - * @param array $values - * - * @return array */ protected function calculateErrorRate(array $targets, float $threshold, string $operator, array $values) : array { @@ -293,10 +265,7 @@ protected function calculateErrorRate(array $targets, float $threshold, string $ * Probability of a sample is calculated as the proportion of the label * within the labels of the training samples in the decision node * - * @param array $sample * @param mixed $label - * - * @return float */ protected function predictProbability(array $sample, $label) : float { @@ -309,8 +278,6 @@ protected function predictProbability(array $sample, $label) : float } /** - * @param array $sample - * * @return mixed */ protected function predictSampleBinary(array $sample) @@ -322,17 +289,11 @@ protected function predictSampleBinary(array $sample) return $this->binaryLabels[1]; } - /** - * @return void - */ protected function resetBinary() { } - /** - * @return string - */ - public function __toString() + public function __toString() : string { return "IF $this->column $this->operator $this->value ". 'THEN '.$this->binaryLabels[0].' '. diff --git a/src/Phpml/Classification/Linear/LogisticRegression.php b/src/Phpml/Classification/Linear/LogisticRegression.php index e3f0482a..bd100baa 100644 --- a/src/Phpml/Classification/Linear/LogisticRegression.php +++ b/src/Phpml/Classification/Linear/LogisticRegression.php @@ -59,12 +59,6 @@ class LogisticRegression extends Adaline * * Penalty (Regularization term) can be 'L2' or empty string to cancel penalty term * - * @param int $maxIterations - * @param bool $normalizeInputs - * @param int $trainingType - * @param string $cost - * @param string $penalty - * * @throws \Exception */ public function __construct( @@ -102,8 +96,6 @@ public function __construct( /** * Sets the learning rate if gradient descent algorithm is * selected for training - * - * @param float $learningRate */ public function setLearningRate(float $learningRate) { @@ -113,8 +105,6 @@ public function setLearningRate(float $learningRate) /** * Lambda (λ) parameter of regularization term. If 0 is given, * then the regularization term is cancelled - * - * @param float $lambda */ public function setLambda(float $lambda) { @@ -125,9 +115,6 @@ public function setLambda(float $lambda) * Adapts the weights with respect to given samples and targets * by use of selected solver * - * @param array $samples - * @param array $targets - * * @throws \Exception */ protected function runTraining(array $samples, array $targets) @@ -154,7 +141,6 @@ protected function runTraining(array $samples, array $targets) * * @param array $samples * @param array $targets - * @param \Closure $gradientFunc */ protected function runConjugateGradient(array $samples, array $targets, \Closure $gradientFunc) { @@ -170,11 +156,9 @@ protected function runConjugateGradient(array $samples, array $targets, \Closure /** * Returns the appropriate callback function for the selected cost function * - * @return \Closure - * * @throws \Exception */ - protected function getCostFunction() + protected function getCostFunction() : \Closure { $penalty = 0; if ($this->penalty == 'L2') { @@ -244,8 +228,6 @@ protected function getCostFunction() /** * Returns the output of the network, a float value between 0.0 and 1.0 * - * @param array $sample - * * @return float */ protected function output(array $sample) @@ -257,12 +239,8 @@ protected function output(array $sample) /** * Returns the class value (either -1 or 1) for the given input - * - * @param array $sample - * - * @return int */ - protected function outputClass(array $sample) + protected function outputClass(array $sample) : int { $output = $this->output($sample); @@ -278,20 +256,17 @@ protected function outputClass(array $sample) * * The probability is simply taken as the distance of the sample * to the decision plane. - * - * @param array $sample + * @param mixed $label - * - * @return float */ - protected function predictProbability(array $sample, $label) + protected function predictProbability(array $sample, $label) : float { $predicted = $this->predictSampleBinary($sample); if ((string) $predicted == (string) $label) { $sample = $this->checkNormalizedSample($sample); - return abs($this->output($sample) - 0.5); + return (float) abs($this->output($sample) - 0.5); } return 0.0; diff --git a/src/Phpml/Classification/Linear/Perceptron.php b/src/Phpml/Classification/Linear/Perceptron.php index 1c534a24..d5f424b0 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -4,13 +4,13 @@ namespace Phpml\Classification\Linear; -use Phpml\Helper\Predictable; +use Phpml\Classification\Classifier; use Phpml\Helper\OneVsRest; -use Phpml\Helper\Optimizer\StochasticGD; use Phpml\Helper\Optimizer\GD; -use Phpml\Classification\Classifier; -use Phpml\Preprocessing\Normalizer; +use Phpml\Helper\Optimizer\StochasticGD; +use Phpml\Helper\Predictable; use Phpml\IncrementalEstimator; +use Phpml\Preprocessing\Normalizer; class Perceptron implements Classifier, IncrementalEstimator { @@ -67,7 +67,6 @@ class Perceptron implements Classifier, IncrementalEstimator * * @param float $learningRate Value between 0.0(exclusive) and 1.0(inclusive) * @param int $maxIterations Must be at least 1 - * @param bool $normalizeInputs * * @throws \Exception */ @@ -89,21 +88,11 @@ public function __construct(float $learningRate = 0.001, int $maxIterations = 10 $this->maxIterations = $maxIterations; } - /** - * @param array $samples - * @param array $targets - * @param array $labels - */ public function partialTrain(array $samples, array $targets, array $labels = []) { $this->trainByLabel($samples, $targets, $labels); } - /** - * @param array $samples - * @param array $targets - * @param array $labels - */ public function trainBinary(array $samples, array $targets, array $labels) { if ($this->normalizer) { @@ -139,8 +128,6 @@ protected function resetBinary() * If "false" is given, the optimization procedure will always be executed * for $maxIterations times * - * @param bool $enable - * * @return $this */ public function setEarlyStop(bool $enable = true) @@ -152,10 +139,8 @@ public function setEarlyStop(bool $enable = true) /** * Returns the cost values obtained during the training. - * - * @return array */ - public function getCostValues() + public function getCostValues() : array { return $this->costValues; } @@ -163,9 +148,6 @@ public function getCostValues() /** * Trains the perceptron model with Stochastic Gradient Descent optimization * to get the correct set of weights - * - * @param array $samples - * @param array $targets */ protected function runTraining(array $samples, array $targets) { @@ -186,11 +168,6 @@ protected function runTraining(array $samples, array $targets) /** * Executes a Gradient Descent algorithm for * the given cost function - * - * @param array $samples - * @param array $targets - * @param \Closure $gradientFunc - * @param bool $isBatch */ protected function runGradientDescent(array $samples, array $targets, \Closure $gradientFunc, bool $isBatch = false) { @@ -211,12 +188,8 @@ protected function runGradientDescent(array $samples, array $targets, \Closure $ /** * Checks if the sample should be normalized and if so, returns the * normalized sample - * - * @param array $sample - * - * @return array */ - protected function checkNormalizedSample(array $sample) + protected function checkNormalizedSample(array $sample) : array { if ($this->normalizer) { $samples = [$sample]; @@ -230,8 +203,6 @@ protected function checkNormalizedSample(array $sample) /** * Calculates net output of the network as a float value for the given input * - * @param array $sample - * * @return int */ protected function output(array $sample) @@ -250,12 +221,8 @@ protected function output(array $sample) /** * Returns the class value (either -1 or 1) for the given input - * - * @param array $sample - * - * @return int */ - protected function outputClass(array $sample) + protected function outputClass(array $sample) : int { return $this->output($sample) > 0 ? 1 : -1; } @@ -266,27 +233,22 @@ protected function outputClass(array $sample) * The probability is simply taken as the distance of the sample * to the decision plane. * - * @param array $sample * @param mixed $label - * - * @return float */ - protected function predictProbability(array $sample, $label) + protected function predictProbability(array $sample, $label) : float { $predicted = $this->predictSampleBinary($sample); if ((string) $predicted == (string) $label) { $sample = $this->checkNormalizedSample($sample); - return abs($this->output($sample)); + return (float) abs($this->output($sample)); } return 0.0; } /** - * @param array $sample - * * @return mixed */ protected function predictSampleBinary(array $sample) diff --git a/src/Phpml/Classification/MLPClassifier.php b/src/Phpml/Classification/MLPClassifier.php index dfb53944..64e6f506 100644 --- a/src/Phpml/Classification/MLPClassifier.php +++ b/src/Phpml/Classification/MLPClassifier.php @@ -13,10 +13,8 @@ class MLPClassifier extends MultilayerPerceptron implements Classifier * @param mixed $target * * @throws InvalidArgumentException - * - * @return int */ - public function getTargetClass($target): int + public function getTargetClass($target) : int { if (!in_array($target, $this->classes)) { throw InvalidArgumentException::invalidTarget($target); @@ -26,8 +24,6 @@ public function getTargetClass($target): int } /** - * @param array $sample - * * @return mixed */ protected function predictSample(array $sample) @@ -47,7 +43,6 @@ protected function predictSample(array $sample) } /** - * @param array $sample * @param mixed $target */ protected function trainSample(array $sample, $target) diff --git a/src/Phpml/Classification/NaiveBayes.php b/src/Phpml/Classification/NaiveBayes.php index 8daaf860..08073da6 100644 --- a/src/Phpml/Classification/NaiveBayes.php +++ b/src/Phpml/Classification/NaiveBayes.php @@ -57,10 +57,6 @@ class NaiveBayes implements Classifier */ private $labels = []; - /** - * @param array $samples - * @param array $targets - */ public function train(array $samples, array $targets) { $this->samples = array_merge($this->samples, $samples); @@ -80,11 +76,8 @@ public function train(array $samples, array $targets) /** * Calculates vital statistics for each label & feature. Stores these * values in private array in order to avoid repeated calculation - * - * @param string $label - * @param array $samples */ - private function calculateStatistics($label, $samples) + private function calculateStatistics(string $label, array $samples) { $this->std[$label] = array_fill(0, $this->featureCount, 0); $this->mean[$label] = array_fill(0, $this->featureCount, 0); @@ -114,14 +107,8 @@ private function calculateStatistics($label, $samples) /** * Calculates the probability P(label|sample_n) - * - * @param array $sample - * @param int $feature - * @param string $label - * - * @return float */ - private function sampleProbability($sample, $feature, $label) + private function sampleProbability(array $sample, int $feature, string $label) : float { $value = $sample[$feature]; if ($this->dataType[$label][$feature] == self::NOMINAL) { @@ -149,12 +136,8 @@ private function sampleProbability($sample, $feature, $label) /** * Return samples belonging to specific label - * - * @param string $label - * - * @return array */ - private function getSamplesByLabel($label) + private function getSamplesByLabel(string $label) : array { $samples = []; for ($i = 0; $i < $this->sampleCount; ++$i) { @@ -167,8 +150,6 @@ private function getSamplesByLabel($label) } /** - * @param array $sample - * * @return mixed */ protected function predictSample(array $sample) diff --git a/src/Phpml/Classification/SVC.php b/src/Phpml/Classification/SVC.php index bba5d09d..8cabcbcd 100644 --- a/src/Phpml/Classification/SVC.php +++ b/src/Phpml/Classification/SVC.php @@ -10,17 +10,6 @@ class SVC extends SupportVectorMachine implements Classifier { - /** - * @param int $kernel - * @param float $cost - * @param int $degree - * @param float|null $gamma - * @param float $coef0 - * @param float $tolerance - * @param int $cacheSize - * @param bool $shrinking - * @param bool $probabilityEstimates - */ public function __construct( int $kernel = Kernel::LINEAR, float $cost = 1.0, diff --git a/src/Phpml/Clustering/Clusterer.php b/src/Phpml/Clustering/Clusterer.php index 0c58b2e9..ad24af2a 100644 --- a/src/Phpml/Clustering/Clusterer.php +++ b/src/Phpml/Clustering/Clusterer.php @@ -6,10 +6,5 @@ interface Clusterer { - /** - * @param array $samples - * - * @return array - */ - public function cluster(array $samples); + public function cluster(array $samples) : array; } diff --git a/src/Phpml/Clustering/DBSCAN.php b/src/Phpml/Clustering/DBSCAN.php index 9e65063e..70cf302e 100644 --- a/src/Phpml/Clustering/DBSCAN.php +++ b/src/Phpml/Clustering/DBSCAN.php @@ -24,12 +24,7 @@ class DBSCAN implements Clusterer */ private $distanceMetric; - /** - * @param float $epsilon - * @param int $minSamples - * @param Distance $distanceMetric - */ - public function __construct($epsilon = 0.5, $minSamples = 3, Distance $distanceMetric = null) + public function __construct(float $epsilon = 0.5, int $minSamples = 3, Distance $distanceMetric = null) { if (null === $distanceMetric) { $distanceMetric = new Euclidean(); @@ -40,12 +35,7 @@ public function __construct($epsilon = 0.5, $minSamples = 3, Distance $distanceM $this->distanceMetric = $distanceMetric; } - /** - * @param array $samples - * - * @return array - */ - public function cluster(array $samples) + public function cluster(array $samples) : array { $clusters = []; $visited = []; @@ -65,13 +55,7 @@ public function cluster(array $samples) return $clusters; } - /** - * @param array $localSample - * @param array $samples - * - * @return array - */ - private function getSamplesInRegion($localSample, $samples) + private function getSamplesInRegion(array $localSample, array $samples) : array { $region = []; @@ -84,13 +68,7 @@ private function getSamplesInRegion($localSample, $samples) return $region; } - /** - * @param array $samples - * @param array $visited - * - * @return array - */ - private function expandCluster($samples, &$visited) + private function expandCluster(array $samples, array &$visited) : array { $cluster = []; diff --git a/src/Phpml/Clustering/FuzzyCMeans.php b/src/Phpml/Clustering/FuzzyCMeans.php index da1398ee..d14c0be4 100644 --- a/src/Phpml/Clustering/FuzzyCMeans.php +++ b/src/Phpml/Clustering/FuzzyCMeans.php @@ -4,8 +4,8 @@ namespace Phpml\Clustering; -use Phpml\Clustering\KMeans\Point; use Phpml\Clustering\KMeans\Cluster; +use Phpml\Clustering\KMeans\Point; use Phpml\Clustering\KMeans\Space; use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Distance\Euclidean; @@ -58,11 +58,6 @@ class FuzzyCMeans implements Clusterer private $samples; /** - * @param int $clustersNumber - * @param float $fuzziness - * @param float $epsilon - * @param int $maxIterations - * * @throws InvalidArgumentException */ public function __construct(int $clustersNumber, float $fuzziness = 2.0, float $epsilon = 1e-2, int $maxIterations = 100) @@ -85,10 +80,6 @@ protected function initClusters() $this->updateClusters(); } - /** - * @param int $rows - * @param int $cols - */ protected function generateRandomMembership(int $rows, int $cols) { $this->membership = []; @@ -155,14 +146,7 @@ protected function updateMembershipMatrix() } } - /** - * - * @param int $row - * @param int $col - * - * @return float - */ - protected function getDistanceCalc(int $row, int $col) + protected function getDistanceCalc(int $row, int $col) : float { $sum = 0.0; $distance = new Euclidean(); @@ -204,20 +188,15 @@ protected function getObjective() return $sum; } - /** - * @return array - */ - public function getMembershipMatrix() + public function getMembershipMatrix() : array { return $this->membership; } /** * @param array|Point[] $samples - * - * @return array */ - public function cluster(array $samples) + public function cluster(array $samples) : array { // Initialize variables, clusters and membership matrix $this->sampleCount = count($samples); diff --git a/src/Phpml/Clustering/KMeans.php b/src/Phpml/Clustering/KMeans.php index a9e90833..0a776a4f 100644 --- a/src/Phpml/Clustering/KMeans.php +++ b/src/Phpml/Clustering/KMeans.php @@ -22,12 +22,6 @@ class KMeans implements Clusterer */ private $initialization; - /** - * @param int $clustersNumber - * @param int $initialization - * - * @throws InvalidArgumentException - */ public function __construct(int $clustersNumber, int $initialization = self::INIT_KMEANS_PLUS_PLUS) { if ($clustersNumber <= 0) { @@ -38,12 +32,7 @@ public function __construct(int $clustersNumber, int $initialization = self::INI $this->initialization = $initialization; } - /** - * @param array $samples - * - * @return array - */ - public function cluster(array $samples) + public function cluster(array $samples) : array { $space = new Space(count($samples[0])); foreach ($samples as $sample) { diff --git a/src/Phpml/Clustering/KMeans/Cluster.php b/src/Phpml/Clustering/KMeans/Cluster.php index 7cb9f126..b7c7ecf9 100644 --- a/src/Phpml/Clustering/KMeans/Cluster.php +++ b/src/Phpml/Clustering/KMeans/Cluster.php @@ -4,10 +4,10 @@ namespace Phpml\Clustering\KMeans; -use IteratorAggregate; use Countable; -use SplObjectStorage; +use IteratorAggregate; use LogicException; +use SplObjectStorage; class Cluster extends Point implements IteratorAggregate, Countable { @@ -21,10 +21,6 @@ class Cluster extends Point implements IteratorAggregate, Countable */ protected $points; - /** - * @param Space $space - * @param array $coordinates - */ public function __construct(Space $space, array $coordinates) { parent::__construct($coordinates); @@ -32,10 +28,7 @@ public function __construct(Space $space, array $coordinates) $this->points = new SplObjectStorage(); } - /** - * @return array - */ - public function getPoints() + public function getPoints() : array { $points = []; foreach ($this->points as $point) { @@ -45,10 +38,7 @@ public function getPoints() return $points; } - /** - * @return array - */ - public function toArray() + public function toArray() : array { return [ 'centroid' => parent::toArray(), @@ -56,14 +46,7 @@ public function toArray() ]; } - /** - * @param Point $point - * - * @return Point - * - * @throws \LogicException - */ - public function attach(Point $point) + public function attach(Point $point) : Point { if ($point instanceof self) { throw new LogicException('cannot attach a cluster to another'); @@ -74,29 +57,18 @@ public function attach(Point $point) return $point; } - /** - * @param Point $point - * - * @return Point - */ - public function detach(Point $point) + public function detach(Point $point) : Point { $this->points->detach($point); return $point; } - /** - * @param SplObjectStorage $points - */ public function attachAll(SplObjectStorage $points) { $this->points->addAll($points); } - /** - * @param SplObjectStorage $points - */ public function detachAll(SplObjectStorage $points) { $this->points->removeAll($points); @@ -136,10 +108,7 @@ public function count() { return count($this->points); } - - /** - * @param array $newCoordinates - */ + public function setCoordinates(array $newCoordinates) { $this->coordinates = $newCoordinates; diff --git a/src/Phpml/Clustering/KMeans/Point.php b/src/Phpml/Clustering/KMeans/Point.php index ce1c44ee..08fa11ea 100644 --- a/src/Phpml/Clustering/KMeans/Point.php +++ b/src/Phpml/Clustering/KMeans/Point.php @@ -18,30 +18,21 @@ class Point implements ArrayAccess */ protected $coordinates; - /** - * @param array $coordinates - */ public function __construct(array $coordinates) { $this->dimension = count($coordinates); $this->coordinates = $coordinates; } - /** - * @return array - */ - public function toArray() + public function toArray() : array { return $this->coordinates; } /** - * @param Point $point - * @param bool $precise - * * @return int|mixed */ - public function getDistanceWith(self $point, $precise = true) + public function getDistanceWith(self $point, bool $precise = true) { $distance = 0; for ($n = 0; $n < $this->dimension; ++$n) { @@ -53,8 +44,6 @@ public function getDistanceWith(self $point, $precise = true) } /** - * @param array $points - * * @return mixed */ public function getClosest(array $points) @@ -77,20 +66,15 @@ public function getClosest(array $points) return $minPoint; } - /** - * @return array - */ - public function getCoordinates() + public function getCoordinates() : array { return $this->coordinates; } /** * @param mixed $offset - * - * @return bool */ - public function offsetExists($offset) + public function offsetExists($offset): bool { return isset($this->coordinates[$offset]); } diff --git a/src/Phpml/Clustering/KMeans/Space.php b/src/Phpml/Clustering/KMeans/Space.php index 14c17601..bf2a3453 100644 --- a/src/Phpml/Clustering/KMeans/Space.php +++ b/src/Phpml/Clustering/KMeans/Space.php @@ -4,10 +4,10 @@ namespace Phpml\Clustering\KMeans; +use InvalidArgumentException; +use LogicException; use Phpml\Clustering\KMeans; use SplObjectStorage; -use LogicException; -use InvalidArgumentException; class Space extends SplObjectStorage { @@ -16,9 +16,6 @@ class Space extends SplObjectStorage */ protected $dimension; - /** - * @param $dimension - */ public function __construct($dimension) { if ($dimension < 1) { @@ -28,10 +25,7 @@ public function __construct($dimension) $this->dimension = $dimension; } - /** - * @return array - */ - public function toArray() + public function toArray() : array { $points = []; foreach ($this as $point) { @@ -41,12 +35,7 @@ public function toArray() return ['points' => $points]; } - /** - * @param array $coordinates - * - * @return Point - */ - public function newPoint(array $coordinates) + public function newPoint(array $coordinates) : Point { if (count($coordinates) != $this->dimension) { throw new LogicException('('.implode(',', $coordinates).') is not a point of this space'); @@ -56,7 +45,6 @@ public function newPoint(array $coordinates) } /** - * @param array $coordinates * @param null $data */ public function addPoint(array $coordinates, $data = null) @@ -77,10 +65,7 @@ public function attach($point, $data = null) parent::attach($point, $data); } - /** - * @return int - */ - public function getDimension() + public function getDimension() : int { return $this->dimension; } @@ -107,13 +92,7 @@ public function getBoundaries() return [$min, $max]; } - /** - * @param Point $min - * @param Point $max - * - * @return Point - */ - public function getRandomPoint(Point $min, Point $max) + public function getRandomPoint(Point $min, Point $max) : Point { $point = $this->newPoint(array_fill(0, $this->dimension, null)); @@ -125,12 +104,9 @@ public function getRandomPoint(Point $min, Point $max) } /** - * @param int $clustersNumber - * @param int $initMethod - * * @return array|Cluster[] */ - public function cluster(int $clustersNumber, int $initMethod = KMeans::INIT_RANDOM) + public function cluster(int $clustersNumber, int $initMethod = KMeans::INIT_RANDOM) : array { $clusters = $this->initializeClusters($clustersNumber, $initMethod); @@ -141,12 +117,9 @@ public function cluster(int $clustersNumber, int $initMethod = KMeans::INIT_RAND } /** - * @param $clustersNumber - * @param $initMethod - * * @return array|Cluster[] */ - protected function initializeClusters(int $clustersNumber, int $initMethod) + protected function initializeClusters(int $clustersNumber, int $initMethod) : array { switch ($initMethod) { case KMeans::INIT_RANDOM: @@ -166,12 +139,7 @@ protected function initializeClusters(int $clustersNumber, int $initMethod) return $clusters; } - /** - * @param $clusters - * - * @return bool - */ - protected function iterate($clusters) + protected function iterate($clusters) : bool { $convergence = true; @@ -209,12 +177,7 @@ protected function iterate($clusters) return $convergence; } - /** - * @param int $clustersNumber - * - * @return array - */ - private function initializeRandomClusters(int $clustersNumber) + private function initializeRandomClusters(int $clustersNumber) : array { $clusters = []; list($min, $max) = $this->getBoundaries(); @@ -226,12 +189,7 @@ private function initializeRandomClusters(int $clustersNumber) return $clusters; } - /** - * @param int $clustersNumber - * - * @return array - */ - protected function initializeKMPPClusters(int $clustersNumber) + protected function initializeKMPPClusters(int $clustersNumber) : array { $clusters = []; $this->rewind(); diff --git a/src/Phpml/CrossValidation/RandomSplit.php b/src/Phpml/CrossValidation/RandomSplit.php index 69c44c12..7ec18758 100644 --- a/src/Phpml/CrossValidation/RandomSplit.php +++ b/src/Phpml/CrossValidation/RandomSplit.php @@ -8,10 +8,6 @@ class RandomSplit extends Split { - /** - * @param Dataset $dataset - * @param float $testSize - */ protected function splitDataset(Dataset $dataset, float $testSize) { $samples = $dataset->getSamples(); diff --git a/src/Phpml/CrossValidation/Split.php b/src/Phpml/CrossValidation/Split.php index add181cb..77eab3ce 100644 --- a/src/Phpml/CrossValidation/Split.php +++ b/src/Phpml/CrossValidation/Split.php @@ -29,13 +29,6 @@ abstract class Split */ protected $testLabels = []; - /** - * @param Dataset $dataset - * @param float $testSize - * @param int $seed - * - * @throws InvalidArgumentException - */ public function __construct(Dataset $dataset, float $testSize = 0.3, int $seed = null) { if (0 >= $testSize || 1 <= $testSize) { @@ -48,41 +41,26 @@ public function __construct(Dataset $dataset, float $testSize = 0.3, int $seed = abstract protected function splitDataset(Dataset $dataset, float $testSize); - /** - * @return array - */ - public function getTrainSamples() + public function getTrainSamples() : array { return $this->trainSamples; } - /** - * @return array - */ - public function getTestSamples() + public function getTestSamples() : array { return $this->testSamples; } - /** - * @return array - */ - public function getTrainLabels() + public function getTrainLabels() : array { return $this->trainLabels; } - /** - * @return array - */ - public function getTestLabels() + public function getTestLabels() : array { return $this->testLabels; } - /** - * @param int|null $seed - */ protected function seedGenerator(int $seed = null) { if (null === $seed) { diff --git a/src/Phpml/CrossValidation/StratifiedRandomSplit.php b/src/Phpml/CrossValidation/StratifiedRandomSplit.php index e6c80a2f..41f5d348 100644 --- a/src/Phpml/CrossValidation/StratifiedRandomSplit.php +++ b/src/Phpml/CrossValidation/StratifiedRandomSplit.php @@ -9,10 +9,6 @@ class StratifiedRandomSplit extends RandomSplit { - /** - * @param Dataset $dataset - * @param float $testSize - */ protected function splitDataset(Dataset $dataset, float $testSize) { $datasets = $this->splitByTarget($dataset); @@ -23,11 +19,9 @@ protected function splitDataset(Dataset $dataset, float $testSize) } /** - * @param Dataset $dataset - * * @return Dataset[]|array */ - private function splitByTarget(Dataset $dataset): array + private function splitByTarget(Dataset $dataset) : array { $targets = $dataset->getTargets(); $samples = $dataset->getSamples(); @@ -44,13 +38,7 @@ private function splitByTarget(Dataset $dataset): array return $datasets; } - /** - * @param array $uniqueTargets - * @param array $split - * - * @return array - */ - private function createDatasets(array $uniqueTargets, array $split): array + private function createDatasets(array $uniqueTargets, array $split) : array { $datasets = []; foreach ($uniqueTargets as $target) { diff --git a/src/Phpml/Dataset/ArrayDataset.php b/src/Phpml/Dataset/ArrayDataset.php index 6f765fe5..96feec77 100644 --- a/src/Phpml/Dataset/ArrayDataset.php +++ b/src/Phpml/Dataset/ArrayDataset.php @@ -37,7 +37,7 @@ public function __construct(array $samples, array $targets) /** * @return array */ - public function getSamples(): array + public function getSamples() : array { return $this->samples; } @@ -45,7 +45,7 @@ public function getSamples(): array /** * @return array */ - public function getTargets(): array + public function getTargets() : array { return $this->targets; } diff --git a/src/Phpml/Dataset/CsvDataset.php b/src/Phpml/Dataset/CsvDataset.php index 65ff5159..ef33e2cb 100644 --- a/src/Phpml/Dataset/CsvDataset.php +++ b/src/Phpml/Dataset/CsvDataset.php @@ -14,12 +14,6 @@ class CsvDataset extends ArrayDataset protected $columnNames; /** - * @param string $filepath - * @param int $features - * @param bool $headingRow - * @param string $delimiter - * @param int $maxLineLength - * * @throws FileException */ public function __construct(string $filepath, int $features, bool $headingRow = true, string $delimiter = ',', int $maxLineLength = 0) @@ -50,10 +44,7 @@ public function __construct(string $filepath, int $features, bool $headingRow = parent::__construct($samples, $targets); } - /** - * @return array - */ - public function getColumnNames() + public function getColumnNames() : array { return $this->columnNames; } diff --git a/src/Phpml/Dataset/Dataset.php b/src/Phpml/Dataset/Dataset.php index f851d852..ce75a8ae 100644 --- a/src/Phpml/Dataset/Dataset.php +++ b/src/Phpml/Dataset/Dataset.php @@ -9,10 +9,10 @@ interface Dataset /** * @return array */ - public function getSamples(): array; + public function getSamples() : array; /** * @return array */ - public function getTargets(): array; + public function getTargets() : array; } diff --git a/src/Phpml/Dataset/FilesDataset.php b/src/Phpml/Dataset/FilesDataset.php index f7c789fa..73bc4b0e 100644 --- a/src/Phpml/Dataset/FilesDataset.php +++ b/src/Phpml/Dataset/FilesDataset.php @@ -8,11 +8,6 @@ class FilesDataset extends ArrayDataset { - /** - * @param string $rootPath - * - * @throws DatasetException - */ public function __construct(string $rootPath) { if (!is_dir($rootPath)) { @@ -22,9 +17,6 @@ public function __construct(string $rootPath) $this->scanRootPath($rootPath); } - /** - * @param string $rootPath - */ private function scanRootPath(string $rootPath) { foreach (glob($rootPath.DIRECTORY_SEPARATOR.'*', GLOB_ONLYDIR) as $dir) { @@ -32,9 +24,6 @@ private function scanRootPath(string $rootPath) } } - /** - * @param string $dir - */ private function scanDir(string $dir) { $target = basename($dir); diff --git a/src/Phpml/DimensionReduction/EigenTransformerBase.php b/src/Phpml/DimensionReduction/EigenTransformerBase.php index 5e27a139..913148b9 100644 --- a/src/Phpml/DimensionReduction/EigenTransformerBase.php +++ b/src/Phpml/DimensionReduction/EigenTransformerBase.php @@ -90,7 +90,7 @@ protected function eigenDecomposition(array $matrix) * * @return array */ - protected function reduce(array $data) + protected function reduce(array $data) : array { $m1 = new Matrix($data); $m2 = new Matrix($this->eigVectors); diff --git a/src/Phpml/DimensionReduction/KernelPCA.php b/src/Phpml/DimensionReduction/KernelPCA.php index 908c441a..1c7cecb5 100644 --- a/src/Phpml/DimensionReduction/KernelPCA.php +++ b/src/Phpml/DimensionReduction/KernelPCA.php @@ -44,14 +44,13 @@ class KernelPCA extends PCA * will initialize the algorithm with an RBF kernel having the gamma parameter as 15,0.
* This transformation will return the same number of rows with only 2 columns. * - * @param int $kernel * @param float $totalVariance Total variance to be preserved if numFeatures is not given * @param int $numFeatures Number of columns to be returned * @param float $gamma Gamma parameter is used with RBF and Sigmoid kernels * * @throws \Exception */ - public function __construct(int $kernel = self::KERNEL_RBF, $totalVariance = null, $numFeatures = null, $gamma = null) + public function __construct(int $kernel = self::KERNEL_RBF, float $totalVariance = null, int $numFeatures = null, float $gamma = null) { $availableKernels = [self::KERNEL_RBF, self::KERNEL_SIGMOID, self::KERNEL_LAPLACIAN, self::KERNEL_LINEAR]; if (!in_array($kernel, $availableKernels)) { @@ -69,12 +68,8 @@ public function __construct(int $kernel = self::KERNEL_RBF, $totalVariance = nul * of this data while preserving $totalVariance or $numFeatures.
* $data is an n-by-m matrix and returned array is * n-by-k matrix where k <= m - * - * @param array $data - * - * @return array */ - public function fit(array $data) + public function fit(array $data) : array { $numRows = count($data); $this->data = $data; @@ -96,13 +91,8 @@ public function fit(array $data) /** * Calculates similarity matrix by use of selected kernel function
* An n-by-m matrix is given and an n-by-n matrix is returned - * - * @param array $data - * @param int $numRows - * - * @return array */ - protected function calculateKernelMatrix(array $data, int $numRows) + protected function calculateKernelMatrix(array $data, int $numRows) : array { $kernelFunc = $this->getKernel(); @@ -125,13 +115,8 @@ protected function calculateKernelMatrix(array $data, int $numRows) * conversion: * * K′ = K − N.K − K.N + N.K.N where N is n-by-n matrix filled with 1/n - * - * @param array $matrix - * @param int $n - * - * @return array */ - protected function centerMatrix(array $matrix, int $n) + protected function centerMatrix(array $matrix, int $n) : array { $N = array_fill(0, $n, array_fill(0, $n, 1.0 / $n)); $N = new Matrix($N, false); @@ -153,11 +138,9 @@ protected function centerMatrix(array $matrix, int $n) /** * Returns the callable kernel function * - * @return \Closure - * * @throws \Exception */ - protected function getKernel() + protected function getKernel(): \Closure { switch ($this->kernel) { case self::KERNEL_LINEAR: @@ -194,12 +177,7 @@ protected function getKernel() } } - /** - * @param array $sample - * - * @return array - */ - protected function getDistancePairs(array $sample) + protected function getDistancePairs(array $sample) : array { $kernel = $this->getKernel(); @@ -211,12 +189,7 @@ protected function getDistancePairs(array $sample) return $pairs; } - /** - * @param array $pairs - * - * @return array - */ - protected function projectSample(array $pairs) + protected function projectSample(array $pairs) : array { // Normalize eigenvectors by eig = eigVectors / eigValues $func = function ($eigVal, $eigVect) { @@ -235,13 +208,9 @@ protected function projectSample(array $pairs) * Transforms the given sample to a lower dimensional vector by using * the variables obtained during the last run of fit. * - * @param array $sample - * - * @return array - * * @throws \Exception */ - public function transform(array $sample) + public function transform(array $sample) : array { if (!$this->fit) { throw new \Exception('KernelPCA has not been fitted with respect to original dataset, please run KernelPCA::fit() first'); diff --git a/src/Phpml/DimensionReduction/LDA.php b/src/Phpml/DimensionReduction/LDA.php index a2df627d..8a94f468 100644 --- a/src/Phpml/DimensionReduction/LDA.php +++ b/src/Phpml/DimensionReduction/LDA.php @@ -47,7 +47,7 @@ class LDA extends EigenTransformerBase * * @throws \Exception */ - public function __construct($totalVariance = null, $numFeatures = null) + public function __construct(float $totalVariance = null, int $numFeatures = null) { if ($totalVariance !== null && ($totalVariance < 0.1 || $totalVariance > 0.99)) { throw new \Exception('Total variance can be a value between 0.1 and 0.99'); @@ -69,11 +69,6 @@ public function __construct($totalVariance = null, $numFeatures = null) /** * Trains the algorithm to transform the given data to a lower dimensional space. - * - * @param array $data - * @param array $classes - * - * @return array */ public function fit(array $data, array $classes) : array { @@ -93,12 +88,8 @@ public function fit(array $data, array $classes) : array /** * Returns unique labels in the dataset - * - * @param array $classes - * - * @return array */ - protected function getLabels(array $classes): array + protected function getLabels(array $classes) : array { $counts = array_count_values($classes); @@ -108,11 +99,6 @@ protected function getLabels(array $classes): array /** * Calculates mean of each column for each class and returns * n by m matrix where n is number of labels and m is number of columns - * - * @param array $data - * @param array $classes - * - * @return array */ protected function calculateMeans(array $data, array $classes) : array { @@ -159,13 +145,8 @@ protected function calculateMeans(array $data, array $classes) : array * Returns in-class scatter matrix for each class, which * is a n by m matrix where n is number of classes and * m is number of columns - * - * @param array $data - * @param array $classes - * - * @return Matrix */ - protected function calculateClassVar($data, $classes) + protected function calculateClassVar(array $data, array $classes) : Matrix { // s is an n (number of classes) by m (number of column) matrix $s = array_fill(0, count($data[0]), array_fill(0, count($data[0]), 0)); @@ -187,10 +168,8 @@ protected function calculateClassVar($data, $classes) * Returns between-class scatter matrix for each class, which * is an n by m matrix where n is number of classes and * m is number of columns - * - * @return Matrix */ - protected function calculateClassCov() + protected function calculateClassCov() : Matrix { // s is an n (number of classes) by m (number of column) matrix $s = array_fill(0, count($this->overallMean), array_fill(0, count($this->overallMean), 0)); @@ -207,13 +186,8 @@ protected function calculateClassCov() /** * Returns the result of the calculation (x - m)T.(x - m) - * - * @param array $row - * @param array $means - * - * @return Matrix */ - protected function calculateVar(array $row, array $means) + protected function calculateVar(array $row, array $means) : Matrix { $x = new Matrix($row, false); $m = new Matrix($means, false); @@ -226,13 +200,9 @@ protected function calculateVar(array $row, array $means) * Transforms the given sample to a lower dimensional vector by using * the eigenVectors obtained in the last run of fit. * - * @param array $sample - * - * @return array - * * @throws \Exception */ - public function transform(array $sample) + public function transform(array $sample) : array { if (!$this->fit) { throw new \Exception('LDA has not been fitted with respect to original dataset, please run LDA::fit() first'); diff --git a/src/Phpml/DimensionReduction/PCA.php b/src/Phpml/DimensionReduction/PCA.php index 7d3fd4ff..f5cb2190 100644 --- a/src/Phpml/DimensionReduction/PCA.php +++ b/src/Phpml/DimensionReduction/PCA.php @@ -32,7 +32,7 @@ class PCA extends EigenTransformerBase * * @throws \Exception */ - public function __construct($totalVariance = null, $numFeatures = null) + public function __construct(float $totalVariance = null, int $numFeatures = null) { if ($totalVariance !== null && ($totalVariance < 0.1 || $totalVariance > 0.99)) { throw new \Exception('Total variance can be a value between 0.1 and 0.99'); @@ -57,12 +57,8 @@ public function __construct($totalVariance = null, $numFeatures = null) * of this data while preserving $totalVariance or $numFeatures.
* $data is an n-by-m matrix and returned array is * n-by-k matrix where k <= m - * - * @param array $data - * - * @return array */ - public function fit(array $data) + public function fit(array $data) : array { $n = count($data[0]); @@ -77,10 +73,6 @@ public function fit(array $data) return $this->reduce($data); } - /** - * @param array $data - * @param int $n - */ protected function calculateMeans(array $data, int $n) { // Calculate means for each dimension @@ -94,13 +86,8 @@ protected function calculateMeans(array $data, int $n) /** * Normalization of the data includes subtracting mean from * each dimension therefore dimensions will be centered to zero - * - * @param array $data - * @param int $n - * - * @return array */ - protected function normalize(array $data, int $n) + protected function normalize(array $data, int $n) : array { if (empty($this->means)) { $this->calculateMeans($data, $n); @@ -120,13 +107,9 @@ protected function normalize(array $data, int $n) * Transforms the given sample to a lower dimensional vector by using * the eigenVectors obtained in the last run of fit. * - * @param array $sample - * - * @return array - * * @throws \Exception */ - public function transform(array $sample) + public function transform(array $sample) : array { if (!$this->fit) { throw new \Exception('PCA has not been fitted with respect to original dataset, please run PCA::fit() first'); diff --git a/src/Phpml/Exception/DatasetException.php b/src/Phpml/Exception/DatasetException.php index ca7b0656..5d3e0db7 100644 --- a/src/Phpml/Exception/DatasetException.php +++ b/src/Phpml/Exception/DatasetException.php @@ -6,12 +6,7 @@ class DatasetException extends \Exception { - /** - * @param string $path - * - * @return DatasetException - */ - public static function missingFolder(string $path) + public static function missingFolder(string $path) : DatasetException { return new self(sprintf('Dataset root folder "%s" missing.', $path)); } diff --git a/src/Phpml/Exception/FileException.php b/src/Phpml/Exception/FileException.php index 20b29360..39b9b03d 100644 --- a/src/Phpml/Exception/FileException.php +++ b/src/Phpml/Exception/FileException.php @@ -6,32 +6,17 @@ class FileException extends \Exception { - /** - * @param string $filepath - * - * @return FileException - */ - public static function missingFile(string $filepath) + public static function missingFile(string $filepath) : FileException { return new self(sprintf('File "%s" missing.', $filepath)); } - /** - * @param string $filepath - * - * @return FileException - */ - public static function cantOpenFile(string $filepath) + public static function cantOpenFile(string $filepath) : FileException { return new self(sprintf('File "%s" can\'t be open.', $filepath)); } - /** - * @param string $filepath - * - * @return FileException - */ - public static function cantSaveFile(string $filepath) + public static function cantSaveFile(string $filepath) : FileException { return new self(sprintf('File "%s" can\'t be saved.', $filepath)); } diff --git a/src/Phpml/Exception/InvalidArgumentException.php b/src/Phpml/Exception/InvalidArgumentException.php index 8dcbd03c..d96bb33a 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -6,164 +6,95 @@ class InvalidArgumentException extends \Exception { - /** - * @return InvalidArgumentException - */ - public static function arraySizeNotMatch() + public static function arraySizeNotMatch() : InvalidArgumentException { return new self('Size of given arrays does not match'); } - /** - * @param $name - * - * @return InvalidArgumentException - */ - public static function percentNotInRange($name) + public static function percentNotInRange($name) : InvalidArgumentException { return new self(sprintf('%s must be between 0.0 and 1.0', $name)); } - /** - * @return InvalidArgumentException - */ - public static function arrayCantBeEmpty() + public static function arrayCantBeEmpty() : InvalidArgumentException { return new self('The array has zero elements'); } - /** - * @param int $minimumSize - * - * @return InvalidArgumentException - */ - public static function arraySizeToSmall($minimumSize = 2) + public static function arraySizeToSmall(int $minimumSize = 2) : InvalidArgumentException { return new self(sprintf('The array must have at least %d elements', $minimumSize)); } - /** - * @return InvalidArgumentException - */ - public static function matrixDimensionsDidNotMatch() + public static function matrixDimensionsDidNotMatch() : InvalidArgumentException { return new self('Matrix dimensions did not match'); } - /** - * @return InvalidArgumentException - */ - public static function inconsistentMatrixSupplied() + public static function inconsistentMatrixSupplied() : InvalidArgumentException { return new self('Inconsistent matrix supplied'); } - /** - * @return InvalidArgumentException - */ - public static function invalidClustersNumber() + public static function invalidClustersNumber() : InvalidArgumentException { return new self('Invalid clusters number'); } /** * @param mixed $target - * - * @return InvalidArgumentException */ - public static function invalidTarget($target) + public static function invalidTarget($target) : InvalidArgumentException { return new self(sprintf('Target with value "%s" is not part of the accepted classes', $target)); } - /** - * @param string $language - * - * @return InvalidArgumentException - */ - public static function invalidStopWordsLanguage(string $language) + public static function invalidStopWordsLanguage(string $language) : InvalidArgumentException { return new self(sprintf('Can\'t find "%s" language for StopWords', $language)); } - /** - * @return InvalidArgumentException - */ - public static function invalidLayerNodeClass() + public static function invalidLayerNodeClass() : InvalidArgumentException { return new self('Layer node class must implement Node interface'); } - /** - * @return InvalidArgumentException - */ - public static function invalidLayersNumber() + public static function invalidLayersNumber() : InvalidArgumentException { return new self('Provide at least 1 hidden layer'); } - /** - * @return InvalidArgumentException - */ - public static function invalidClassesNumber() + public static function invalidClassesNumber() : InvalidArgumentException { return new self('Provide at least 2 different classes'); } - /** - * @return InvalidArgumentException - */ - public static function inconsistentClasses() + public static function inconsistentClasses() : InvalidArgumentException { return new self('The provided classes don\'t match the classes provided in the constructor'); } - /** - * @param string $file - * - * @return InvalidArgumentException - */ - public static function fileNotFound(string $file) + public static function fileNotFound(string $file) : InvalidArgumentException { return new self(sprintf('File "%s" not found', $file)); } - /** - * @param string $file - * - * @return InvalidArgumentException - */ - public static function fileNotExecutable(string $file) + public static function fileNotExecutable(string $file) : InvalidArgumentException { return new self(sprintf('File "%s" is not executable', $file)); } - /** - * @param string $path - * - * @return InvalidArgumentException - */ - public static function pathNotFound(string $path) + public static function pathNotFound(string $path) : InvalidArgumentException { return new self(sprintf('The specified path "%s" does not exist', $path)); } - /** - * @param string $path - * - * @return InvalidArgumentException - */ - public static function pathNotWritable(string $path) + public static function pathNotWritable(string $path) : InvalidArgumentException { return new self(sprintf('The specified path "%s" is not writable', $path)); } - /** - * @param string $operator - * - * @return InvalidArgumentException - */ - public static function invalidOperator(string $operator) + public static function invalidOperator(string $operator) : InvalidArgumentException { return new self(sprintf('Invalid operator "%s" provided', $operator)); } diff --git a/src/Phpml/Exception/MatrixException.php b/src/Phpml/Exception/MatrixException.php index 1b016597..a52feaad 100644 --- a/src/Phpml/Exception/MatrixException.php +++ b/src/Phpml/Exception/MatrixException.php @@ -6,26 +6,17 @@ class MatrixException extends \Exception { - /** - * @return MatrixException - */ - public static function notSquareMatrix() + public static function notSquareMatrix() : MatrixException { return new self('Matrix is not square matrix'); } - /** - * @return MatrixException - */ - public static function columnOutOfRange() + public static function columnOutOfRange() : MatrixException { return new self('Column out of range'); } - /** - * @return MatrixException - */ - public static function singularMatrix() + public static function singularMatrix() : MatrixException { return new self('Matrix is singular'); } diff --git a/src/Phpml/Exception/NormalizerException.php b/src/Phpml/Exception/NormalizerException.php index e9762d37..a7604e86 100644 --- a/src/Phpml/Exception/NormalizerException.php +++ b/src/Phpml/Exception/NormalizerException.php @@ -6,10 +6,7 @@ class NormalizerException extends \Exception { - /** - * @return NormalizerException - */ - public static function unknownNorm() + public static function unknownNorm() : NormalizerException { return new self('Unknown norm supplied.'); } diff --git a/src/Phpml/Exception/SerializeException.php b/src/Phpml/Exception/SerializeException.php index 5753eb7e..913667a0 100644 --- a/src/Phpml/Exception/SerializeException.php +++ b/src/Phpml/Exception/SerializeException.php @@ -6,22 +6,12 @@ class SerializeException extends \Exception { - /** - * @param string $filepath - * - * @return SerializeException - */ - public static function cantUnserialize(string $filepath) + public static function cantUnserialize(string $filepath) : SerializeException { return new self(sprintf('"%s" can not be unserialized.', $filepath)); } - /** - * @param string $classname - * - * @return SerializeException - */ - public static function cantSerialize(string $classname) + public static function cantSerialize(string $classname) : SerializeException { return new self(sprintf('Class "%s" can not be serialized.', $classname)); } diff --git a/src/Phpml/FeatureExtraction/StopWords.php b/src/Phpml/FeatureExtraction/StopWords.php index ad01bbb5..b6717b94 100644 --- a/src/Phpml/FeatureExtraction/StopWords.php +++ b/src/Phpml/FeatureExtraction/StopWords.php @@ -13,32 +13,17 @@ class StopWords */ protected $stopWords; - /** - * @param array $stopWords - */ public function __construct(array $stopWords) { $this->stopWords = array_fill_keys($stopWords, true); } - /** - * @param string $token - * - * @return bool - */ - public function isStopWord(string $token): bool + public function isStopWord(string $token) : bool { return isset($this->stopWords[$token]); } - /** - * @param string $language - * - * @return StopWords - * - * @throws InvalidArgumentException - */ - public static function factory($language = 'English'): StopWords + public static function factory(string $language = 'English') : StopWords { $className = __NAMESPACE__."\\StopWords\\$language"; diff --git a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php index f5fab21c..eb7a4ac6 100644 --- a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php +++ b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php @@ -34,11 +34,6 @@ class TokenCountVectorizer implements Transformer */ private $frequencies; - /** - * @param Tokenizer $tokenizer - * @param StopWords $stopWords - * @param float $minDF - */ public function __construct(Tokenizer $tokenizer, StopWords $stopWords = null, float $minDF = 0.0) { $this->tokenizer = $tokenizer; @@ -49,17 +44,11 @@ public function __construct(Tokenizer $tokenizer, StopWords $stopWords = null, f $this->frequencies = []; } - /** - * @param array $samples - */ public function fit(array $samples) { $this->buildVocabulary($samples); } - /** - * @param array $samples - */ public function transform(array &$samples) { foreach ($samples as &$sample) { @@ -69,17 +58,11 @@ public function transform(array &$samples) $this->checkDocumentFrequency($samples); } - /** - * @return array - */ - public function getVocabulary() + public function getVocabulary() : array { return array_flip($this->vocabulary); } - /** - * @param array $samples - */ private function buildVocabulary(array &$samples) { foreach ($samples as $index => $sample) { @@ -90,9 +73,6 @@ private function buildVocabulary(array &$samples) } } - /** - * @param string $sample - */ private function transformSample(string &$sample) { $counts = []; @@ -122,8 +102,6 @@ private function transformSample(string &$sample) } /** - * @param string $token - * * @return int|bool */ private function getTokenIndex(string $token) @@ -135,9 +113,6 @@ private function getTokenIndex(string $token) return $this->vocabulary[$token] ?? false; } - /** - * @param string $token - */ private function addTokenToVocabulary(string $token) { if ($this->isStopWord($token)) { @@ -149,19 +124,11 @@ private function addTokenToVocabulary(string $token) } } - /** - * @param string $token - * - * @return bool - */ private function isStopWord(string $token): bool { return $this->stopWords && $this->stopWords->isStopWord($token); } - /** - * @param string $token - */ private function updateFrequency(string $token) { if (!isset($this->frequencies[$token])) { @@ -171,9 +138,6 @@ private function updateFrequency(string $token) ++$this->frequencies[$token]; } - /** - * @param array $samples - */ private function checkDocumentFrequency(array &$samples) { if ($this->minDF > 0) { @@ -184,10 +148,6 @@ private function checkDocumentFrequency(array &$samples) } } - /** - * @param array $sample - * @param array $beyondMinimum - */ private function resetBeyondMinimum(array &$sample, array $beyondMinimum) { foreach ($beyondMinimum as $index) { @@ -195,12 +155,7 @@ private function resetBeyondMinimum(array &$sample, array $beyondMinimum) } } - /** - * @param int $samplesCount - * - * @return array - */ - private function getBeyondMinimumIndexes(int $samplesCount) + private function getBeyondMinimumIndexes(int $samplesCount) : array { $indexes = []; foreach ($this->frequencies as $token => $frequency) { diff --git a/src/Phpml/Helper/OneVsRest.php b/src/Phpml/Helper/OneVsRest.php index 7776ccdc..5ae126c6 100644 --- a/src/Phpml/Helper/OneVsRest.php +++ b/src/Phpml/Helper/OneVsRest.php @@ -27,9 +27,6 @@ trait OneVsRest /** * Train a binary classifier in the OvR style - * - * @param array $samples - * @param array $targets */ public function train(array $samples, array $targets) { @@ -39,13 +36,6 @@ public function train(array $samples, array $targets) $this->trainBylabel($samples, $targets); } - /** - * @param array $samples - * @param array $targets - * @param array $allLabels All training set labels - * - * @return void - */ protected function trainByLabel(array $samples, array $targets, array $allLabels = []) { // Overwrites the current value if it exist. $allLabels must be provided for each partialTrain run. @@ -122,12 +112,11 @@ protected function getClassifierCopy() * $targets is not passed by reference nor contains objects so this method * changes will not affect the caller $targets array. * - * @param array $targets * @param mixed $label * * @return array Binarized targets and target's labels */ - private function binarizeTargets($targets, $label) + private function binarizeTargets(array $targets, $label) : array { $notLabel = "not_$label"; foreach ($targets as $key => $target) { @@ -140,8 +129,6 @@ private function binarizeTargets($targets, $label) } /** - * @param array $sample - * * @return mixed */ protected function predictSample(array $sample) @@ -163,10 +150,6 @@ protected function predictSample(array $sample) /** * Each classifier should implement this method instead of train(samples, targets) - * - * @param array $samples - * @param array $targets - * @param array $labels */ abstract protected function trainBinary(array $samples, array $targets, array $labels); @@ -181,9 +164,6 @@ abstract protected function resetBinary(); * Each classifier that make use of OvR approach should be able to * return a probability for a sample to belong to the given label. * - * @param array $sample - * @param string $label - * * @return mixed */ abstract protected function predictProbability(array $sample, string $label); @@ -191,8 +171,6 @@ abstract protected function predictProbability(array $sample, string $label); /** * Each classifier should implement this method instead of predictSample() * - * @param array $sample - * * @return mixed */ abstract protected function predictSampleBinary(array $sample); diff --git a/src/Phpml/Helper/Optimizer/ConjugateGradient.php b/src/Phpml/Helper/Optimizer/ConjugateGradient.php index 67a65a46..994971d4 100644 --- a/src/Phpml/Helper/Optimizer/ConjugateGradient.php +++ b/src/Phpml/Helper/Optimizer/ConjugateGradient.php @@ -20,11 +20,10 @@ class ConjugateGradient extends GD /** * @param array $samples * @param array $targets - * @param \Closure $gradientCb * * @return array */ - public function runOptimization(array $samples, array $targets, \Closure $gradientCb) + public function runOptimization(array $samples, array $targets, \Closure $gradientCb) : array { $this->samples = $samples; $this->targets = $targets; @@ -65,12 +64,8 @@ public function runOptimization(array $samples, array $targets, \Closure $gradie /** * Executes the callback function for the problem and returns * sum of the gradient for all samples & targets. - * - * @param array $theta - * - * @return array */ - protected function gradient(array $theta) + protected function gradient(array $theta) : array { list(, $gradient) = parent::gradient($theta); @@ -79,12 +74,8 @@ protected function gradient(array $theta) /** * Returns the value of f(x) for given solution - * - * @param array $theta - * - * @return float */ - protected function cost(array $theta) + protected function cost(array $theta) : float { list($cost) = parent::gradient($theta); @@ -104,12 +95,8 @@ protected function cost(array $theta) * b) Probe a larger alpha (0.01) and calculate cost function * b-1) If cost function decreases, continue enlarging alpha * b-2) If cost function increases, take the midpoint and try again - * - * @param float $d - * - * @return float */ - protected function getAlpha(float $d) + protected function getAlpha(float $d) : float { $small = 0.0001 * $d; $large = 0.01 * $d; @@ -153,13 +140,8 @@ protected function getAlpha(float $d) * gradient direction. * * θ(k+1) = θ(k) + α.d - * - * @param float $alpha - * @param array $d - * - * @return array */ - protected function getNewTheta(float $alpha, array $d) + protected function getNewTheta(float $alpha, array $d) : array { $theta = $this->theta; @@ -187,12 +169,8 @@ protected function getNewTheta(float $alpha, array $d) * * See: * R. Fletcher and C. M. Reeves, "Function minimization by conjugate gradients", Comput. J. 7 (1964), 149–154. - * - * @param array $newTheta - * - * @return float */ - protected function getBeta(array $newTheta) + protected function getBeta(array $newTheta) : float { $dNew = array_sum($this->gradient($newTheta)); $dOld = array_sum($this->gradient($this->theta)) + 1e-100; @@ -204,14 +182,8 @@ protected function getBeta(array $newTheta) * Calculates the new conjugate direction * * d(k+1) =–∇f(x(k+1)) + β(k).d(k) - * - * @param array $theta - * @param float $beta - * @param array $d - * - * @return array */ - protected function getNewDirection(array $theta, float $beta, array $d) + protected function getNewDirection(array $theta, float $beta, array $d) : array { $grad = $this->gradient($theta); @@ -227,13 +199,8 @@ class mp { /** * Element-wise multiplication of two vectors of the same size - * - * @param array $m1 - * @param array $m2 - * - * @return array */ - public static function mul(array $m1, array $m2) + public static function mul(array $m1, array $m2) : array { $res = []; foreach ($m1 as $i => $val) { @@ -245,13 +212,8 @@ public static function mul(array $m1, array $m2) /** * Element-wise division of two vectors of the same size - * - * @param array $m1 - * @param array $m2 - * - * @return array */ - public static function div(array $m1, array $m2) + public static function div(array $m1, array $m2) : array { $res = []; foreach ($m1 as $i => $val) { @@ -263,14 +225,8 @@ public static function div(array $m1, array $m2) /** * Element-wise addition of two vectors of the same size - * - * @param array $m1 - * @param array $m2 - * @param int $mag - * - * @return array */ - public static function add(array $m1, array $m2, int $mag = 1) + public static function add(array $m1, array $m2, int $mag = 1) : array { $res = []; foreach ($m1 as $i => $val) { @@ -282,26 +238,16 @@ public static function add(array $m1, array $m2, int $mag = 1) /** * Element-wise subtraction of two vectors of the same size - * - * @param array $m1 - * @param array $m2 - * - * @return array */ - public static function sub(array $m1, array $m2) + public static function sub(array $m1, array $m2) : array { return self::add($m1, $m2, -1); } /** * Element-wise multiplication of a vector with a scalar - * - * @param array $m1 - * @param float $m2 - * - * @return array */ - public static function muls(array $m1, float $m2) + public static function muls(array $m1, float $m2) : array { $res = []; foreach ($m1 as $val) { @@ -313,13 +259,8 @@ public static function muls(array $m1, float $m2) /** * Element-wise division of a vector with a scalar - * - * @param array $m1 - * @param float $m2 - * - * @return array */ - public static function divs(array $m1, float $m2) + public static function divs(array $m1, float $m2) : array { $res = []; foreach ($m1 as $val) { @@ -331,14 +272,8 @@ public static function divs(array $m1, float $m2) /** * Element-wise addition of a vector with a scalar - * - * @param array $m1 - * @param float $m2 - * @param int $mag - * - * @return array */ - public static function adds(array $m1, float $m2, int $mag = 1) + public static function adds(array $m1, float $m2, int $mag = 1) : array { $res = []; foreach ($m1 as $val) { @@ -350,13 +285,8 @@ public static function adds(array $m1, float $m2, int $mag = 1) /** * Element-wise subtraction of a vector with a scalar - * - * @param array $m1 - * @param float $m2 - * - * @return array */ - public static function subs(array $m1, float $m2) + public static function subs(array $m1, float $m2) : array { return self::adds($m1, $m2, -1); } diff --git a/src/Phpml/Helper/Optimizer/GD.php b/src/Phpml/Helper/Optimizer/GD.php index 8babc7d9..ae3c6e21 100644 --- a/src/Phpml/Helper/Optimizer/GD.php +++ b/src/Phpml/Helper/Optimizer/GD.php @@ -17,14 +17,7 @@ class GD extends StochasticGD */ protected $sampleCount = null; - /** - * @param array $samples - * @param array $targets - * @param \Closure $gradientCb - * - * @return array - */ - public function runOptimization(array $samples, array $targets, \Closure $gradientCb) + public function runOptimization(array $samples, array $targets, \Closure $gradientCb) : array { $this->samples = $samples; $this->targets = $targets; @@ -57,12 +50,8 @@ public function runOptimization(array $samples, array $targets, \Closure $gradie /** * Calculates gradient, cost function and penalty term for each sample * then returns them as an array of values - * - * @param array $theta - * - * @return array */ - protected function gradient(array $theta) + protected function gradient(array $theta) : array { $costs = []; $gradient = []; @@ -84,10 +73,6 @@ protected function gradient(array $theta) return [$costs, $gradient, $totalPenalty]; } - /** - * @param array $updates - * @param float $penalty - */ protected function updateWeightsWithUpdates(array $updates, float $penalty) { // Updates all weights at once @@ -110,8 +95,6 @@ protected function updateWeightsWithUpdates(array $updates, float $penalty) /** * Clears the optimizer internal vars after the optimization process. - * - * @return void */ protected function clear() { diff --git a/src/Phpml/Helper/Optimizer/Optimizer.php b/src/Phpml/Helper/Optimizer/Optimizer.php index 24787e16..ee613215 100644 --- a/src/Phpml/Helper/Optimizer/Optimizer.php +++ b/src/Phpml/Helper/Optimizer/Optimizer.php @@ -22,8 +22,6 @@ abstract class Optimizer /** * Inits a new instance of Optimizer for the given number of dimensions - * - * @param int $dimensions */ public function __construct(int $dimensions) { @@ -39,8 +37,6 @@ public function __construct(int $dimensions) /** * Sets the weights manually * - * @param array $theta - * * @return $this * * @throws \Exception @@ -59,10 +55,6 @@ public function setInitialTheta(array $theta) /** * Executes the optimization with the given samples & targets * and returns the weights - * - * @param array $samples - * @param array $targets - * @param \Closure $gradientCb */ abstract public function runOptimization(array $samples, array $targets, \Closure $gradientCb); } diff --git a/src/Phpml/Helper/Optimizer/StochasticGD.php b/src/Phpml/Helper/Optimizer/StochasticGD.php index df292616..0a622d42 100644 --- a/src/Phpml/Helper/Optimizer/StochasticGD.php +++ b/src/Phpml/Helper/Optimizer/StochasticGD.php @@ -76,8 +76,6 @@ class StochasticGD extends Optimizer /** * Initializes the SGD optimizer for the given number of dimensions - * - * @param int $dimensions */ public function __construct(int $dimensions) { @@ -94,8 +92,6 @@ public function __construct(int $dimensions) * If change in the theta is less than given value then the * algorithm will stop training * - * @param float $threshold - * * @return $this */ public function setChangeThreshold(float $threshold = 1e-5) @@ -109,8 +105,6 @@ public function setChangeThreshold(float $threshold = 1e-5) * Enable/Disable early stopping by checking at each iteration * whether changes in theta or cost value are not large enough * - * @param bool $enable - * * @return $this */ public function setEarlyStop(bool $enable = true) @@ -121,8 +115,6 @@ public function setEarlyStop(bool $enable = true) } /** - * @param float $learningRate - * * @return $this */ public function setLearningRate(float $learningRate) @@ -133,8 +125,6 @@ public function setLearningRate(float $learningRate) } /** - * @param int $maxIterations - * * @return $this */ public function setMaxIterations(int $maxIterations) @@ -150,14 +140,8 @@ public function setMaxIterations(int $maxIterations) * * The cost function to minimize and the gradient of the function are to be * handled by the callback function provided as the third parameter of the method. - * - * @param array $samples - * @param array $targets - * @param \Closure $gradientCb - * - * @return array */ - public function runOptimization(array $samples, array $targets, \Closure $gradientCb) + public function runOptimization(array $samples, array $targets, \Closure $gradientCb) : array { $this->samples = $samples; $this->targets = $targets; @@ -197,10 +181,7 @@ public function runOptimization(array $samples, array $targets, \Closure $gradie return $this->theta = $bestTheta; } - /** - * @return float - */ - protected function updateTheta() + protected function updateTheta() : float { $jValue = 0.0; $theta = $this->theta; @@ -231,12 +212,8 @@ protected function updateTheta() /** * Checks if the optimization is not effective enough and can be stopped * in case large enough changes in the solution do not happen - * - * @param array $oldTheta - * - * @return boolean */ - protected function earlyStop($oldTheta) + protected function earlyStop(array $oldTheta): bool { // Check for early stop: No change larger than threshold (default 1e-5) $diff = array_map( @@ -263,18 +240,14 @@ function ($w1, $w2) { /** * Returns the list of cost values for each iteration executed in * last run of the optimization - * - * @return array */ - public function getCostValues() + public function getCostValues() : array { return $this->costValues; } /** * Clears the optimizer internal vars after the optimization process. - * - * @return void */ protected function clear() { diff --git a/src/Phpml/Math/Comparison.php b/src/Phpml/Math/Comparison.php index 1c8b6aae..de7414d1 100644 --- a/src/Phpml/Math/Comparison.php +++ b/src/Phpml/Math/Comparison.php @@ -9,12 +9,6 @@ class Comparison { /** - * @param mixed $a - * @param mixed $b - * @param string $operator - * - * @return bool - * * @throws InvalidArgumentException */ public static function compare($a, $b, string $operator): bool diff --git a/src/Phpml/Math/Distance.php b/src/Phpml/Math/Distance.php index 3df5c2c7..696ee4b1 100644 --- a/src/Phpml/Math/Distance.php +++ b/src/Phpml/Math/Distance.php @@ -9,8 +9,6 @@ interface Distance /** * @param array $a * @param array $b - * - * @return float */ - public function distance(array $a, array $b): float; + public function distance(array $a, array $b) : float; } diff --git a/src/Phpml/Math/Distance/Chebyshev.php b/src/Phpml/Math/Distance/Chebyshev.php index ed2911c4..40cdfbc4 100644 --- a/src/Phpml/Math/Distance/Chebyshev.php +++ b/src/Phpml/Math/Distance/Chebyshev.php @@ -10,14 +10,9 @@ class Chebyshev implements Distance { /** - * @param array $a - * @param array $b - * - * @return float - * * @throws InvalidArgumentException */ - public function distance(array $a, array $b): float + public function distance(array $a, array $b) : float { if (count($a) !== count($b)) { throw InvalidArgumentException::arraySizeNotMatch(); diff --git a/src/Phpml/Math/Distance/Euclidean.php b/src/Phpml/Math/Distance/Euclidean.php index 1158f5d8..f6a87cf1 100644 --- a/src/Phpml/Math/Distance/Euclidean.php +++ b/src/Phpml/Math/Distance/Euclidean.php @@ -10,14 +10,9 @@ class Euclidean implements Distance { /** - * @param array $a - * @param array $b - * - * @return float - * * @throws InvalidArgumentException */ - public function distance(array $a, array $b): float + public function distance(array $a, array $b) : float { if (count($a) !== count($b)) { throw InvalidArgumentException::arraySizeNotMatch(); @@ -34,13 +29,8 @@ public function distance(array $a, array $b): float /** * Square of Euclidean distance - * - * @param array $a - * @param array $b - * - * @return float */ - public function sqDistance(array $a, array $b): float + public function sqDistance(array $a, array $b) : float { return $this->distance($a, $b) ** 2; } diff --git a/src/Phpml/Math/Distance/Manhattan.php b/src/Phpml/Math/Distance/Manhattan.php index b6f6eb8b..72a9d1fd 100644 --- a/src/Phpml/Math/Distance/Manhattan.php +++ b/src/Phpml/Math/Distance/Manhattan.php @@ -10,14 +10,9 @@ class Manhattan implements Distance { /** - * @param array $a - * @param array $b - * - * @return float - * * @throws InvalidArgumentException */ - public function distance(array $a, array $b): float + public function distance(array $a, array $b) : float { if (count($a) !== count($b)) { throw InvalidArgumentException::arraySizeNotMatch(); diff --git a/src/Phpml/Math/Distance/Minkowski.php b/src/Phpml/Math/Distance/Minkowski.php index 2af835e9..17df39d2 100644 --- a/src/Phpml/Math/Distance/Minkowski.php +++ b/src/Phpml/Math/Distance/Minkowski.php @@ -14,23 +14,15 @@ class Minkowski implements Distance */ private $lambda; - /** - * @param float $lambda - */ public function __construct(float $lambda = 3.0) { $this->lambda = $lambda; } /** - * @param array $a - * @param array $b - * - * @return float - * * @throws InvalidArgumentException */ - public function distance(array $a, array $b): float + public function distance(array $a, array $b) : float { if (count($a) !== count($b)) { throw InvalidArgumentException::arraySizeNotMatch(); diff --git a/src/Phpml/Math/Kernel/RBF.php b/src/Phpml/Math/Kernel/RBF.php index 2cd92db2..e47dbb52 100644 --- a/src/Phpml/Math/Kernel/RBF.php +++ b/src/Phpml/Math/Kernel/RBF.php @@ -14,9 +14,6 @@ class RBF implements Kernel */ private $gamma; - /** - * @param float $gamma - */ public function __construct(float $gamma) { $this->gamma = $gamma; @@ -25,8 +22,6 @@ public function __construct(float $gamma) /** * @param array $a * @param array $b - * - * @return float */ public function compute($a, $b) { diff --git a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php index c67673b9..d24b1a9e 100644 --- a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -835,7 +835,6 @@ private function hqr2() /** * Return the eigenvector matrix * - * * @return array */ public function getEigenvectors() diff --git a/src/Phpml/Math/LinearAlgebra/LUDecomposition.php b/src/Phpml/Math/LinearAlgebra/LUDecomposition.php index 7a143f1d..164a72f6 100644 --- a/src/Phpml/Math/LinearAlgebra/LUDecomposition.php +++ b/src/Phpml/Math/LinearAlgebra/LUDecomposition.php @@ -2,25 +2,25 @@ declare(strict_types=1); /** - * @package JAMA + * @package JAMA * - * For an m-by-n matrix A with m >= n, the LU decomposition is an m-by-n - * unit lower triangular matrix L, an n-by-n upper triangular matrix U, - * and a permutation vector piv of length m so that A(piv,:) = L*U. - * If m < n, then L is m-by-m and U is m-by-n. + * For an m-by-n matrix A with m >= n, the LU decomposition is an m-by-n + * unit lower triangular matrix L, an n-by-n upper triangular matrix U, + * and a permutation vector piv of length m so that A(piv,:) = L*U. + * If m < n, then L is m-by-m and U is m-by-n. * - * The LU decompostion with pivoting always exists, even if the matrix is - * singular, so the constructor will never fail. The primary use of the - * LU decomposition is in the solution of square systems of simultaneous - * linear equations. This will fail if isNonsingular() returns false. + * The LU decompostion with pivoting always exists, even if the matrix is + * singular, so the constructor will never fail. The primary use of the + * LU decomposition is in the solution of square systems of simultaneous + * linear equations. This will fail if isNonsingular() returns false. * - * @author Paul Meagher - * @author Bartosz Matosiuk - * @author Michael Bommarito + * @author Paul Meagher + * @author Bartosz Matosiuk + * @author Michael Bommarito * - * @version 1.1 + * @version 1.1 * - * @license PHP v3.0 + * @license PHP v3.0 * * Slightly changed to adapt the original code to PHP-ML library * @date 2017/04/24 @@ -30,43 +30,43 @@ namespace Phpml\Math\LinearAlgebra; -use Phpml\Math\Matrix; use Phpml\Exception\MatrixException; +use Phpml\Math\Matrix; class LUDecomposition { /** - * Decomposition storage + * Decomposition storage * - * @var array + * @var array */ private $LU = []; /** - * Row dimension. + * Row dimension. * - * @var int + * @var int */ private $m; /** - * Column dimension. + * Column dimension. * - * @var int + * @var int */ private $n; /** - * Pivot sign. + * Pivot sign. * - * @var int + * @var int */ private $pivsign; /** - * Internal storage of pivot vector. + * Internal storage of pivot vector. * - * @var array + * @var array */ private $piv = []; @@ -142,7 +142,7 @@ public function __construct(Matrix $A) * * @return Matrix Lower triangular factor */ - public function getL() + public function getL() : Matrix { $L = []; for ($i = 0; $i < $this->m; ++$i) { @@ -165,7 +165,7 @@ public function getL() * * @return Matrix Upper triangular factor */ - public function getU() + public function getU() : Matrix { $U = []; for ($i = 0; $i < $this->n; ++$i) { @@ -186,7 +186,7 @@ public function getU() * * @return array Pivot vector */ - public function getPivot() + public function getPivot() : array { return $this->piv; } @@ -247,7 +247,7 @@ public function det() * * @throws MatrixException */ - public function solve(Matrix $B) + public function solve(Matrix $B) : array { if ($B->getRows() != $this->m) { throw MatrixException::notSquareMatrix(); @@ -283,15 +283,7 @@ public function solve(Matrix $B) return $X; } - /** - * @param array $matrix - * @param array $RL - * @param int $j0 - * @param int $jF - * - * @return array - */ - protected function getSubMatrix(array $matrix, array $RL, int $j0, int $jF) + protected function getSubMatrix(array $matrix, array $RL, int $j0, int $jF) : array { $m = count($RL); $n = $jF - $j0; diff --git a/src/Phpml/Math/Matrix.php b/src/Phpml/Math/Matrix.php index fd91234f..6145521a 100644 --- a/src/Phpml/Math/Matrix.php +++ b/src/Phpml/Math/Matrix.php @@ -4,9 +4,9 @@ namespace Phpml\Math; -use Phpml\Math\LinearAlgebra\LUDecomposition; use Phpml\Exception\InvalidArgumentException; use Phpml\Exception\MatrixException; +use Phpml\Math\LinearAlgebra\LUDecomposition; class Matrix { @@ -31,9 +31,6 @@ class Matrix private $determinant; /** - * @param array $matrix - * @param bool $validate - * * @throws InvalidArgumentException */ public function __construct(array $matrix, bool $validate = true) @@ -59,12 +56,7 @@ public function __construct(array $matrix, bool $validate = true) $this->matrix = $matrix; } - /** - * @param array $array - * - * @return Matrix - */ - public static function fromFlatArray(array $array) + public static function fromFlatArray(array $array) : Matrix { $matrix = []; foreach ($array as $value) { @@ -74,46 +66,30 @@ public static function fromFlatArray(array $array) return new self($matrix); } - /** - * @return array - */ - public function toArray() + public function toArray() : array { return $this->matrix; } - /** - * @return float - */ - public function toScalar() + public function toScalar() : float { return $this->matrix[0][0]; } - /** - * @return int - */ - public function getRows() + public function getRows(): int { return $this->rows; } - /** - * @return int - */ - public function getColumns() + public function getColumns(): int { return $this->columns; } /** - * @param $column - * - * @return array - * * @throws MatrixException */ - public function getColumnValues($column) + public function getColumnValues($column) : array { if ($column >= $this->columns) { throw MatrixException::columnOutOfRange(); @@ -142,18 +118,12 @@ public function getDeterminant() return $this->determinant = $lu->det(); } - /** - * @return bool - */ - public function isSquare() + public function isSquare(): bool { return $this->columns === $this->rows; } - /** - * @return Matrix - */ - public function transpose() + public function transpose() : Matrix { if ($this->rows == 1) { $matrix = array_map(function ($el) { @@ -166,14 +136,7 @@ public function transpose() return new self($matrix, false); } - /** - * @param Matrix $matrix - * - * @return Matrix - * - * @throws InvalidArgumentException - */ - public function multiply(Matrix $matrix) + public function multiply(Matrix $matrix) : Matrix { if ($this->columns != $matrix->getRows()) { throw InvalidArgumentException::inconsistentMatrixSupplied(); @@ -194,12 +157,7 @@ public function multiply(Matrix $matrix) return new self($product, false); } - /** - * @param $value - * - * @return Matrix - */ - public function divideByScalar($value) + public function divideByScalar($value) : Matrix { $newMatrix = []; for ($i = 0; $i < $this->rows; ++$i) { @@ -211,12 +169,7 @@ public function divideByScalar($value) return new self($newMatrix, false); } - /** - * @param $value - * - * @return Matrix - */ - public function multiplyByScalar($value) + public function multiplyByScalar($value) : Matrix { $newMatrix = []; for ($i = 0; $i < $this->rows; ++$i) { @@ -230,37 +183,24 @@ public function multiplyByScalar($value) /** * Element-wise addition of the matrix with another one - * - * @param Matrix $other - * - * @return Matrix */ - public function add(Matrix $other) + public function add(Matrix $other) : Matrix { return $this->_add($other); } /** * Element-wise subtracting of another matrix from this one - * - * @param Matrix $other - * - * @return Matrix */ - public function subtract(Matrix $other) + public function subtract(Matrix $other) : Matrix { return $this->_add($other, -1); } /** * Element-wise addition or substraction depending on the given sign parameter - * - * @param Matrix $other - * @param int $sign - * - * @return Matrix */ - protected function _add(Matrix $other, $sign = 1) + protected function _add(Matrix $other, int $sign = 1) : Matrix { $a1 = $this->toArray(); $a2 = $other->toArray(); @@ -275,12 +215,7 @@ protected function _add(Matrix $other, $sign = 1) return new self($newMatrix, false); } - /** - * @return Matrix - * - * @throws MatrixException - */ - public function inverse() + public function inverse() : Matrix { if (!$this->isSquare()) { throw MatrixException::notSquareMatrix(); @@ -295,10 +230,8 @@ public function inverse() /** * Returns diagonal identity matrix of the same size of this matrix - * - * @return Matrix */ - protected function getIdentity() + protected function getIdentity() : Matrix { $array = array_fill(0, $this->rows, array_fill(0, $this->columns, 0)); for ($i = 0; $i < $this->rows; ++$i) { @@ -308,13 +241,7 @@ protected function getIdentity() return new self($array, false); } - /** - * @param int $row - * @param int $column - * - * @return Matrix - */ - public function crossOut(int $row, int $column) + public function crossOut(int $row, int $column) : Matrix { $newMatrix = []; $r = 0; @@ -334,9 +261,6 @@ public function crossOut(int $row, int $column) return new self($newMatrix, false); } - /** - * @return bool - */ public function isSingular() : bool { return 0 == $this->getDeterminant(); @@ -344,12 +268,8 @@ public function isSingular() : bool /** * Returns the transpose of given array - * - * @param array $array - * - * @return array */ - public static function transposeArray(array $array) + public static function transposeArray(array $array) : array { return (new self($array, false))->transpose()->toArray(); } @@ -357,13 +277,8 @@ public static function transposeArray(array $array) /** * Returns the dot product of two arrays
* Matrix::dot(x, y) ==> x.y' - * - * @param array $array1 - * @param array $array2 - * - * @return array */ - public static function dot(array $array1, array $array2) + public static function dot(array $array1, array $array2) : array { $m1 = new self($array1, false); $m2 = new self($array2, false); diff --git a/src/Phpml/Math/Set.php b/src/Phpml/Math/Set.php index 20fc7809..c5c5aa7c 100644 --- a/src/Phpml/Math/Set.php +++ b/src/Phpml/Math/Set.php @@ -21,11 +21,6 @@ public function __construct(array $elements = []) /** * Creates the union of A and B. - * - * @param Set $a - * @param Set $b - * - * @return Set */ public static function union(Set $a, Set $b) : Set { @@ -34,11 +29,6 @@ public static function union(Set $a, Set $b) : Set /** * Creates the intersection of A and B. - * - * @param Set $a - * @param Set $b - * - * @return Set */ public static function intersection(Set $a, Set $b) : Set { @@ -47,11 +37,6 @@ public static function intersection(Set $a, Set $b) : Set /** * Creates the difference of A and B. - * - * @param Set $a - * @param Set $b - * - * @return Set */ public static function difference(Set $a, Set $b) : Set { @@ -61,9 +46,6 @@ public static function difference(Set $a, Set $b) : Set /** * Creates the Cartesian product of A and B. * - * @param Set $a - * @param Set $b - * * @return Set[] */ public static function cartesian(Set $a, Set $b) : array @@ -82,8 +64,6 @@ public static function cartesian(Set $a, Set $b) : array /** * Creates the power set of A. * - * @param Set $a - * * @return Set[] */ public static function power(Set $a) : array @@ -115,8 +95,6 @@ private static function sanitize(array $elements) : array /** * @param string|int|float $element - * - * @return Set */ public function add($element) : Set { @@ -125,8 +103,6 @@ public function add($element) : Set /** * @param string[]|int[]|float[] $elements - * - * @return Set */ public function addAll(array $elements) : Set { @@ -137,8 +113,6 @@ public function addAll(array $elements) : Set /** * @param string|int|float $element - * - * @return Set */ public function remove($element) : Set { @@ -147,8 +121,6 @@ public function remove($element) : Set /** * @param string[]|int[]|float[] $elements - * - * @return Set */ public function removeAll(array $elements) : Set { @@ -159,8 +131,6 @@ public function removeAll(array $elements) : Set /** * @param string|int|float $element - * - * @return bool */ public function contains($element) : bool { @@ -169,8 +139,6 @@ public function contains($element) : bool /** * @param string[]|int[]|float[] $elements - * - * @return bool */ public function containsAll(array $elements) : bool { @@ -185,25 +153,16 @@ public function toArray() : array return $this->elements; } - /** - * @return \ArrayIterator - */ public function getIterator() : \ArrayIterator { return new \ArrayIterator($this->elements); } - /** - * @return bool - */ public function isEmpty() : bool { return $this->cardinality() == 0; } - /** - * @return int - */ public function cardinality() : int { return count($this->elements); diff --git a/src/Phpml/Math/Statistic/Correlation.php b/src/Phpml/Math/Statistic/Correlation.php index 0f60223f..9bcf271a 100644 --- a/src/Phpml/Math/Statistic/Correlation.php +++ b/src/Phpml/Math/Statistic/Correlation.php @@ -12,11 +12,9 @@ class Correlation * @param array|int[]|float[] $x * @param array|int[]|float[] $y * - * @return float - * * @throws InvalidArgumentException */ - public static function pearson(array $x, array $y) + public static function pearson(array $x, array $y) : float { if (count($x) !== count($y)) { throw InvalidArgumentException::arraySizeNotMatch(); diff --git a/src/Phpml/Math/Statistic/Covariance.php b/src/Phpml/Math/Statistic/Covariance.php index e0a239dc..2c72e6ca 100644 --- a/src/Phpml/Math/Statistic/Covariance.php +++ b/src/Phpml/Math/Statistic/Covariance.php @@ -11,17 +11,9 @@ class Covariance /** * Calculates covariance from two given arrays, x and y, respectively * - * @param array $x - * @param array $y - * @param bool $sample - * @param float $meanX - * @param float $meanY - * - * @return float - * * @throws InvalidArgumentException */ - public static function fromXYArrays(array $x, array $y, $sample = true, float $meanX = null, float $meanY = null) + public static function fromXYArrays(array $x, array $y, bool $sample = true, float $meanX = null, float $meanY = null) : float { if (empty($x) || empty($y)) { throw InvalidArgumentException::arrayCantBeEmpty(); @@ -56,19 +48,10 @@ public static function fromXYArrays(array $x, array $y, $sample = true, float $m /** * Calculates covariance of two dimensions, i and k in the given data. * - * @param array $data - * @param int $i - * @param int $k - * @param bool $sample - * @param float $meanX - * @param float $meanY - * - * @return float - * * @throws InvalidArgumentException * @throws \Exception */ - public static function fromDataset(array $data, int $i, int $k, bool $sample = true, float $meanX = null, float $meanY = null) + public static function fromDataset(array $data, int $i, int $k, bool $sample = true, float $meanX = null, float $meanY = null) : float { if (empty($data)) { throw InvalidArgumentException::arrayCantBeEmpty(); @@ -127,12 +110,9 @@ public static function fromDataset(array $data, int $i, int $k, bool $sample = t /** * Returns the covariance matrix of n-dimensional data * - * @param array $data * @param array|null $means - * - * @return array */ - public static function covarianceMatrix(array $data, array $means = null) + public static function covarianceMatrix(array $data, array $means = null) : array { $n = count($data[0]); diff --git a/src/Phpml/Math/Statistic/Gaussian.php b/src/Phpml/Math/Statistic/Gaussian.php index ae4c9a67..bdf83081 100644 --- a/src/Phpml/Math/Statistic/Gaussian.php +++ b/src/Phpml/Math/Statistic/Gaussian.php @@ -16,10 +16,6 @@ class Gaussian */ protected $std; - /** - * @param float $mean - * @param float $std - */ public function __construct(float $mean, float $std) { $this->mean = $mean; @@ -29,8 +25,6 @@ public function __construct(float $mean, float $std) /** * Returns probability density of the given $value * - * @param float $value - * * @return float|int */ public function pdf(float $value) @@ -46,14 +40,8 @@ public function pdf(float $value) /** * Returns probability density value of the given $value based on * given standard deviation and the mean - * - * @param float $mean - * @param float $std - * @param float $value - * - * @return float */ - public static function distributionPdf(float $mean, float $std, float $value) + public static function distributionPdf(float $mean, float $std, float $value) : float { $normal = new self($mean, $std); diff --git a/src/Phpml/Math/Statistic/Mean.php b/src/Phpml/Math/Statistic/Mean.php index 6dd9853b..22cd4bbe 100644 --- a/src/Phpml/Math/Statistic/Mean.php +++ b/src/Phpml/Math/Statistic/Mean.php @@ -11,11 +11,9 @@ class Mean /** * @param array $numbers * - * @return float - * * @throws InvalidArgumentException */ - public static function arithmetic(array $numbers) + public static function arithmetic(array $numbers) : float { self::checkArrayLength($numbers); diff --git a/src/Phpml/Math/Statistic/StandardDeviation.php b/src/Phpml/Math/Statistic/StandardDeviation.php index 76552831..3da8ef58 100644 --- a/src/Phpml/Math/Statistic/StandardDeviation.php +++ b/src/Phpml/Math/Statistic/StandardDeviation.php @@ -10,13 +10,10 @@ class StandardDeviation { /** * @param array|float[] $a - * @param bool $sample - * - * @return float * * @throws InvalidArgumentException */ - public static function population(array $a, $sample = true) + public static function population(array $a, bool $sample = true) : float { if (empty($a)) { throw InvalidArgumentException::arrayCantBeEmpty(); diff --git a/src/Phpml/Metric/ClassificationReport.php b/src/Phpml/Metric/ClassificationReport.php index 6fc026c0..dac82a07 100644 --- a/src/Phpml/Metric/ClassificationReport.php +++ b/src/Phpml/Metric/ClassificationReport.php @@ -31,10 +31,6 @@ class ClassificationReport */ private $average = []; - /** - * @param array $actualLabels - * @param array $predictedLabels - */ public function __construct(array $actualLabels, array $predictedLabels) { $truePositive = $falsePositive = $falseNegative = $this->support = self::getLabelIndexedArray($actualLabels, $predictedLabels); @@ -55,51 +51,31 @@ public function __construct(array $actualLabels, array $predictedLabels) $this->computeAverage(); } - /** - * @return array - */ - public function getPrecision() + public function getPrecision() : array { return $this->precision; } - /** - * @return array - */ - public function getRecall() + public function getRecall() : array { return $this->recall; } - /** - * @return array - */ - public function getF1score() + public function getF1score() : array { return $this->f1score; } - /** - * @return array - */ - public function getSupport() + public function getSupport() : array { return $this->support; } - /** - * @return array - */ - public function getAverage() + public function getAverage() : array { return $this->average; } - /** - * @param array $truePositive - * @param array $falsePositive - * @param array $falseNegative - */ private function computeMetrics(array $truePositive, array $falsePositive, array $falseNegative) { foreach ($truePositive as $label => $tp) { @@ -122,9 +98,6 @@ private function computeAverage() } /** - * @param int $truePositive - * @param int $falsePositive - * * @return float|string */ private function computePrecision(int $truePositive, int $falsePositive) @@ -137,9 +110,6 @@ private function computePrecision(int $truePositive, int $falsePositive) } /** - * @param int $truePositive - * @param int $falseNegative - * * @return float|string */ private function computeRecall(int $truePositive, int $falseNegative) @@ -151,13 +121,7 @@ private function computeRecall(int $truePositive, int $falseNegative) return $truePositive / $divider; } - /** - * @param float $precision - * @param float $recall - * - * @return float - */ - private function computeF1Score(float $precision, float $recall): float + private function computeF1Score(float $precision, float $recall) : float { if (0 == ($divider = $precision + $recall)) { return 0.0; @@ -172,7 +136,7 @@ private function computeF1Score(float $precision, float $recall): float * * @return array */ - private static function getLabelIndexedArray(array $actualLabels, array $predictedLabels): array + private static function getLabelIndexedArray(array $actualLabels, array $predictedLabels) : array { $labels = array_values(array_unique(array_merge($actualLabels, $predictedLabels))); sort($labels); diff --git a/src/Phpml/Metric/ConfusionMatrix.php b/src/Phpml/Metric/ConfusionMatrix.php index 664e355e..9dc2595f 100644 --- a/src/Phpml/Metric/ConfusionMatrix.php +++ b/src/Phpml/Metric/ConfusionMatrix.php @@ -13,7 +13,7 @@ class ConfusionMatrix * * @return array */ - public static function compute(array $actualLabels, array $predictedLabels, array $labels = null): array + public static function compute(array $actualLabels, array $predictedLabels, array $labels = null) : array { $labels = $labels ? array_flip($labels) : self::getUniqueLabels($actualLabels); $matrix = self::generateMatrixWithZeros($labels); @@ -43,7 +43,7 @@ public static function compute(array $actualLabels, array $predictedLabels, arra * * @return array */ - private static function generateMatrixWithZeros(array $labels): array + private static function generateMatrixWithZeros(array $labels) : array { $count = count($labels); $matrix = []; @@ -60,7 +60,7 @@ private static function generateMatrixWithZeros(array $labels): array * * @return array */ - private static function getUniqueLabels(array $labels): array + private static function getUniqueLabels(array $labels) : array { $labels = array_values(array_unique($labels)); sort($labels); diff --git a/src/Phpml/ModelManager.php b/src/Phpml/ModelManager.php index 08ab3e63..28594be7 100644 --- a/src/Phpml/ModelManager.php +++ b/src/Phpml/ModelManager.php @@ -4,18 +4,11 @@ namespace Phpml; -use Phpml\Exception\SerializeException; use Phpml\Exception\FileException; +use Phpml\Exception\SerializeException; class ModelManager { - /** - * @param Estimator $estimator - * @param string $filepath - * - * @throws FileException - * @throws SerializeException - */ public function saveToFile(Estimator $estimator, string $filepath) { if (!is_writable(dirname($filepath))) { @@ -33,14 +26,6 @@ public function saveToFile(Estimator $estimator, string $filepath) } } - /** - * @param string $filepath - * - * @return Estimator - * - * @throws FileException - * @throws SerializeException - */ public function restoreFromFile(string $filepath) : Estimator { if (!file_exists($filepath) || !is_readable($filepath)) { diff --git a/src/Phpml/NeuralNetwork/ActivationFunction.php b/src/Phpml/NeuralNetwork/ActivationFunction.php index f209f343..65ba7b44 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction.php @@ -8,8 +8,6 @@ interface ActivationFunction { /** * @param float|int $value - * - * @return float */ - public function compute($value): float; + public function compute($value) : float; } diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/BinaryStep.php b/src/Phpml/NeuralNetwork/ActivationFunction/BinaryStep.php index 350404b7..75b2ff1c 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/BinaryStep.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/BinaryStep.php @@ -10,10 +10,8 @@ class BinaryStep implements ActivationFunction { /** * @param float|int $value - * - * @return float */ - public function compute($value): float + public function compute($value) : float { return $value >= 0 ? 1.0 : 0.0; } diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php b/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php index 0e3e848d..081b8a56 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php @@ -10,10 +10,8 @@ class Gaussian implements ActivationFunction { /** * @param float|int $value - * - * @return float */ - public function compute($value): float + public function compute($value) : float { return exp(-pow($value, 2)); } diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/HyperbolicTangent.php b/src/Phpml/NeuralNetwork/ActivationFunction/HyperbolicTangent.php index 93b1001c..5c66fd9c 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/HyperbolicTangent.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/HyperbolicTangent.php @@ -13,20 +13,15 @@ class HyperbolicTangent implements ActivationFunction */ private $beta; - /** - * @param float $beta - */ - public function __construct($beta = 1.0) + public function __construct(float $beta = 1.0) { $this->beta = $beta; } /** * @param float|int $value - * - * @return float */ - public function compute($value): float + public function compute($value) : float { return tanh($this->beta * $value); } diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php b/src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php index 76674a08..60ade03c 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php @@ -13,20 +13,15 @@ class PReLU implements ActivationFunction */ private $beta; - /** - * @param float $beta - */ - public function __construct($beta = 0.01) + public function __construct(float $beta = 0.01) { $this->beta = $beta; } /** * @param float|int $value - * - * @return float */ - public function compute($value): float + public function compute($value) : float { return $value >= 0 ? $value : $this->beta * $value; } diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php b/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php index 23ac7ce1..dec45a29 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php @@ -13,20 +13,15 @@ class Sigmoid implements ActivationFunction */ private $beta; - /** - * @param float $beta - */ - public function __construct($beta = 1.0) + public function __construct(float $beta = 1.0) { $this->beta = $beta; } /** * @param float|int $value - * - * @return float */ - public function compute($value): float + public function compute($value) : float { return 1 / (1 + exp(-$this->beta * $value)); } diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php b/src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php index 0963e093..dbe8ee63 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php @@ -13,20 +13,15 @@ class ThresholdedReLU implements ActivationFunction */ private $theta; - /** - * @param float $theta - */ - public function __construct($theta = 1.0) + public function __construct(float $theta = 1.0) { $this->theta = $theta; } /** * @param float|int $value - * - * @return float */ - public function compute($value): float + public function compute($value) : float { return $value > $this->theta ? $value : 0.0; } diff --git a/src/Phpml/NeuralNetwork/Layer.php b/src/Phpml/NeuralNetwork/Layer.php index 632bc009..524bf35c 100644 --- a/src/Phpml/NeuralNetwork/Layer.php +++ b/src/Phpml/NeuralNetwork/Layer.php @@ -15,10 +15,6 @@ class Layer private $nodes = []; /** - * @param int $nodesNumber - * @param string $nodeClass - * @param ActivationFunction|null $activationFunction - * * @throws InvalidArgumentException */ public function __construct(int $nodesNumber = 0, string $nodeClass = Neuron::class, ActivationFunction $activationFunction = null) @@ -33,7 +29,6 @@ public function __construct(int $nodesNumber = 0, string $nodeClass = Neuron::cl } /** - * @param string $nodeClass * @param ActivationFunction|null $activationFunction * * @return Neuron @@ -47,9 +42,6 @@ private function createNode(string $nodeClass, ActivationFunction $activationFun return new $nodeClass(); } - /** - * @param Node $node - */ public function addNode(Node $node) { $this->nodes[] = $node; @@ -58,7 +50,7 @@ public function addNode(Node $node) /** * @return Node[] */ - public function getNodes() + public function getNodes() : array { return $this->nodes; } diff --git a/src/Phpml/NeuralNetwork/Network.php b/src/Phpml/NeuralNetwork/Network.php index c6c25af2..af04f4a6 100644 --- a/src/Phpml/NeuralNetwork/Network.php +++ b/src/Phpml/NeuralNetwork/Network.php @@ -16,15 +16,12 @@ public function setInput($input); /** * @return array */ - public function getOutput(): array; + public function getOutput() : array; - /** - * @param Layer $layer - */ public function addLayer(Layer $layer); /** * @return Layer[] */ - public function getLayers(): array; + public function getLayers() : array; } diff --git a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php index b20f6bbc..3f8bc041 100644 --- a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php +++ b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php @@ -16,9 +16,6 @@ abstract class LayeredNetwork implements Network */ protected $layers; - /** - * @param Layer $layer - */ public function addLayer(Layer $layer) { $this->layers[] = $layer; @@ -27,22 +24,16 @@ public function addLayer(Layer $layer) /** * @return Layer[] */ - public function getLayers(): array + public function getLayers() : array { return $this->layers; } - /** - * @return void - */ public function removeLayers() { unset($this->layers); } - /** - * @return Layer - */ public function getOutputLayer(): Layer { return $this->layers[count($this->layers) - 1]; @@ -51,7 +42,7 @@ public function getOutputLayer(): Layer /** * @return array */ - public function getOutput(): array + public function getOutput() : array { $result = []; foreach ($this->getOutputLayer()->getNodes() as $neuron) { diff --git a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php index 21510c4e..530fbef7 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -5,16 +5,16 @@ namespace Phpml\NeuralNetwork\Network; use Phpml\Estimator; -use Phpml\IncrementalEstimator; use Phpml\Exception\InvalidArgumentException; -use Phpml\NeuralNetwork\Training\Backpropagation; +use Phpml\Helper\Predictable; +use Phpml\IncrementalEstimator; use Phpml\NeuralNetwork\ActivationFunction; use Phpml\NeuralNetwork\Layer; use Phpml\NeuralNetwork\Node\Bias; use Phpml\NeuralNetwork\Node\Input; use Phpml\NeuralNetwork\Node\Neuron; use Phpml\NeuralNetwork\Node\Neuron\Synapse; -use Phpml\Helper\Predictable; +use Phpml\NeuralNetwork\Training\Backpropagation; abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, IncrementalEstimator { @@ -56,13 +56,6 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, protected $backpropagation = null; /** - * @param int $inputLayerFeatures - * @param array $hiddenLayers - * @param array $classes - * @param int $iterations - * @param ActivationFunction|null $activationFunction - * @param int $theta - * * @throws InvalidArgumentException */ public function __construct(int $inputLayerFeatures, array $hiddenLayers, array $classes, int $iterations = 10000, ActivationFunction $activationFunction = null, int $theta = 1) @@ -85,9 +78,6 @@ public function __construct(int $inputLayerFeatures, array $hiddenLayers, array $this->initNetwork(); } - /** - * @return void - */ private function initNetwork() { $this->addInputLayer($this->inputLayerFeatures); @@ -100,10 +90,6 @@ private function initNetwork() $this->backpropagation = new Backpropagation($this->theta); } - /** - * @param array $samples - * @param array $targets - */ public function train(array $samples, array $targets) { $this->reset(); @@ -112,10 +98,6 @@ public function train(array $samples, array $targets) } /** - * @param array $samples - * @param array $targets - * @param array $classes - * * @throws InvalidArgumentException */ public function partialTrain(array $samples, array $targets, array $classes = []) @@ -131,38 +113,25 @@ public function partialTrain(array $samples, array $targets, array $classes = [] } /** - * @param array $sample * @param mixed $target */ abstract protected function trainSample(array $sample, $target); /** - * @param array $sample - * * @return mixed */ abstract protected function predictSample(array $sample); - /** - * @return void - */ protected function reset() { $this->removeLayers(); } - /** - * @param int $nodes - */ private function addInputLayer(int $nodes) { $this->addLayer(new Layer($nodes, Input::class)); } - /** - * @param array $layers - * @param ActivationFunction|null $activationFunction - */ private function addNeuronLayers(array $layers, ActivationFunction $activationFunction = null) { foreach ($layers as $neurons) { @@ -188,10 +157,6 @@ private function addBiasNodes() } } - /** - * @param Layer $nextLayer - * @param Layer $currentLayer - */ private function generateLayerSynapses(Layer $nextLayer, Layer $currentLayer) { foreach ($nextLayer->getNodes() as $nextNeuron) { @@ -201,10 +166,6 @@ private function generateLayerSynapses(Layer $nextLayer, Layer $currentLayer) } } - /** - * @param Layer $currentLayer - * @param Neuron $nextNeuron - */ private function generateNeuronSynapses(Layer $currentLayer, Neuron $nextNeuron) { foreach ($currentLayer->getNodes() as $currentNeuron) { @@ -212,10 +173,6 @@ private function generateNeuronSynapses(Layer $currentLayer, Neuron $nextNeuron) } } - /** - * @param array $samples - * @param array $targets - */ private function trainSamples(array $samples, array $targets) { foreach ($targets as $key => $target) { diff --git a/src/Phpml/NeuralNetwork/Node.php b/src/Phpml/NeuralNetwork/Node.php index 65d5cdcd..6627c024 100644 --- a/src/Phpml/NeuralNetwork/Node.php +++ b/src/Phpml/NeuralNetwork/Node.php @@ -6,8 +6,5 @@ interface Node { - /** - * @return float - */ - public function getOutput(): float; + public function getOutput() : float; } diff --git a/src/Phpml/NeuralNetwork/Node/Bias.php b/src/Phpml/NeuralNetwork/Node/Bias.php index bf2e5ff0..4f328844 100644 --- a/src/Phpml/NeuralNetwork/Node/Bias.php +++ b/src/Phpml/NeuralNetwork/Node/Bias.php @@ -8,10 +8,7 @@ class Bias implements Node { - /** - * @return float - */ - public function getOutput(): float + public function getOutput() : float { return 1.0; } diff --git a/src/Phpml/NeuralNetwork/Node/Input.php b/src/Phpml/NeuralNetwork/Node/Input.php index 15318683..569b303d 100644 --- a/src/Phpml/NeuralNetwork/Node/Input.php +++ b/src/Phpml/NeuralNetwork/Node/Input.php @@ -13,25 +13,16 @@ class Input implements Node */ private $input; - /** - * @param float $input - */ public function __construct(float $input = 0.0) { $this->input = $input; } - /** - * @return float - */ - public function getOutput(): float + public function getOutput() : float { return $this->input; } - /** - * @param float $input - */ public function setInput(float $input) { $this->input = $input; diff --git a/src/Phpml/NeuralNetwork/Node/Neuron.php b/src/Phpml/NeuralNetwork/Node/Neuron.php index 7c246bed..520c3b26 100644 --- a/src/Phpml/NeuralNetwork/Node/Neuron.php +++ b/src/Phpml/NeuralNetwork/Node/Neuron.php @@ -5,8 +5,8 @@ namespace Phpml\NeuralNetwork\Node; use Phpml\NeuralNetwork\ActivationFunction; -use Phpml\NeuralNetwork\Node\Neuron\Synapse; use Phpml\NeuralNetwork\Node; +use Phpml\NeuralNetwork\Node\Neuron\Synapse; class Neuron implements Node { @@ -25,9 +25,6 @@ class Neuron implements Node */ protected $output; - /** - * @param ActivationFunction|null $activationFunction - */ public function __construct(ActivationFunction $activationFunction = null) { $this->activationFunction = $activationFunction ?: new ActivationFunction\Sigmoid(); @@ -35,9 +32,6 @@ public function __construct(ActivationFunction $activationFunction = null) $this->output = 0; } - /** - * @param Synapse $synapse - */ public function addSynapse(Synapse $synapse) { $this->synapses[] = $synapse; @@ -51,10 +45,7 @@ public function getSynapses() return $this->synapses; } - /** - * @return float - */ - public function getOutput(): float + public function getOutput() : float { if (0 === $this->output) { $sum = 0; diff --git a/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php b/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php index b9c036fb..35c8a0cb 100644 --- a/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php +++ b/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php @@ -28,42 +28,27 @@ public function __construct(Node $node, float $weight = null) $this->weight = $weight ?: $this->generateRandomWeight(); } - /** - * @return float - */ - protected function generateRandomWeight(): float + protected function generateRandomWeight() : float { return 1 / random_int(5, 25) * (random_int(0, 1) ? -1 : 1); } - /** - * @return float - */ - public function getOutput(): float + public function getOutput() : float { return $this->weight * $this->node->getOutput(); } - /** - * @param float $delta - */ - public function changeWeight($delta) + public function changeWeight(float $delta) { $this->weight += $delta; } - /** - * @return float - */ - public function getWeight() + public function getWeight() : float { return $this->weight; } - /** - * @return Node - */ - public function getNode() + public function getNode(): Node { return $this->node; } diff --git a/src/Phpml/NeuralNetwork/Training/Backpropagation.php b/src/Phpml/NeuralNetwork/Training/Backpropagation.php index ba90b45e..b113454c 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -24,16 +24,12 @@ class Backpropagation */ private $prevSigmas = null; - /** - * @param int $theta - */ public function __construct(int $theta) { $this->theta = $theta; } /** - * @param array $layers * @param mixed $targetClass */ public function backpropagate(array $layers, $targetClass) @@ -59,15 +55,7 @@ public function backpropagate(array $layers, $targetClass) $this->prevSigmas = null; } - /** - * @param Neuron $neuron - * @param int $targetClass - * @param int $key - * @param bool $lastLayer - * - * @return float - */ - private function getSigma(Neuron $neuron, int $targetClass, int $key, bool $lastLayer): float + private function getSigma(Neuron $neuron, int $targetClass, int $key, bool $lastLayer) : float { $neuronOutput = $neuron->getOutput(); $sigma = $neuronOutput * (1 - $neuronOutput); @@ -87,12 +75,7 @@ private function getSigma(Neuron $neuron, int $targetClass, int $key, bool $last return $sigma; } - /** - * @param Neuron $neuron - * - * @return float - */ - private function getPrevSigma(Neuron $neuron): float + private function getPrevSigma(Neuron $neuron) : float { $sigma = 0.0; diff --git a/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php b/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php index 62e2e7a5..23560fe4 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php @@ -18,38 +18,23 @@ class Sigma */ private $sigma; - /** - * @param Neuron $neuron - * @param float $sigma - */ - public function __construct(Neuron $neuron, $sigma) + public function __construct(Neuron $neuron, float $sigma) { $this->neuron = $neuron; $this->sigma = $sigma; } - /** - * @return Neuron - */ - public function getNeuron() + public function getNeuron(): Neuron { return $this->neuron; } - /** - * @return float - */ - public function getSigma() + public function getSigma() : float { return $this->sigma; } - /** - * @param Neuron $neuron - * - * @return float - */ - public function getSigmaForNeuron(Neuron $neuron): float + public function getSigmaForNeuron(Neuron $neuron) : float { $sigma = 0.0; diff --git a/src/Phpml/Pipeline.php b/src/Phpml/Pipeline.php index ca2914ad..5330daf6 100644 --- a/src/Phpml/Pipeline.php +++ b/src/Phpml/Pipeline.php @@ -29,17 +29,11 @@ public function __construct(array $transformers, Estimator $estimator) $this->estimator = $estimator; } - /** - * @param Transformer $transformer - */ public function addTransformer(Transformer $transformer) { $this->transformers[] = $transformer; } - /** - * @param Estimator $estimator - */ public function setEstimator(Estimator $estimator) { $this->estimator = $estimator; @@ -48,15 +42,12 @@ public function setEstimator(Estimator $estimator) /** * @return array|Transformer[] */ - public function getTransformers() + public function getTransformers() : array { return $this->transformers; } - /** - * @return Estimator - */ - public function getEstimator() + public function getEstimator(): Estimator { return $this->estimator; } diff --git a/src/Phpml/Preprocessing/Imputer.php b/src/Phpml/Preprocessing/Imputer.php index 805d3f62..ee9282b3 100644 --- a/src/Phpml/Preprocessing/Imputer.php +++ b/src/Phpml/Preprocessing/Imputer.php @@ -81,7 +81,7 @@ private function preprocessSample(array &$sample) * * @return array */ - private function getAxis(int $column, array $currentSample): array + private function getAxis(int $column, array $currentSample) : array { if (self::AXIS_ROW === $this->axis) { return array_diff($currentSample, [$this->missingValue]); diff --git a/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php b/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php index 88fbd3a8..4c57d8d3 100644 --- a/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php +++ b/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php @@ -4,17 +4,15 @@ namespace Phpml\Preprocessing\Imputer\Strategy; -use Phpml\Preprocessing\Imputer\Strategy; use Phpml\Math\Statistic\Mean; +use Phpml\Preprocessing\Imputer\Strategy; class MeanStrategy implements Strategy { /** * @param array $currentAxis - * - * @return float */ - public function replaceValue(array $currentAxis) + public function replaceValue(array $currentAxis) : float { return Mean::arithmetic($currentAxis); } diff --git a/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php b/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php index d4e19e0f..cf60f7ea 100644 --- a/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php +++ b/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php @@ -4,17 +4,15 @@ namespace Phpml\Preprocessing\Imputer\Strategy; -use Phpml\Preprocessing\Imputer\Strategy; use Phpml\Math\Statistic\Mean; +use Phpml\Preprocessing\Imputer\Strategy; class MedianStrategy implements Strategy { /** * @param array $currentAxis - * - * @return float */ - public function replaceValue(array $currentAxis) + public function replaceValue(array $currentAxis) : float { return Mean::median($currentAxis); } diff --git a/src/Phpml/Preprocessing/Imputer/Strategy/MostFrequentStrategy.php b/src/Phpml/Preprocessing/Imputer/Strategy/MostFrequentStrategy.php index 0bf8e8cf..9aea453f 100644 --- a/src/Phpml/Preprocessing/Imputer/Strategy/MostFrequentStrategy.php +++ b/src/Phpml/Preprocessing/Imputer/Strategy/MostFrequentStrategy.php @@ -4,8 +4,8 @@ namespace Phpml\Preprocessing\Imputer\Strategy; -use Phpml\Preprocessing\Imputer\Strategy; use Phpml\Math\Statistic\Mean; +use Phpml\Preprocessing\Imputer\Strategy; class MostFrequentStrategy implements Strategy { diff --git a/src/Phpml/Preprocessing/Normalizer.php b/src/Phpml/Preprocessing/Normalizer.php index fc00030b..412e06ec 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -5,8 +5,8 @@ namespace Phpml\Preprocessing; use Phpml\Exception\NormalizerException; -use Phpml\Math\Statistic\StandardDeviation; use Phpml\Math\Statistic\Mean; +use Phpml\Math\Statistic\StandardDeviation; class Normalizer implements Preprocessor { @@ -35,8 +35,6 @@ class Normalizer implements Preprocessor private $mean; /** - * @param int $norm - * * @throws NormalizerException */ public function __construct(int $norm = self::NORM_L2) diff --git a/src/Phpml/Regression/LeastSquares.php b/src/Phpml/Regression/LeastSquares.php index 1b664ed0..28602cd4 100644 --- a/src/Phpml/Regression/LeastSquares.php +++ b/src/Phpml/Regression/LeastSquares.php @@ -30,10 +30,6 @@ class LeastSquares implements Regression */ private $coefficients; - /** - * @param array $samples - * @param array $targets - */ public function train(array $samples, array $targets) { $this->samples = array_merge($this->samples, $samples); @@ -43,8 +39,6 @@ public function train(array $samples, array $targets) } /** - * @param array $sample - * * @return mixed */ public function predictSample(array $sample) @@ -57,18 +51,12 @@ public function predictSample(array $sample) return $result; } - /** - * @return array - */ - public function getCoefficients() + public function getCoefficients() : array { return $this->coefficients; } - /** - * @return float - */ - public function getIntercept() + public function getIntercept() : float { return $this->intercept; } @@ -90,10 +78,8 @@ private function computeCoefficients() /** * Add one dimension for intercept calculation. - * - * @return Matrix */ - private function getSamplesMatrix() + private function getSamplesMatrix() : Matrix { $samples = []; foreach ($this->samples as $sample) { @@ -104,10 +90,7 @@ private function getSamplesMatrix() return new Matrix($samples); } - /** - * @return Matrix - */ - private function getTargetsMatrix() + private function getTargetsMatrix() : Matrix { if (is_array($this->targets[0])) { return new Matrix($this->targets); diff --git a/src/Phpml/Regression/SVR.php b/src/Phpml/Regression/SVR.php index d4e5651f..54215e05 100644 --- a/src/Phpml/Regression/SVR.php +++ b/src/Phpml/Regression/SVR.php @@ -10,17 +10,6 @@ class SVR extends SupportVectorMachine implements Regression { - /** - * @param int $kernel - * @param int $degree - * @param float $epsilon - * @param float $cost - * @param float|null $gamma - * @param float $coef0 - * @param float $tolerance - * @param int $cacheSize - * @param bool $shrinking - */ public function __construct( int $kernel = Kernel::RBF, int $degree = 3, diff --git a/src/Phpml/SupportVectorMachine/DataTransformer.php b/src/Phpml/SupportVectorMachine/DataTransformer.php index ad5e180f..b057d01c 100644 --- a/src/Phpml/SupportVectorMachine/DataTransformer.php +++ b/src/Phpml/SupportVectorMachine/DataTransformer.php @@ -6,13 +6,6 @@ class DataTransformer { - /** - * @param array $samples - * @param array $labels - * @param bool $targets - * - * @return string - */ public static function trainingSet(array $samples, array $labels, bool $targets = false): string { $set = ''; @@ -27,11 +20,6 @@ public static function trainingSet(array $samples, array $labels, bool $targets return $set; } - /** - * @param array $samples - * - * @return string - */ public static function testSet(array $samples): string { if (!is_array($samples[0])) { @@ -46,13 +34,7 @@ public static function testSet(array $samples): string return $set; } - /** - * @param string $rawPredictions - * @param array $labels - * - * @return array - */ - public static function predictions(string $rawPredictions, array $labels): array + public static function predictions(string $rawPredictions, array $labels) : array { $numericLabels = self::numericLabels($labels); $results = []; @@ -65,12 +47,7 @@ public static function predictions(string $rawPredictions, array $labels): array return $results; } - /** - * @param array $labels - * - * @return array - */ - public static function numericLabels(array $labels): array + public static function numericLabels(array $labels) : array { $numericLabels = []; foreach ($labels as $label) { @@ -84,11 +61,6 @@ public static function numericLabels(array $labels): array return $numericLabels; } - /** - * @param array $sample - * - * @return string - */ private static function sampleRow(array $sample): string { $row = []; diff --git a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php index 9ee3c3b2..6b6c48d1 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -91,20 +91,6 @@ class SupportVectorMachine */ private $targets = []; - /** - * @param int $type - * @param int $kernel - * @param float $cost - * @param float $nu - * @param int $degree - * @param float|null $gamma - * @param float $coef0 - * @param float $epsilon - * @param float $tolerance - * @param int $cacheSize - * @param bool $shrinking - * @param bool $probabilityEstimates - */ public function __construct( int $type, int $kernel, @@ -138,11 +124,6 @@ public function __construct( $this->varPath = $rootPath.'var'.DIRECTORY_SEPARATOR; } - /** - * @param string $binPath - * - * @throws InvalidArgumentException - */ public function setBinPath(string $binPath) { $this->ensureDirectorySeparator($binPath); @@ -151,11 +132,6 @@ public function setBinPath(string $binPath) $this->binPath = $binPath; } - /** - * @param string $varPath - * - * @throws InvalidArgumentException - */ public function setVarPath(string $varPath) { if (!is_writable($varPath)) { @@ -166,10 +142,6 @@ public function setVarPath(string $varPath) $this->varPath = $varPath; } - /** - * @param array $samples - * @param array $targets - */ public function train(array $samples, array $targets) { $this->samples = array_merge($this->samples, $samples); @@ -189,17 +161,12 @@ public function train(array $samples, array $targets) unlink($modelFileName); } - /** - * @return string - */ - public function getModel() + public function getModel(): string { return $this->model; } /** - * @param array $samples - * * @return array */ public function predict(array $samples) @@ -232,10 +199,7 @@ public function predict(array $samples) return $predictions; } - /** - * @return string - */ - private function getOSExtension() + private function getOSExtension(): string { $os = strtoupper(substr(PHP_OS, 0, 3)); if ($os === 'WIN') { @@ -247,12 +211,6 @@ private function getOSExtension() return ''; } - /** - * @param string $trainingSetFileName - * @param string $modelFileName - * - * @return string - */ private function buildTrainCommand(string $trainingSetFileName, string $modelFileName): string { return sprintf( @@ -276,9 +234,6 @@ private function buildTrainCommand(string $trainingSetFileName, string $modelFil ); } - /** - * @param string $path - */ private function ensureDirectorySeparator(string &$path) { if (substr($path, -1) !== DIRECTORY_SEPARATOR) { @@ -286,11 +241,6 @@ private function ensureDirectorySeparator(string &$path) } } - /** - * @param string $path - * - * @throws InvalidArgumentException - */ private function verifyBinPath(string $path) { if (!is_dir($path)) { diff --git a/src/Phpml/Tokenization/Tokenizer.php b/src/Phpml/Tokenization/Tokenizer.php index 9a145c57..e1f0f353 100644 --- a/src/Phpml/Tokenization/Tokenizer.php +++ b/src/Phpml/Tokenization/Tokenizer.php @@ -6,10 +6,5 @@ interface Tokenizer { - /** - * @param string $text - * - * @return array - */ - public function tokenize(string $text): array; + public function tokenize(string $text) : array; } diff --git a/src/Phpml/Tokenization/WhitespaceTokenizer.php b/src/Phpml/Tokenization/WhitespaceTokenizer.php index ff918292..14e7d0a2 100644 --- a/src/Phpml/Tokenization/WhitespaceTokenizer.php +++ b/src/Phpml/Tokenization/WhitespaceTokenizer.php @@ -6,12 +6,7 @@ class WhitespaceTokenizer implements Tokenizer { - /** - * @param string $text - * - * @return array - */ - public function tokenize(string $text): array + public function tokenize(string $text) : array { return preg_split('/[\pZ\pC]+/u', $text, -1, PREG_SPLIT_NO_EMPTY); } diff --git a/src/Phpml/Tokenization/WordTokenizer.php b/src/Phpml/Tokenization/WordTokenizer.php index 431ae00e..03d134b3 100644 --- a/src/Phpml/Tokenization/WordTokenizer.php +++ b/src/Phpml/Tokenization/WordTokenizer.php @@ -6,12 +6,7 @@ class WordTokenizer implements Tokenizer { - /** - * @param string $text - * - * @return array - */ - public function tokenize(string $text): array + public function tokenize(string $text) : array { $tokens = []; preg_match_all('/\w\w+/u', $text, $tokens); diff --git a/tests/Phpml/Classification/Ensemble/BaggingTest.php b/tests/Phpml/Classification/Ensemble/BaggingTest.php index a158e3e3..afc358af 100644 --- a/tests/Phpml/Classification/Ensemble/BaggingTest.php +++ b/tests/Phpml/Classification/Ensemble/BaggingTest.php @@ -4,8 +4,8 @@ namespace tests\Phpml\Classification\Ensemble; -use Phpml\Classification\Ensemble\Bagging; use Phpml\Classification\DecisionTree; +use Phpml\Classification\Ensemble\Bagging; use Phpml\Classification\NaiveBayes; use Phpml\ModelManager; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/Classification/Ensemble/RandomForestTest.php b/tests/Phpml/Classification/Ensemble/RandomForestTest.php index cc1cd0fd..3ff51e07 100644 --- a/tests/Phpml/Classification/Ensemble/RandomForestTest.php +++ b/tests/Phpml/Classification/Ensemble/RandomForestTest.php @@ -4,8 +4,8 @@ namespace tests\Phpml\Classification\Ensemble; -use Phpml\Classification\Ensemble\RandomForest; use Phpml\Classification\DecisionTree; +use Phpml\Classification\Ensemble\RandomForest; use Phpml\Classification\NaiveBayes; class RandomForestTest extends BaggingTest diff --git a/tests/Phpml/Classification/MLPClassifierTest.php b/tests/Phpml/Classification/MLPClassifierTest.php index db30afd2..95438c61 100644 --- a/tests/Phpml/Classification/MLPClassifierTest.php +++ b/tests/Phpml/Classification/MLPClassifierTest.php @@ -5,8 +5,8 @@ namespace tests\Phpml\Classification; use Phpml\Classification\MLPClassifier; -use Phpml\NeuralNetwork\Node\Neuron; use Phpml\ModelManager; +use Phpml\NeuralNetwork\Node\Neuron; use PHPUnit\Framework\TestCase; class MLPClassifierTest extends TestCase @@ -194,7 +194,7 @@ public function testThrowExceptionOnInvalidClassesNumber() * * @return array */ - private function getSynapsesNodes(array $synapses): array + private function getSynapsesNodes(array $synapses) : array { $nodes = []; foreach ($synapses as $synapse) { diff --git a/tests/Phpml/Classification/SVCTest.php b/tests/Phpml/Classification/SVCTest.php index f143eef1..1b529d06 100644 --- a/tests/Phpml/Classification/SVCTest.php +++ b/tests/Phpml/Classification/SVCTest.php @@ -5,8 +5,8 @@ namespace tests\Phpml\Classification; use Phpml\Classification\SVC; -use Phpml\SupportVectorMachine\Kernel; use Phpml\ModelManager; +use Phpml\SupportVectorMachine\Kernel; use PHPUnit\Framework\TestCase; class SVCTest extends TestCase diff --git a/tests/Phpml/DimensionReduction/LDATest.php b/tests/Phpml/DimensionReduction/LDATest.php index 637fb1e3..4498ea5e 100644 --- a/tests/Phpml/DimensionReduction/LDATest.php +++ b/tests/Phpml/DimensionReduction/LDATest.php @@ -4,8 +4,8 @@ namespace tests\Phpml\DimensionReduction; -use Phpml\DimensionReduction\LDA; use Phpml\Dataset\Demo\IrisDataset; +use Phpml\DimensionReduction\LDA; use PHPUnit\Framework\TestCase; class LDATest extends TestCase diff --git a/tests/Phpml/NeuralNetwork/LayerTest.php b/tests/Phpml/NeuralNetwork/LayerTest.php index 118e9fee..4105b6f9 100644 --- a/tests/Phpml/NeuralNetwork/LayerTest.php +++ b/tests/Phpml/NeuralNetwork/LayerTest.php @@ -4,8 +4,8 @@ namespace tests\Phpml\NeuralNetwork; -use Phpml\NeuralNetwork\Node\Bias; use Phpml\NeuralNetwork\Layer; +use Phpml\NeuralNetwork\Node\Bias; use Phpml\NeuralNetwork\Node\Neuron; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php b/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php index 7f7f08ec..126d70db 100644 --- a/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php +++ b/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php @@ -4,8 +4,8 @@ namespace tests\Phpml\NeuralNetwork\Node\Neuron; -use Phpml\NeuralNetwork\Node\Neuron\Synapse; use Phpml\NeuralNetwork\Node\Neuron; +use Phpml\NeuralNetwork\Node\Neuron\Synapse; use PHPUnit\Framework\TestCase; class SynapseTest extends TestCase diff --git a/tests/Phpml/PipelineTest.php b/tests/Phpml/PipelineTest.php index 92a62238..7fc35451 100644 --- a/tests/Phpml/PipelineTest.php +++ b/tests/Phpml/PipelineTest.php @@ -9,8 +9,8 @@ use Phpml\FeatureExtraction\TokenCountVectorizer; use Phpml\Pipeline; use Phpml\Preprocessing\Imputer; -use Phpml\Preprocessing\Normalizer; use Phpml\Preprocessing\Imputer\Strategy\MostFrequentStrategy; +use Phpml\Preprocessing\Normalizer; use Phpml\Regression\SVR; use Phpml\Tokenization\WordTokenizer; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/Regression/LeastSquaresTest.php b/tests/Phpml/Regression/LeastSquaresTest.php index f4405f42..ac457a97 100644 --- a/tests/Phpml/Regression/LeastSquaresTest.php +++ b/tests/Phpml/Regression/LeastSquaresTest.php @@ -4,8 +4,8 @@ namespace tests\Phpml\Regression; -use Phpml\Regression\LeastSquares; use Phpml\ModelManager; +use Phpml\Regression\LeastSquares; use PHPUnit\Framework\TestCase; class LeastSquaresTest extends TestCase diff --git a/tests/Phpml/Regression/SVRTest.php b/tests/Phpml/Regression/SVRTest.php index e8f77def..4ea19a3f 100644 --- a/tests/Phpml/Regression/SVRTest.php +++ b/tests/Phpml/Regression/SVRTest.php @@ -4,9 +4,9 @@ namespace tests\Phpml\Regression; +use Phpml\ModelManager; use Phpml\Regression\SVR; use Phpml\SupportVectorMachine\Kernel; -use Phpml\ModelManager; use PHPUnit\Framework\TestCase; class SVRTest extends TestCase From d85bfed468bd05b3897afda3b55ef7a1d45d0c67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Votruba?= Date: Mon, 13 Nov 2017 11:42:40 +0100 Subject: [PATCH 202/328] [cs] remove more unused comments (#146) * [cs] remove more unused comments * [cs] remove unused array phpdocs * [cs] remove empty lines in docs * [cs] space-proof useless docs * [cs] remove empty @param lines * [cs] remove references arrays --- src/Phpml/Classification/DecisionTree.php | 23 ------------------- src/Phpml/Classification/Linear/Adaline.php | 1 - .../Linear/LogisticRegression.php | 3 --- .../Classification/WeightedClassifier.php | 2 -- src/Phpml/Dataset/ArrayDataset.php | 9 -------- .../EigenTransformerBase.php | 6 ----- .../FeatureExtraction/TfIdfTransformer.php | 12 ---------- .../Helper/Optimizer/ConjugateGradient.php | 6 ----- .../LinearAlgebra/EigenvalueDecomposition.php | 3 --- src/Phpml/Math/Product.php | 3 --- src/Phpml/Math/Statistic/Mean.php | 8 ------- src/Phpml/Metric/Accuracy.php | 4 ---- src/Phpml/Metric/ClassificationReport.php | 6 ----- src/Phpml/Metric/ConfusionMatrix.php | 17 -------------- .../NeuralNetwork/Network/LayeredNetwork.php | 3 --- .../NeuralNetwork/Node/Neuron/Synapse.php | 1 - src/Phpml/Pipeline.php | 10 -------- src/Phpml/Preprocessing/Imputer.php | 17 -------------- .../Imputer/Strategy/MeanStrategy.php | 3 --- .../Imputer/Strategy/MedianStrategy.php | 3 --- .../Imputer/Strategy/MostFrequentStrategy.php | 2 -- src/Phpml/Preprocessing/Normalizer.php | 15 ------------ .../Classification/MLPClassifierTest.php | 5 ---- .../StratifiedRandomSplitTest.php | 6 ----- tests/Phpml/Math/ComparisonTest.php | 2 -- tests/Phpml/Math/MatrixTest.php | 1 - .../ActivationFunction/BinaryStepTest.php | 3 --- .../ActivationFunction/GaussianTest.php | 3 --- .../HyperboliTangentTest.php | 4 ---- .../ActivationFunction/PReLUTest.php | 4 ---- .../ActivationFunction/SigmoidTest.php | 4 ---- .../ThresholdedReLUTest.php | 4 ---- 32 files changed, 193 deletions(-) diff --git a/src/Phpml/Classification/DecisionTree.php b/src/Phpml/Classification/DecisionTree.php index c8e8674c..f99b0329 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -72,10 +72,6 @@ public function __construct(int $maxDepth = 10) $this->maxDepth = $maxDepth; } - /** - * @param array $samples - * @param array $targets - */ public function train(array $samples, array $targets) { $this->samples = array_merge($this->samples, $samples); @@ -104,11 +100,6 @@ public function train(array $samples, array $targets) } } - /** - * @param array $samples - * - * @return array - */ public static function getColumnTypes(array $samples) : array { $types = []; @@ -122,10 +113,6 @@ public static function getColumnTypes(array $samples) : array return $types; } - /** - * @param array $records - * @param int $depth - */ protected function getSplitLeaf(array $records, int $depth = 0) : DecisionTreeLeaf { $split = $this->getBestSplit($records); @@ -239,8 +226,6 @@ protected function getBestSplit(array $records) : DecisionTreeLeaf * * If any of above methods were not called beforehand, then all features * are returned by default. - * - * @return array */ protected function getSelectedFeatures() : array { @@ -296,11 +281,6 @@ public function getGiniIndex($baseValue, array $colValues, array $targets) : flo return array_sum($giniParts) / count($colValues); } - /** - * @param array $samples - * - * @return array - */ protected function preprocess(array $samples) : array { // Detect and convert continuous data column values into @@ -325,9 +305,6 @@ protected function preprocess(array $samples) : array return array_map(null, ...$columns); } - /** - * @param array $columnValues - */ protected static function isCategoricalColumn(array $columnValues) : bool { $count = count($columnValues); diff --git a/src/Phpml/Classification/Linear/Adaline.php b/src/Phpml/Classification/Linear/Adaline.php index bcc014ef..2a649ad9 100644 --- a/src/Phpml/Classification/Linear/Adaline.php +++ b/src/Phpml/Classification/Linear/Adaline.php @@ -52,7 +52,6 @@ public function __construct( /** * Adapts the weights with respect to given samples and targets * by use of gradient descent learning rule - * @param array $targets */ protected function runTraining(array $samples, array $targets) { diff --git a/src/Phpml/Classification/Linear/LogisticRegression.php b/src/Phpml/Classification/Linear/LogisticRegression.php index bd100baa..bfbeb445 100644 --- a/src/Phpml/Classification/Linear/LogisticRegression.php +++ b/src/Phpml/Classification/Linear/LogisticRegression.php @@ -138,9 +138,6 @@ protected function runTraining(array $samples, array $targets) /** * Executes Conjugate Gradient method to optimize the weights of the LogReg model - * - * @param array $samples - * @param array $targets */ protected function runConjugateGradient(array $samples, array $targets, \Closure $gradientFunc) { diff --git a/src/Phpml/Classification/WeightedClassifier.php b/src/Phpml/Classification/WeightedClassifier.php index 4af3de49..0a5f1926 100644 --- a/src/Phpml/Classification/WeightedClassifier.php +++ b/src/Phpml/Classification/WeightedClassifier.php @@ -13,8 +13,6 @@ abstract class WeightedClassifier implements Classifier /** * Sets the array including a weight for each sample - * - * @param array $weights */ public function setSampleWeights(array $weights) { diff --git a/src/Phpml/Dataset/ArrayDataset.php b/src/Phpml/Dataset/ArrayDataset.php index 96feec77..e27b2e39 100644 --- a/src/Phpml/Dataset/ArrayDataset.php +++ b/src/Phpml/Dataset/ArrayDataset.php @@ -19,9 +19,6 @@ class ArrayDataset implements Dataset protected $targets = []; /** - * @param array $samples - * @param array $targets - * * @throws InvalidArgumentException */ public function __construct(array $samples, array $targets) @@ -34,17 +31,11 @@ public function __construct(array $samples, array $targets) $this->targets = $targets; } - /** - * @return array - */ public function getSamples() : array { return $this->samples; } - /** - * @return array - */ public function getTargets() : array { return $this->targets; diff --git a/src/Phpml/DimensionReduction/EigenTransformerBase.php b/src/Phpml/DimensionReduction/EigenTransformerBase.php index 913148b9..df18d116 100644 --- a/src/Phpml/DimensionReduction/EigenTransformerBase.php +++ b/src/Phpml/DimensionReduction/EigenTransformerBase.php @@ -47,8 +47,6 @@ abstract class EigenTransformerBase * Calculates eigenValues and eigenVectors of the given matrix. Returns * top eigenVectors along with the largest eigenValues. The total explained variance * of these eigenVectors will be no less than desired $totalVariance value - * - * @param array $matrix */ protected function eigenDecomposition(array $matrix) { @@ -85,10 +83,6 @@ protected function eigenDecomposition(array $matrix) /** * Returns the reduced data - * - * @param array $data - * - * @return array */ protected function reduce(array $data) : array { diff --git a/src/Phpml/FeatureExtraction/TfIdfTransformer.php b/src/Phpml/FeatureExtraction/TfIdfTransformer.php index 61f7e65b..c5e1e591 100644 --- a/src/Phpml/FeatureExtraction/TfIdfTransformer.php +++ b/src/Phpml/FeatureExtraction/TfIdfTransformer.php @@ -13,9 +13,6 @@ class TfIdfTransformer implements Transformer */ private $idf; - /** - * @param array $samples - */ public function __construct(array $samples = null) { if ($samples) { @@ -23,9 +20,6 @@ public function __construct(array $samples = null) } } - /** - * @param array $samples - */ public function fit(array $samples) { $this->countTokensFrequency($samples); @@ -36,9 +30,6 @@ public function fit(array $samples) } } - /** - * @param array $samples - */ public function transform(array &$samples) { foreach ($samples as &$sample) { @@ -48,9 +39,6 @@ public function transform(array &$samples) } } - /** - * @param array $samples - */ private function countTokensFrequency(array $samples) { $this->idf = array_fill_keys(array_keys($samples[0]), 0); diff --git a/src/Phpml/Helper/Optimizer/ConjugateGradient.php b/src/Phpml/Helper/Optimizer/ConjugateGradient.php index 994971d4..7333b9a4 100644 --- a/src/Phpml/Helper/Optimizer/ConjugateGradient.php +++ b/src/Phpml/Helper/Optimizer/ConjugateGradient.php @@ -17,12 +17,6 @@ */ class ConjugateGradient extends GD { - /** - * @param array $samples - * @param array $targets - * - * @return array - */ public function runOptimization(array $samples, array $targets, \Closure $gradientCb) : array { $this->samples = $samples; diff --git a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php index d24b1a9e..b76ddb9f 100644 --- a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -2,7 +2,6 @@ declare(strict_types=1); /** - * * Class to obtain eigenvalues and eigenvectors of a real matrix. * * If A is symmetric, then A = V*D*V' where the eigenvalue matrix D @@ -88,8 +87,6 @@ class EigenvalueDecomposition /** * Constructor: Check for symmetry, then construct the eigenvalue decomposition - * - * @param array $Arg */ public function __construct(array $Arg) { diff --git a/src/Phpml/Math/Product.php b/src/Phpml/Math/Product.php index 35ef79cb..ab1e75a9 100644 --- a/src/Phpml/Math/Product.php +++ b/src/Phpml/Math/Product.php @@ -7,9 +7,6 @@ class Product { /** - * @param array $a - * @param array $b - * * @return mixed */ public static function scalar(array $a, array $b) diff --git a/src/Phpml/Math/Statistic/Mean.php b/src/Phpml/Math/Statistic/Mean.php index 22cd4bbe..50b37884 100644 --- a/src/Phpml/Math/Statistic/Mean.php +++ b/src/Phpml/Math/Statistic/Mean.php @@ -9,8 +9,6 @@ class Mean { /** - * @param array $numbers - * * @throws InvalidArgumentException */ public static function arithmetic(array $numbers) : float @@ -21,8 +19,6 @@ public static function arithmetic(array $numbers) : float } /** - * @param array $numbers - * * @return float|mixed * * @throws InvalidArgumentException @@ -44,8 +40,6 @@ public static function median(array $numbers) } /** - * @param array $numbers - * * @return mixed * * @throws InvalidArgumentException @@ -60,8 +54,6 @@ public static function mode(array $numbers) } /** - * @param array $array - * * @throws InvalidArgumentException */ private static function checkArrayLength(array $array) diff --git a/src/Phpml/Metric/Accuracy.php b/src/Phpml/Metric/Accuracy.php index 3dfcb34c..3fd545cd 100644 --- a/src/Phpml/Metric/Accuracy.php +++ b/src/Phpml/Metric/Accuracy.php @@ -9,10 +9,6 @@ class Accuracy { /** - * @param array $actualLabels - * @param array $predictedLabels - * @param bool $normalize - * * @return float|int * * @throws InvalidArgumentException diff --git a/src/Phpml/Metric/ClassificationReport.php b/src/Phpml/Metric/ClassificationReport.php index dac82a07..f4fdaee7 100644 --- a/src/Phpml/Metric/ClassificationReport.php +++ b/src/Phpml/Metric/ClassificationReport.php @@ -130,12 +130,6 @@ private function computeF1Score(float $precision, float $recall) : float return 2.0 * (($precision * $recall) / $divider); } - /** - * @param array $actualLabels - * @param array $predictedLabels - * - * @return array - */ private static function getLabelIndexedArray(array $actualLabels, array $predictedLabels) : array { $labels = array_values(array_unique(array_merge($actualLabels, $predictedLabels))); diff --git a/src/Phpml/Metric/ConfusionMatrix.php b/src/Phpml/Metric/ConfusionMatrix.php index 9dc2595f..5c7bc0b7 100644 --- a/src/Phpml/Metric/ConfusionMatrix.php +++ b/src/Phpml/Metric/ConfusionMatrix.php @@ -6,13 +6,6 @@ class ConfusionMatrix { - /** - * @param array $actualLabels - * @param array $predictedLabels - * @param array $labels - * - * @return array - */ public static function compute(array $actualLabels, array $predictedLabels, array $labels = null) : array { $labels = $labels ? array_flip($labels) : self::getUniqueLabels($actualLabels); @@ -38,11 +31,6 @@ public static function compute(array $actualLabels, array $predictedLabels, arra return $matrix; } - /** - * @param array $labels - * - * @return array - */ private static function generateMatrixWithZeros(array $labels) : array { $count = count($labels); @@ -55,11 +43,6 @@ private static function generateMatrixWithZeros(array $labels) : array return $matrix; } - /** - * @param array $labels - * - * @return array - */ private static function getUniqueLabels(array $labels) : array { $labels = array_values(array_unique($labels)); diff --git a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php index 3f8bc041..1234c41d 100644 --- a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php +++ b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php @@ -39,9 +39,6 @@ public function getOutputLayer(): Layer return $this->layers[count($this->layers) - 1]; } - /** - * @return array - */ public function getOutput() : array { $result = []; diff --git a/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php b/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php index 35c8a0cb..adb4b023 100644 --- a/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php +++ b/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php @@ -19,7 +19,6 @@ class Synapse protected $node; /** - * @param Node $node * @param float|null $weight */ public function __construct(Node $node, float $weight = null) diff --git a/src/Phpml/Pipeline.php b/src/Phpml/Pipeline.php index 5330daf6..a49fb27d 100644 --- a/src/Phpml/Pipeline.php +++ b/src/Phpml/Pipeline.php @@ -18,7 +18,6 @@ class Pipeline implements Estimator /** * @param array|Transformer[] $transformers - * @param Estimator $estimator */ public function __construct(array $transformers, Estimator $estimator) { @@ -52,10 +51,6 @@ public function getEstimator(): Estimator return $this->estimator; } - /** - * @param array $samples - * @param array $targets - */ public function train(array $samples, array $targets) { foreach ($this->transformers as $transformer) { @@ -67,8 +62,6 @@ public function train(array $samples, array $targets) } /** - * @param array $samples - * * @return mixed */ public function predict(array $samples) @@ -78,9 +71,6 @@ public function predict(array $samples) return $this->estimator->predict($samples); } - /** - * @param array $samples - */ private function transformSamples(array &$samples) { foreach ($this->transformers as $transformer) { diff --git a/src/Phpml/Preprocessing/Imputer.php b/src/Phpml/Preprocessing/Imputer.php index ee9282b3..cc90c46f 100644 --- a/src/Phpml/Preprocessing/Imputer.php +++ b/src/Phpml/Preprocessing/Imputer.php @@ -33,8 +33,6 @@ class Imputer implements Preprocessor /** * @param mixed $missingValue - * @param Strategy $strategy - * @param int $axis * @param array|null $samples */ public function __construct($missingValue, Strategy $strategy, int $axis = self::AXIS_COLUMN, array $samples = []) @@ -45,17 +43,11 @@ public function __construct($missingValue, Strategy $strategy, int $axis = self: $this->samples = $samples; } - /** - * @param array $samples - */ public function fit(array $samples) { $this->samples = $samples; } - /** - * @param array $samples - */ public function transform(array &$samples) { foreach ($samples as &$sample) { @@ -63,9 +55,6 @@ public function transform(array &$samples) } } - /** - * @param array $sample - */ private function preprocessSample(array &$sample) { foreach ($sample as $column => &$value) { @@ -75,12 +64,6 @@ private function preprocessSample(array &$sample) } } - /** - * @param int $column - * @param array $currentSample - * - * @return array - */ private function getAxis(int $column, array $currentSample) : array { if (self::AXIS_ROW === $this->axis) { diff --git a/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php b/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php index 4c57d8d3..91badfbc 100644 --- a/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php +++ b/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php @@ -9,9 +9,6 @@ class MeanStrategy implements Strategy { - /** - * @param array $currentAxis - */ public function replaceValue(array $currentAxis) : float { return Mean::arithmetic($currentAxis); diff --git a/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php b/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php index cf60f7ea..f010bea7 100644 --- a/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php +++ b/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php @@ -9,9 +9,6 @@ class MedianStrategy implements Strategy { - /** - * @param array $currentAxis - */ public function replaceValue(array $currentAxis) : float { return Mean::median($currentAxis); diff --git a/src/Phpml/Preprocessing/Imputer/Strategy/MostFrequentStrategy.php b/src/Phpml/Preprocessing/Imputer/Strategy/MostFrequentStrategy.php index 9aea453f..9a8fd63e 100644 --- a/src/Phpml/Preprocessing/Imputer/Strategy/MostFrequentStrategy.php +++ b/src/Phpml/Preprocessing/Imputer/Strategy/MostFrequentStrategy.php @@ -10,8 +10,6 @@ class MostFrequentStrategy implements Strategy { /** - * @param array $currentAxis - * * @return float|mixed */ public function replaceValue(array $currentAxis) diff --git a/src/Phpml/Preprocessing/Normalizer.php b/src/Phpml/Preprocessing/Normalizer.php index 412e06ec..07777f93 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -46,9 +46,6 @@ public function __construct(int $norm = self::NORM_L2) $this->norm = $norm; } - /** - * @param array $samples - */ public function fit(array $samples) { if ($this->fitted) { @@ -67,9 +64,6 @@ public function fit(array $samples) $this->fitted = true; } - /** - * @param array $samples - */ public function transform(array &$samples) { $methods = [ @@ -86,9 +80,6 @@ public function transform(array &$samples) } } - /** - * @param array $sample - */ private function normalizeL1(array &$sample) { $norm1 = 0; @@ -106,9 +97,6 @@ private function normalizeL1(array &$sample) } } - /** - * @param array $sample - */ private function normalizeL2(array &$sample) { $norm2 = 0; @@ -126,9 +114,6 @@ private function normalizeL2(array &$sample) } } - /** - * @param array $sample - */ private function normalizeSTD(array &$sample) { foreach ($sample as $i => $val) { diff --git a/tests/Phpml/Classification/MLPClassifierTest.php b/tests/Phpml/Classification/MLPClassifierTest.php index 95438c61..1745bc62 100644 --- a/tests/Phpml/Classification/MLPClassifierTest.php +++ b/tests/Phpml/Classification/MLPClassifierTest.php @@ -189,11 +189,6 @@ public function testThrowExceptionOnInvalidClassesNumber() new MLPClassifier(2, [2], [0]); } - /** - * @param array $synapses - * - * @return array - */ private function getSynapsesNodes(array $synapses) : array { $nodes = []; diff --git a/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php b/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php index ef07398f..57023d38 100644 --- a/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php +++ b/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php @@ -46,12 +46,6 @@ public function testDatasetStratifiedRandomSplitWithEvenDistributionAndNumericTa $this->assertEquals(1, $this->countSamplesByTarget($split->getTestLabels(), 2)); } - /** - * @param $splitTargets - * @param $countTarget - * - * @return int - */ private function countSamplesByTarget($splitTargets, $countTarget): int { $count = 0; diff --git a/tests/Phpml/Math/ComparisonTest.php b/tests/Phpml/Math/ComparisonTest.php index 2d41273e..8fff3cb2 100644 --- a/tests/Phpml/Math/ComparisonTest.php +++ b/tests/Phpml/Math/ComparisonTest.php @@ -12,8 +12,6 @@ class ComparisonTest extends TestCase /** * @param mixed $a * @param mixed $b - * @param string $operator - * @param bool $expected * * @dataProvider provideData */ diff --git a/tests/Phpml/Math/MatrixTest.php b/tests/Phpml/Math/MatrixTest.php index 257fd72d..0285b618 100644 --- a/tests/Phpml/Math/MatrixTest.php +++ b/tests/Phpml/Math/MatrixTest.php @@ -261,7 +261,6 @@ public function testDot() $matrix1 = [[1, 1], [2, 2]]; $matrix2 = [[3, 3], [3, 3], [3, 3]]; $dot = [6, 12]; - $this->assertEquals($dot, Matrix::dot($matrix2, $matrix1)); } } diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php index 85cf9f87..a62b1c97 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php @@ -10,9 +10,6 @@ class BinaryStepTest extends TestCase { /** - * @param $expected - * @param $value - * * @dataProvider binaryStepProvider */ public function testBinaryStepActivationFunction($expected, $value) diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php index 78355739..19b6cca3 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php @@ -10,9 +10,6 @@ class GaussianTest extends TestCase { /** - * @param $expected - * @param $value - * * @dataProvider gaussianProvider */ public function testGaussianActivationFunction($expected, $value) diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php index b4908325..b27dfa0a 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php @@ -10,10 +10,6 @@ class HyperboliTangentTest extends TestCase { /** - * @param $beta - * @param $expected - * @param $value - * * @dataProvider tanhProvider */ public function testHyperbolicTangentActivationFunction($beta, $expected, $value) diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php index a390bf07..5e14ce88 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php @@ -10,10 +10,6 @@ class PReLUTest extends TestCase { /** - * @param $beta - * @param $expected - * @param $value - * * @dataProvider preluProvider */ public function testPReLUActivationFunction($beta, $expected, $value) diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php index add1b34b..68940060 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php @@ -10,10 +10,6 @@ class SigmoidTest extends TestCase { /** - * @param $beta - * @param $expected - * @param $value - * * @dataProvider sigmoidProvider */ public function testSigmoidActivationFunction($beta, $expected, $value) diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php index b0c6ecf8..571a1971 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php @@ -10,10 +10,6 @@ class ThresholdedReLUTest extends TestCase { /** - * @param $theta - * @param $expected - * @param $value - * * @dataProvider thresholdProvider */ public function testThresholdedReLUActivationFunction($theta, $expected, $value) From 331d4b133ea8ae77c4078cc31bd444be4bfae01f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Votruba?= Date: Mon, 13 Nov 2017 16:54:04 +0100 Subject: [PATCH 203/328] travis: add PHP 7.2 (#147) --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 94f56edf..7c5b9bb7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,9 @@ matrix: - os: linux php: '7.1' + - os: linux + php: '7.2' + - os: osx osx_image: xcode7.3 language: generic From 653c7c772dc3ee85848f01204f6315efc9af139a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Votruba?= Date: Tue, 14 Nov 2017 21:21:23 +0100 Subject: [PATCH 204/328] Upgrade to PHP 7.1 (#150) * upgrade to PHP 7.1 * bump travis and composer to PHP 7.1 * fix tests --- .travis.yml | 5 +-- composer.json | 2 +- src/Phpml/Association/Apriori.php | 12 +++--- src/Phpml/Classification/DecisionTree.php | 8 ++-- .../Classification/Ensemble/AdaBoost.php | 8 ++-- src/Phpml/Classification/Ensemble/Bagging.php | 4 +- .../Classification/KNearestNeighbors.php | 2 +- src/Phpml/Classification/Linear/Adaline.php | 4 +- .../Classification/Linear/DecisionStump.php | 14 +++---- .../Linear/LogisticRegression.php | 12 +++--- .../Classification/Linear/Perceptron.php | 10 +++-- src/Phpml/Classification/MLPClassifier.php | 2 +- src/Phpml/Classification/NaiveBayes.php | 10 ++--- src/Phpml/Classification/SVC.php | 2 +- .../Classification/WeightedClassifier.php | 2 +- src/Phpml/Clustering/DBSCAN.php | 2 +- src/Phpml/Clustering/FuzzyCMeans.php | 8 ++-- src/Phpml/Clustering/KMeans.php | 4 +- src/Phpml/Clustering/KMeans/Cluster.php | 8 ++-- src/Phpml/Clustering/KMeans/Point.php | 4 +- src/Phpml/Clustering/KMeans/Space.php | 6 +-- src/Phpml/CrossValidation/RandomSplit.php | 2 +- src/Phpml/CrossValidation/Split.php | 4 +- .../CrossValidation/StratifiedRandomSplit.php | 2 +- src/Phpml/Dataset/FilesDataset.php | 4 +- .../EigenTransformerBase.php | 2 +- src/Phpml/DimensionReduction/KernelPCA.php | 10 ++--- src/Phpml/DimensionReduction/LDA.php | 2 +- src/Phpml/DimensionReduction/PCA.php | 4 +- .../FeatureExtraction/TfIdfTransformer.php | 8 ++-- .../TokenCountVectorizer.php | 18 ++++----- src/Phpml/Helper/OneVsRest.php | 10 ++--- .../Helper/Optimizer/ConjugateGradient.php | 4 +- src/Phpml/Helper/Optimizer/GD.php | 8 ++-- src/Phpml/Helper/Optimizer/StochasticGD.php | 4 +- src/Phpml/Helper/Trainable.php | 2 +- .../LinearAlgebra/EigenvalueDecomposition.php | 10 ++--- src/Phpml/Math/Statistic/Covariance.php | 6 +-- src/Phpml/Math/Statistic/Mean.php | 2 +- src/Phpml/Metric/ClassificationReport.php | 4 +- src/Phpml/Metric/ConfusionMatrix.php | 2 +- src/Phpml/ModelManager.php | 2 +- src/Phpml/NeuralNetwork/Layer.php | 6 +-- .../NeuralNetwork/Network/LayeredNetwork.php | 4 +- .../Network/MultilayerPerceptron.php | 24 ++++++------ src/Phpml/NeuralNetwork/Node/Input.php | 2 +- src/Phpml/NeuralNetwork/Node/Neuron.php | 6 +-- .../NeuralNetwork/Node/Neuron/Synapse.php | 4 +- .../Training/Backpropagation.php | 2 +- src/Phpml/Pipeline.php | 8 ++-- src/Phpml/Preprocessing/Imputer.php | 10 ++--- src/Phpml/Preprocessing/Normalizer.php | 16 ++++---- src/Phpml/Regression/LeastSquares.php | 4 +- src/Phpml/Regression/SVR.php | 2 +- src/Phpml/SupportVectorMachine/Kernel.php | 8 ++-- .../SupportVectorMachine.php | 12 +++--- src/Phpml/SupportVectorMachine/Type.php | 10 ++--- tests/Phpml/Association/AprioriTest.php | 30 +++++++-------- .../DecisionTree/DecisionTreeLeafTest.php | 2 +- .../Phpml/Classification/DecisionTreeTest.php | 14 +++---- .../Classification/Ensemble/AdaBoostTest.php | 2 +- .../Classification/Ensemble/BaggingTest.php | 14 +++---- .../Ensemble/RandomForestTest.php | 2 +- .../Classification/KNearestNeighborsTest.php | 8 ++-- .../Classification/Linear/AdalineTest.php | 4 +- .../Linear/DecisionStumpTest.php | 2 +- .../Classification/Linear/PerceptronTest.php | 4 +- .../Classification/MLPClassifierTest.php | 22 +++++------ tests/Phpml/Classification/NaiveBayesTest.php | 6 +-- tests/Phpml/Classification/SVCTest.php | 6 +-- tests/Phpml/Clustering/DBSCANTest.php | 4 +- tests/Phpml/Clustering/FuzzyCMeansTest.php | 2 +- tests/Phpml/Clustering/KMeansTest.php | 6 +-- .../Phpml/CrossValidation/RandomSplitTest.php | 12 +++--- .../StratifiedRandomSplitTest.php | 4 +- tests/Phpml/Dataset/ArrayDatasetTest.php | 4 +- tests/Phpml/Dataset/CsvDatasetTest.php | 8 ++-- tests/Phpml/Dataset/Demo/GlassDatasetTest.php | 2 +- tests/Phpml/Dataset/Demo/IrisDatasetTest.php | 2 +- tests/Phpml/Dataset/Demo/WineDatasetTest.php | 2 +- tests/Phpml/Dataset/FilesDatasetTest.php | 4 +- .../DimensionReduction/KernelPCATest.php | 4 +- tests/Phpml/DimensionReduction/LDATest.php | 4 +- tests/Phpml/DimensionReduction/PCATest.php | 6 +-- .../Phpml/FeatureExtraction/StopWordsTest.php | 10 ++--- .../TfIdfTransformerTest.php | 2 +- .../TokenCountVectorizerTest.php | 6 +-- tests/Phpml/Math/ComparisonTest.php | 4 +- tests/Phpml/Math/Distance/ChebyshevTest.php | 10 ++--- tests/Phpml/Math/Distance/EuclideanTest.php | 10 ++--- tests/Phpml/Math/Distance/ManhattanTest.php | 10 ++--- tests/Phpml/Math/Distance/MinkowskiTest.php | 12 +++--- tests/Phpml/Math/Kernel/RBFTest.php | 2 +- .../LinearAlgebra/EigenDecompositionTest.php | 2 +- tests/Phpml/Math/MatrixTest.php | 38 +++++++++---------- tests/Phpml/Math/ProductTest.php | 2 +- tests/Phpml/Math/SetTest.php | 20 +++++----- .../Phpml/Math/Statistic/CorrelationTest.php | 4 +- tests/Phpml/Math/Statistic/CovarianceTest.php | 2 +- tests/Phpml/Math/Statistic/GaussianTest.php | 2 +- tests/Phpml/Math/Statistic/MeanTest.php | 14 +++---- .../Math/Statistic/StandardDeviationTest.php | 6 +-- tests/Phpml/Metric/AccuracyTest.php | 8 ++-- .../Phpml/Metric/ClassificationReportTest.php | 10 ++--- tests/Phpml/Metric/ConfusionMatrixTest.php | 6 +-- tests/Phpml/ModelManagerTest.php | 4 +- .../ActivationFunction/BinaryStepTest.php | 2 +- .../ActivationFunction/GaussianTest.php | 2 +- .../HyperboliTangentTest.php | 2 +- .../ActivationFunction/PReLUTest.php | 2 +- .../ActivationFunction/SigmoidTest.php | 2 +- .../ThresholdedReLUTest.php | 2 +- tests/Phpml/NeuralNetwork/LayerTest.php | 10 ++--- .../Network/LayeredNetworkTest.php | 6 +-- tests/Phpml/NeuralNetwork/Node/BiasTest.php | 2 +- tests/Phpml/NeuralNetwork/Node/InputTest.php | 4 +- .../NeuralNetwork/Node/Neuron/SynapseTest.php | 4 +- tests/Phpml/NeuralNetwork/Node/NeuronTest.php | 8 ++-- tests/Phpml/PipelineTest.php | 8 ++-- tests/Phpml/Preprocessing/ImputerTest.php | 14 +++---- tests/Phpml/Preprocessing/NormalizerTest.php | 12 +++--- tests/Phpml/Regression/LeastSquaresTest.php | 8 ++-- tests/Phpml/Regression/SVRTest.php | 6 +-- .../DataTransformerTest.php | 4 +- .../SupportVectorMachineTest.php | 12 +++--- .../Tokenization/WhitespaceTokenizerTest.php | 4 +- .../Phpml/Tokenization/WordTokenizerTest.php | 4 +- 127 files changed, 419 insertions(+), 420 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7c5b9bb7..48162fda 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,6 @@ matrix: fast_finish: true include: - - os: linux - php: '7.0' - - os: linux php: '7.1' @@ -18,7 +15,7 @@ matrix: language: generic env: - _OSX=10.11 - - _PHP: php70 + - _PHP: php71 before_install: - if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then /usr/bin/env bash tools/prepare_osx_env.sh ; fi diff --git a/composer.json b/composer.json index 18ae9943..8db49347 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ } }, "require": { - "php": ">=7.0.0" + "php": "^7.1" }, "require-dev": { "phpunit/phpunit": "^6.0", diff --git a/src/Phpml/Association/Apriori.php b/src/Phpml/Association/Apriori.php index ee9c3833..f1398d2e 100644 --- a/src/Phpml/Association/Apriori.php +++ b/src/Phpml/Association/Apriori.php @@ -11,13 +11,13 @@ class Apriori implements Associator { use Trainable, Predictable; - const ARRAY_KEY_ANTECEDENT = 'antecedent'; + public const ARRAY_KEY_ANTECEDENT = 'antecedent'; - const ARRAY_KEY_CONFIDENCE = 'confidence'; + public const ARRAY_KEY_CONFIDENCE = 'confidence'; - const ARRAY_KEY_CONSEQUENT = 'consequent'; + public const ARRAY_KEY_CONSEQUENT = 'consequent'; - const ARRAY_KEY_SUPPORT = 'support'; + public const ARRAY_KEY_SUPPORT = 'support'; /** * Minimum relative probability of frequent transactions. @@ -116,7 +116,7 @@ protected function predictSample(array $sample) : array /** * Generate rules for each k-length frequent item set. */ - private function generateAllRules() + private function generateAllRules(): void { for ($k = 2; !empty($this->large[$k]); ++$k) { foreach ($this->large[$k] as $frequent) { @@ -130,7 +130,7 @@ private function generateAllRules() * * @param mixed[] $frequent */ - private function generateRules(array $frequent) + private function generateRules(array $frequent): void { foreach ($this->antecedents($frequent) as $antecedent) { if ($this->confidence <= ($confidence = $this->confidence($frequent, $antecedent))) { diff --git a/src/Phpml/Classification/DecisionTree.php b/src/Phpml/Classification/DecisionTree.php index f99b0329..653b1bf5 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -14,8 +14,8 @@ class DecisionTree implements Classifier { use Trainable, Predictable; - const CONTINUOUS = 1; - const NOMINAL = 2; + public const CONTINUOUS = 1; + public const NOMINAL = 2; /** * @var array @@ -72,7 +72,7 @@ public function __construct(int $maxDepth = 10) $this->maxDepth = $maxDepth; } - public function train(array $samples, array $targets) + public function train(array $samples, array $targets): void { $this->samples = array_merge($this->samples, $samples); $this->targets = array_merge($this->targets, $targets); @@ -354,7 +354,7 @@ public function setNumFeatures(int $numFeatures) /** * Used to set predefined features to consider while deciding which column to use for a split */ - protected function setSelectedFeatures(array $selectedFeatures) + protected function setSelectedFeatures(array $selectedFeatures): void { $this->selectedFeatures = $selectedFeatures; } diff --git a/src/Phpml/Classification/Ensemble/AdaBoost.php b/src/Phpml/Classification/Ensemble/AdaBoost.php index 9fdd65b3..5bdca1b1 100644 --- a/src/Phpml/Classification/Ensemble/AdaBoost.php +++ b/src/Phpml/Classification/Ensemble/AdaBoost.php @@ -84,7 +84,7 @@ public function __construct(int $maxIterations = 50) /** * Sets the base classifier that will be used for boosting (default = DecisionStump) */ - public function setBaseClassifier(string $baseClassifier = DecisionStump::class, array $classifierOptions = []) + public function setBaseClassifier(string $baseClassifier = DecisionStump::class, array $classifierOptions = []): void { $this->baseClassifier = $baseClassifier; $this->classifierOptions = $classifierOptions; @@ -93,7 +93,7 @@ public function setBaseClassifier(string $baseClassifier = DecisionStump::class, /** * @throws \Exception */ - public function train(array $samples, array $targets) + public function train(array $samples, array $targets): void { // Initialize usual variables $this->labels = array_keys(array_count_values($targets)); @@ -149,7 +149,7 @@ protected function getBestClassifier() : Classifier $classifier->setSampleWeights($this->weights); $classifier->train($this->samples, $this->targets); } else { - list($samples, $targets) = $this->resample(); + [$samples, $targets] = $this->resample(); $classifier->train($samples, $targets); } @@ -216,7 +216,7 @@ protected function calculateAlpha(float $errorRate) : float /** * Updates the sample weights */ - protected function updateWeights(Classifier $classifier, float $alpha) + protected function updateWeights(Classifier $classifier, float $alpha): void { $sumOfWeights = array_sum($this->weights); $weightsT1 = []; diff --git a/src/Phpml/Classification/Ensemble/Bagging.php b/src/Phpml/Classification/Ensemble/Bagging.php index ebc75286..8d2bbf9b 100644 --- a/src/Phpml/Classification/Ensemble/Bagging.php +++ b/src/Phpml/Classification/Ensemble/Bagging.php @@ -106,7 +106,7 @@ public function setClassifer(string $classifier, array $classifierOptions = []) return $this; } - public function train(array $samples, array $targets) + public function train(array $samples, array $targets): void { $this->samples = array_merge($this->samples, $samples); $this->targets = array_merge($this->targets, $targets); @@ -117,7 +117,7 @@ public function train(array $samples, array $targets) $this->classifiers = $this->initClassifiers(); $index = 0; foreach ($this->classifiers as $classifier) { - list($samples, $targets) = $this->getRandomSubset($index); + [$samples, $targets] = $this->getRandomSubset($index); $classifier->train($samples, $targets); ++$index; } diff --git a/src/Phpml/Classification/KNearestNeighbors.php b/src/Phpml/Classification/KNearestNeighbors.php index c7783d8c..a261631c 100644 --- a/src/Phpml/Classification/KNearestNeighbors.php +++ b/src/Phpml/Classification/KNearestNeighbors.php @@ -26,7 +26,7 @@ class KNearestNeighbors implements Classifier /** * @param Distance|null $distanceMetric (if null then Euclidean distance as default) */ - public function __construct(int $k = 3, Distance $distanceMetric = null) + public function __construct(int $k = 3, ?Distance $distanceMetric = null) { if (null === $distanceMetric) { $distanceMetric = new Euclidean(); diff --git a/src/Phpml/Classification/Linear/Adaline.php b/src/Phpml/Classification/Linear/Adaline.php index 2a649ad9..64d25a48 100644 --- a/src/Phpml/Classification/Linear/Adaline.php +++ b/src/Phpml/Classification/Linear/Adaline.php @@ -9,12 +9,12 @@ class Adaline extends Perceptron /** * Batch training is the default Adaline training algorithm */ - const BATCH_TRAINING = 1; + public const BATCH_TRAINING = 1; /** * Online training: Stochastic gradient descent learning */ - const ONLINE_TRAINING = 2; + public const ONLINE_TRAINING = 2; /** * Training type may be either 'Batch' or 'Online' learning diff --git a/src/Phpml/Classification/Linear/DecisionStump.php b/src/Phpml/Classification/Linear/DecisionStump.php index 014dceb5..e1486a65 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -14,7 +14,7 @@ class DecisionStump extends WeightedClassifier { use Predictable, OneVsRest; - const AUTO_SELECT = -1; + public const AUTO_SELECT = -1; /** * @var int @@ -86,7 +86,7 @@ public function __construct(int $columnIndex = self::AUTO_SELECT) /** * @throws \Exception */ - protected function trainBinary(array $samples, array $targets, array $labels) + protected function trainBinary(array $samples, array $targets, array $labels): void { $this->binaryLabels = $labels; $this->featureCount = count($samples[0]); @@ -146,7 +146,7 @@ protected function trainBinary(array $samples, array $targets, array $labels) * points to be probed. The more split counts, the better performance but * worse processing time (Default value is 10.0) */ - public function setNumericalSplitCount(float $count) + public function setNumericalSplitCount(float $count): void { $this->numSplitCount = $count; } @@ -171,7 +171,7 @@ protected function getBestNumericalSplit(array $samples, array $targets, int $co // Before trying all possible split points, let's first try // the average value for the cut point $threshold = array_sum($values) / (float) count($values); - list($errorRate, $prob) = $this->calculateErrorRate($targets, $threshold, $operator, $values); + [$errorRate, $prob] = $this->calculateErrorRate($targets, $threshold, $operator, $values); if ($split == null || $errorRate < $split['trainingErrorRate']) { $split = ['value' => $threshold, 'operator' => $operator, 'prob' => $prob, 'column' => $col, @@ -181,7 +181,7 @@ protected function getBestNumericalSplit(array $samples, array $targets, int $co // Try other possible points one by one for ($step = $minValue; $step <= $maxValue; $step += $stepSize) { $threshold = (float) $step; - list($errorRate, $prob) = $this->calculateErrorRate($targets, $threshold, $operator, $values); + [$errorRate, $prob] = $this->calculateErrorRate($targets, $threshold, $operator, $values); if ($errorRate < $split['trainingErrorRate']) { $split = ['value' => $threshold, 'operator' => $operator, 'prob' => $prob, 'column' => $col, @@ -203,7 +203,7 @@ protected function getBestNominalSplit(array $samples, array $targets, int $col) foreach (['=', '!='] as $operator) { foreach ($distinctVals as $val) { - list($errorRate, $prob) = $this->calculateErrorRate($targets, $val, $operator, $values); + [$errorRate, $prob] = $this->calculateErrorRate($targets, $val, $operator, $values); if ($split == null || $split['trainingErrorRate'] < $errorRate) { $split = ['value' => $val, 'operator' => $operator, @@ -289,7 +289,7 @@ protected function predictSampleBinary(array $sample) return $this->binaryLabels[1]; } - protected function resetBinary() + protected function resetBinary(): void { } diff --git a/src/Phpml/Classification/Linear/LogisticRegression.php b/src/Phpml/Classification/Linear/LogisticRegression.php index bfbeb445..e8881be6 100644 --- a/src/Phpml/Classification/Linear/LogisticRegression.php +++ b/src/Phpml/Classification/Linear/LogisticRegression.php @@ -11,17 +11,17 @@ class LogisticRegression extends Adaline /** * Batch training: Gradient descent algorithm (default) */ - const BATCH_TRAINING = 1; + public const BATCH_TRAINING = 1; /** * Online training: Stochastic gradient descent learning */ - const ONLINE_TRAINING = 2; + public const ONLINE_TRAINING = 2; /** * Conjugate Batch: Conjugate Gradient algorithm */ - const CONJUGATE_GRAD_TRAINING = 3; + public const CONJUGATE_GRAD_TRAINING = 3; /** * Cost function to optimize: 'log' and 'sse' are supported
@@ -97,7 +97,7 @@ public function __construct( * Sets the learning rate if gradient descent algorithm is * selected for training */ - public function setLearningRate(float $learningRate) + public function setLearningRate(float $learningRate): void { $this->learningRate = $learningRate; } @@ -106,7 +106,7 @@ public function setLearningRate(float $learningRate) * Lambda (λ) parameter of regularization term. If 0 is given, * then the regularization term is cancelled */ - public function setLambda(float $lambda) + public function setLambda(float $lambda): void { $this->lambda = $lambda; } @@ -139,7 +139,7 @@ protected function runTraining(array $samples, array $targets) /** * Executes Conjugate Gradient method to optimize the weights of the LogReg model */ - protected function runConjugateGradient(array $samples, array $targets, \Closure $gradientFunc) + protected function runConjugateGradient(array $samples, array $targets, \Closure $gradientFunc): void { if (empty($this->optimizer)) { $this->optimizer = (new ConjugateGradient($this->featureCount)) diff --git a/src/Phpml/Classification/Linear/Perceptron.php b/src/Phpml/Classification/Linear/Perceptron.php index d5f424b0..77eb7173 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -88,12 +88,12 @@ public function __construct(float $learningRate = 0.001, int $maxIterations = 10 $this->maxIterations = $maxIterations; } - public function partialTrain(array $samples, array $targets, array $labels = []) + public function partialTrain(array $samples, array $targets, array $labels = []): void { $this->trainByLabel($samples, $targets, $labels); } - public function trainBinary(array $samples, array $targets, array $labels) + public function trainBinary(array $samples, array $targets, array $labels): void { if ($this->normalizer) { $this->normalizer->transform($samples); @@ -111,7 +111,7 @@ public function trainBinary(array $samples, array $targets, array $labels) $this->runTraining($samples, $targets); } - protected function resetBinary() + protected function resetBinary(): void { $this->labels = []; $this->optimizer = null; @@ -148,6 +148,8 @@ public function getCostValues() : array /** * Trains the perceptron model with Stochastic Gradient Descent optimization * to get the correct set of weights + * + * @return void|mixed */ protected function runTraining(array $samples, array $targets) { @@ -169,7 +171,7 @@ protected function runTraining(array $samples, array $targets) * Executes a Gradient Descent algorithm for * the given cost function */ - protected function runGradientDescent(array $samples, array $targets, \Closure $gradientFunc, bool $isBatch = false) + protected function runGradientDescent(array $samples, array $targets, \Closure $gradientFunc, bool $isBatch = false): void { $class = $isBatch ? GD::class : StochasticGD::class; diff --git a/src/Phpml/Classification/MLPClassifier.php b/src/Phpml/Classification/MLPClassifier.php index 64e6f506..b76091dc 100644 --- a/src/Phpml/Classification/MLPClassifier.php +++ b/src/Phpml/Classification/MLPClassifier.php @@ -45,7 +45,7 @@ protected function predictSample(array $sample) /** * @param mixed $target */ - protected function trainSample(array $sample, $target) + protected function trainSample(array $sample, $target): void { // Feed-forward. diff --git a/src/Phpml/Classification/NaiveBayes.php b/src/Phpml/Classification/NaiveBayes.php index 08073da6..97f734ae 100644 --- a/src/Phpml/Classification/NaiveBayes.php +++ b/src/Phpml/Classification/NaiveBayes.php @@ -13,9 +13,9 @@ class NaiveBayes implements Classifier { use Trainable, Predictable; - const CONTINUOS = 1; - const NOMINAL = 2; - const EPSILON = 1e-10; + public const CONTINUOS = 1; + public const NOMINAL = 2; + public const EPSILON = 1e-10; /** * @var array @@ -57,7 +57,7 @@ class NaiveBayes implements Classifier */ private $labels = []; - public function train(array $samples, array $targets) + public function train(array $samples, array $targets): void { $this->samples = array_merge($this->samples, $samples); $this->targets = array_merge($this->targets, $targets); @@ -77,7 +77,7 @@ public function train(array $samples, array $targets) * Calculates vital statistics for each label & feature. Stores these * values in private array in order to avoid repeated calculation */ - private function calculateStatistics(string $label, array $samples) + private function calculateStatistics(string $label, array $samples): void { $this->std[$label] = array_fill(0, $this->featureCount, 0); $this->mean[$label] = array_fill(0, $this->featureCount, 0); diff --git a/src/Phpml/Classification/SVC.php b/src/Phpml/Classification/SVC.php index 8cabcbcd..de71bbe6 100644 --- a/src/Phpml/Classification/SVC.php +++ b/src/Phpml/Classification/SVC.php @@ -14,7 +14,7 @@ public function __construct( int $kernel = Kernel::LINEAR, float $cost = 1.0, int $degree = 3, - float $gamma = null, + ?float $gamma = null, float $coef0 = 0.0, float $tolerance = 0.001, int $cacheSize = 100, diff --git a/src/Phpml/Classification/WeightedClassifier.php b/src/Phpml/Classification/WeightedClassifier.php index 0a5f1926..c9b1f975 100644 --- a/src/Phpml/Classification/WeightedClassifier.php +++ b/src/Phpml/Classification/WeightedClassifier.php @@ -14,7 +14,7 @@ abstract class WeightedClassifier implements Classifier /** * Sets the array including a weight for each sample */ - public function setSampleWeights(array $weights) + public function setSampleWeights(array $weights): void { $this->weights = $weights; } diff --git a/src/Phpml/Clustering/DBSCAN.php b/src/Phpml/Clustering/DBSCAN.php index 70cf302e..1968b836 100644 --- a/src/Phpml/Clustering/DBSCAN.php +++ b/src/Phpml/Clustering/DBSCAN.php @@ -24,7 +24,7 @@ class DBSCAN implements Clusterer */ private $distanceMetric; - public function __construct(float $epsilon = 0.5, int $minSamples = 3, Distance $distanceMetric = null) + public function __construct(float $epsilon = 0.5, int $minSamples = 3, ?Distance $distanceMetric = null) { if (null === $distanceMetric) { $distanceMetric = new Euclidean(); diff --git a/src/Phpml/Clustering/FuzzyCMeans.php b/src/Phpml/Clustering/FuzzyCMeans.php index d14c0be4..6eccea0b 100644 --- a/src/Phpml/Clustering/FuzzyCMeans.php +++ b/src/Phpml/Clustering/FuzzyCMeans.php @@ -71,7 +71,7 @@ public function __construct(int $clustersNumber, float $fuzziness = 2.0, float $ $this->maxIterations = $maxIterations; } - protected function initClusters() + protected function initClusters(): void { // Membership array is a matrix of cluster number by sample counts // We initilize the membership array with random values @@ -80,7 +80,7 @@ protected function initClusters() $this->updateClusters(); } - protected function generateRandomMembership(int $rows, int $cols) + protected function generateRandomMembership(int $rows, int $cols): void { $this->membership = []; for ($i = 0; $i < $rows; ++$i) { @@ -98,7 +98,7 @@ protected function generateRandomMembership(int $rows, int $cols) } } - protected function updateClusters() + protected function updateClusters(): void { $dim = $this->space->getDimension(); if (!$this->clusters) { @@ -136,7 +136,7 @@ protected function getMembershipRowTotal(int $row, int $col, bool $multiply) return $sum; } - protected function updateMembershipMatrix() + protected function updateMembershipMatrix(): void { for ($i = 0; $i < $this->clustersNumber; ++$i) { for ($k = 0; $k < $this->sampleCount; ++$k) { diff --git a/src/Phpml/Clustering/KMeans.php b/src/Phpml/Clustering/KMeans.php index 0a776a4f..a4e85bcf 100644 --- a/src/Phpml/Clustering/KMeans.php +++ b/src/Phpml/Clustering/KMeans.php @@ -9,8 +9,8 @@ class KMeans implements Clusterer { - const INIT_RANDOM = 1; - const INIT_KMEANS_PLUS_PLUS = 2; + public const INIT_RANDOM = 1; + public const INIT_KMEANS_PLUS_PLUS = 2; /** * @var int diff --git a/src/Phpml/Clustering/KMeans/Cluster.php b/src/Phpml/Clustering/KMeans/Cluster.php index b7c7ecf9..22545b6c 100644 --- a/src/Phpml/Clustering/KMeans/Cluster.php +++ b/src/Phpml/Clustering/KMeans/Cluster.php @@ -64,17 +64,17 @@ public function detach(Point $point) : Point return $point; } - public function attachAll(SplObjectStorage $points) + public function attachAll(SplObjectStorage $points): void { $this->points->addAll($points); } - public function detachAll(SplObjectStorage $points) + public function detachAll(SplObjectStorage $points): void { $this->points->removeAll($points); } - public function updateCentroid() + public function updateCentroid(): void { if (!$count = count($this->points)) { return; @@ -109,7 +109,7 @@ public function count() return count($this->points); } - public function setCoordinates(array $newCoordinates) + public function setCoordinates(array $newCoordinates): void { $this->coordinates = $newCoordinates; } diff --git a/src/Phpml/Clustering/KMeans/Point.php b/src/Phpml/Clustering/KMeans/Point.php index 08fa11ea..f90de8a0 100644 --- a/src/Phpml/Clustering/KMeans/Point.php +++ b/src/Phpml/Clustering/KMeans/Point.php @@ -93,7 +93,7 @@ public function offsetGet($offset) * @param mixed $offset * @param mixed $value */ - public function offsetSet($offset, $value) + public function offsetSet($offset, $value): void { $this->coordinates[$offset] = $value; } @@ -101,7 +101,7 @@ public function offsetSet($offset, $value) /** * @param mixed $offset */ - public function offsetUnset($offset) + public function offsetUnset($offset): void { unset($this->coordinates[$offset]); } diff --git a/src/Phpml/Clustering/KMeans/Space.php b/src/Phpml/Clustering/KMeans/Space.php index bf2a3453..4412d536 100644 --- a/src/Phpml/Clustering/KMeans/Space.php +++ b/src/Phpml/Clustering/KMeans/Space.php @@ -47,7 +47,7 @@ public function newPoint(array $coordinates) : Point /** * @param null $data */ - public function addPoint(array $coordinates, $data = null) + public function addPoint(array $coordinates, $data = null): void { $this->attach($this->newPoint($coordinates), $data); } @@ -56,7 +56,7 @@ public function addPoint(array $coordinates, $data = null) * @param Point $point * @param null $data */ - public function attach($point, $data = null) + public function attach($point, $data = null): void { if (!$point instanceof Point) { throw new InvalidArgumentException('can only attach points to spaces'); @@ -180,7 +180,7 @@ protected function iterate($clusters) : bool private function initializeRandomClusters(int $clustersNumber) : array { $clusters = []; - list($min, $max) = $this->getBoundaries(); + [$min, $max] = $this->getBoundaries(); for ($n = 0; $n < $clustersNumber; ++$n) { $clusters[] = new Cluster($this, $this->getRandomPoint($min, $max)->getCoordinates()); diff --git a/src/Phpml/CrossValidation/RandomSplit.php b/src/Phpml/CrossValidation/RandomSplit.php index 7ec18758..8507ee58 100644 --- a/src/Phpml/CrossValidation/RandomSplit.php +++ b/src/Phpml/CrossValidation/RandomSplit.php @@ -8,7 +8,7 @@ class RandomSplit extends Split { - protected function splitDataset(Dataset $dataset, float $testSize) + protected function splitDataset(Dataset $dataset, float $testSize): void { $samples = $dataset->getSamples(); $labels = $dataset->getTargets(); diff --git a/src/Phpml/CrossValidation/Split.php b/src/Phpml/CrossValidation/Split.php index 77eab3ce..e485ffb5 100644 --- a/src/Phpml/CrossValidation/Split.php +++ b/src/Phpml/CrossValidation/Split.php @@ -29,7 +29,7 @@ abstract class Split */ protected $testLabels = []; - public function __construct(Dataset $dataset, float $testSize = 0.3, int $seed = null) + public function __construct(Dataset $dataset, float $testSize = 0.3, ?int $seed = null) { if (0 >= $testSize || 1 <= $testSize) { throw InvalidArgumentException::percentNotInRange('testSize'); @@ -61,7 +61,7 @@ public function getTestLabels() : array return $this->testLabels; } - protected function seedGenerator(int $seed = null) + protected function seedGenerator(?int $seed = null): void { if (null === $seed) { mt_srand(); diff --git a/src/Phpml/CrossValidation/StratifiedRandomSplit.php b/src/Phpml/CrossValidation/StratifiedRandomSplit.php index 41f5d348..153cb8f1 100644 --- a/src/Phpml/CrossValidation/StratifiedRandomSplit.php +++ b/src/Phpml/CrossValidation/StratifiedRandomSplit.php @@ -9,7 +9,7 @@ class StratifiedRandomSplit extends RandomSplit { - protected function splitDataset(Dataset $dataset, float $testSize) + protected function splitDataset(Dataset $dataset, float $testSize): void { $datasets = $this->splitByTarget($dataset); diff --git a/src/Phpml/Dataset/FilesDataset.php b/src/Phpml/Dataset/FilesDataset.php index 73bc4b0e..ca04a3e6 100644 --- a/src/Phpml/Dataset/FilesDataset.php +++ b/src/Phpml/Dataset/FilesDataset.php @@ -17,14 +17,14 @@ public function __construct(string $rootPath) $this->scanRootPath($rootPath); } - private function scanRootPath(string $rootPath) + private function scanRootPath(string $rootPath): void { foreach (glob($rootPath.DIRECTORY_SEPARATOR.'*', GLOB_ONLYDIR) as $dir) { $this->scanDir($dir); } } - private function scanDir(string $dir) + private function scanDir(string $dir): void { $target = basename($dir); diff --git a/src/Phpml/DimensionReduction/EigenTransformerBase.php b/src/Phpml/DimensionReduction/EigenTransformerBase.php index df18d116..a6352ba6 100644 --- a/src/Phpml/DimensionReduction/EigenTransformerBase.php +++ b/src/Phpml/DimensionReduction/EigenTransformerBase.php @@ -48,7 +48,7 @@ abstract class EigenTransformerBase * top eigenVectors along with the largest eigenValues. The total explained variance * of these eigenVectors will be no less than desired $totalVariance value */ - protected function eigenDecomposition(array $matrix) + protected function eigenDecomposition(array $matrix): void { $eig = new EigenvalueDecomposition($matrix); $eigVals = $eig->getRealEigenvalues(); diff --git a/src/Phpml/DimensionReduction/KernelPCA.php b/src/Phpml/DimensionReduction/KernelPCA.php index 1c7cecb5..d11e1a6a 100644 --- a/src/Phpml/DimensionReduction/KernelPCA.php +++ b/src/Phpml/DimensionReduction/KernelPCA.php @@ -10,10 +10,10 @@ class KernelPCA extends PCA { - const KERNEL_RBF = 1; - const KERNEL_SIGMOID = 2; - const KERNEL_LAPLACIAN = 3; - const KERNEL_LINEAR = 4; + public const KERNEL_RBF = 1; + public const KERNEL_SIGMOID = 2; + public const KERNEL_LAPLACIAN = 3; + public const KERNEL_LINEAR = 4; /** * Selected kernel function @@ -50,7 +50,7 @@ class KernelPCA extends PCA * * @throws \Exception */ - public function __construct(int $kernel = self::KERNEL_RBF, float $totalVariance = null, int $numFeatures = null, float $gamma = null) + public function __construct(int $kernel = self::KERNEL_RBF, ?float $totalVariance = null, ?int $numFeatures = null, ?float $gamma = null) { $availableKernels = [self::KERNEL_RBF, self::KERNEL_SIGMOID, self::KERNEL_LAPLACIAN, self::KERNEL_LINEAR]; if (!in_array($kernel, $availableKernels)) { diff --git a/src/Phpml/DimensionReduction/LDA.php b/src/Phpml/DimensionReduction/LDA.php index 8a94f468..26b2324e 100644 --- a/src/Phpml/DimensionReduction/LDA.php +++ b/src/Phpml/DimensionReduction/LDA.php @@ -47,7 +47,7 @@ class LDA extends EigenTransformerBase * * @throws \Exception */ - public function __construct(float $totalVariance = null, int $numFeatures = null) + public function __construct(?float $totalVariance = null, ?int $numFeatures = null) { if ($totalVariance !== null && ($totalVariance < 0.1 || $totalVariance > 0.99)) { throw new \Exception('Total variance can be a value between 0.1 and 0.99'); diff --git a/src/Phpml/DimensionReduction/PCA.php b/src/Phpml/DimensionReduction/PCA.php index f5cb2190..25b71863 100644 --- a/src/Phpml/DimensionReduction/PCA.php +++ b/src/Phpml/DimensionReduction/PCA.php @@ -32,7 +32,7 @@ class PCA extends EigenTransformerBase * * @throws \Exception */ - public function __construct(float $totalVariance = null, int $numFeatures = null) + public function __construct(?float $totalVariance = null, ?int $numFeatures = null) { if ($totalVariance !== null && ($totalVariance < 0.1 || $totalVariance > 0.99)) { throw new \Exception('Total variance can be a value between 0.1 and 0.99'); @@ -73,7 +73,7 @@ public function fit(array $data) : array return $this->reduce($data); } - protected function calculateMeans(array $data, int $n) + protected function calculateMeans(array $data, int $n): void { // Calculate means for each dimension $this->means = []; diff --git a/src/Phpml/FeatureExtraction/TfIdfTransformer.php b/src/Phpml/FeatureExtraction/TfIdfTransformer.php index c5e1e591..6efd90f4 100644 --- a/src/Phpml/FeatureExtraction/TfIdfTransformer.php +++ b/src/Phpml/FeatureExtraction/TfIdfTransformer.php @@ -13,14 +13,14 @@ class TfIdfTransformer implements Transformer */ private $idf; - public function __construct(array $samples = null) + public function __construct(?array $samples = null) { if ($samples) { $this->fit($samples); } } - public function fit(array $samples) + public function fit(array $samples): void { $this->countTokensFrequency($samples); @@ -30,7 +30,7 @@ public function fit(array $samples) } } - public function transform(array &$samples) + public function transform(array &$samples): void { foreach ($samples as &$sample) { foreach ($sample as $index => &$feature) { @@ -39,7 +39,7 @@ public function transform(array &$samples) } } - private function countTokensFrequency(array $samples) + private function countTokensFrequency(array $samples): void { $this->idf = array_fill_keys(array_keys($samples[0]), 0); diff --git a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php index eb7a4ac6..e00fc698 100644 --- a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php +++ b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php @@ -34,7 +34,7 @@ class TokenCountVectorizer implements Transformer */ private $frequencies; - public function __construct(Tokenizer $tokenizer, StopWords $stopWords = null, float $minDF = 0.0) + public function __construct(Tokenizer $tokenizer, ?StopWords $stopWords = null, float $minDF = 0.0) { $this->tokenizer = $tokenizer; $this->stopWords = $stopWords; @@ -44,12 +44,12 @@ public function __construct(Tokenizer $tokenizer, StopWords $stopWords = null, f $this->frequencies = []; } - public function fit(array $samples) + public function fit(array $samples): void { $this->buildVocabulary($samples); } - public function transform(array &$samples) + public function transform(array &$samples): void { foreach ($samples as &$sample) { $this->transformSample($sample); @@ -63,7 +63,7 @@ public function getVocabulary() : array return array_flip($this->vocabulary); } - private function buildVocabulary(array &$samples) + private function buildVocabulary(array &$samples): void { foreach ($samples as $index => $sample) { $tokens = $this->tokenizer->tokenize($sample); @@ -73,7 +73,7 @@ private function buildVocabulary(array &$samples) } } - private function transformSample(string &$sample) + private function transformSample(string &$sample): void { $counts = []; $tokens = $this->tokenizer->tokenize($sample); @@ -113,7 +113,7 @@ private function getTokenIndex(string $token) return $this->vocabulary[$token] ?? false; } - private function addTokenToVocabulary(string $token) + private function addTokenToVocabulary(string $token): void { if ($this->isStopWord($token)) { return; @@ -129,7 +129,7 @@ private function isStopWord(string $token): bool return $this->stopWords && $this->stopWords->isStopWord($token); } - private function updateFrequency(string $token) + private function updateFrequency(string $token): void { if (!isset($this->frequencies[$token])) { $this->frequencies[$token] = 0; @@ -138,7 +138,7 @@ private function updateFrequency(string $token) ++$this->frequencies[$token]; } - private function checkDocumentFrequency(array &$samples) + private function checkDocumentFrequency(array &$samples): void { if ($this->minDF > 0) { $beyondMinimum = $this->getBeyondMinimumIndexes(count($samples)); @@ -148,7 +148,7 @@ private function checkDocumentFrequency(array &$samples) } } - private function resetBeyondMinimum(array &$sample, array $beyondMinimum) + private function resetBeyondMinimum(array &$sample, array $beyondMinimum): void { foreach ($beyondMinimum as $index) { $sample[$index] = 0; diff --git a/src/Phpml/Helper/OneVsRest.php b/src/Phpml/Helper/OneVsRest.php index 5ae126c6..15d62d86 100644 --- a/src/Phpml/Helper/OneVsRest.php +++ b/src/Phpml/Helper/OneVsRest.php @@ -28,7 +28,7 @@ trait OneVsRest /** * Train a binary classifier in the OvR style */ - public function train(array $samples, array $targets) + public function train(array $samples, array $targets): void { // Clears previous stuff. $this->reset(); @@ -36,7 +36,7 @@ public function train(array $samples, array $targets) $this->trainBylabel($samples, $targets); } - protected function trainByLabel(array $samples, array $targets, array $allLabels = []) + protected function trainByLabel(array $samples, array $targets, array $allLabels = []): void { // Overwrites the current value if it exist. $allLabels must be provided for each partialTrain run. if (!empty($allLabels)) { @@ -63,7 +63,7 @@ protected function trainByLabel(array $samples, array $targets, array $allLabels $this->classifiers[$label] = $this->getClassifierCopy(); } - list($binarizedTargets, $classifierLabels) = $this->binarizeTargets($targets, $label); + [$binarizedTargets, $classifierLabels] = $this->binarizeTargets($targets, $label); $this->classifiers[$label]->trainBinary($samples, $binarizedTargets, $classifierLabels); } } @@ -80,7 +80,7 @@ protected function trainByLabel(array $samples, array $targets, array $allLabels /** * Resets the classifier and the vars internally used by OneVsRest to create multiple classifiers. */ - public function reset() + public function reset(): void { $this->classifiers = []; $this->allLabels = []; @@ -158,7 +158,7 @@ abstract protected function trainBinary(array $samples, array $targets, array $l * * @return void */ - abstract protected function resetBinary(); + abstract protected function resetBinary(): void; /** * Each classifier that make use of OvR approach should be able to diff --git a/src/Phpml/Helper/Optimizer/ConjugateGradient.php b/src/Phpml/Helper/Optimizer/ConjugateGradient.php index 7333b9a4..c119eae2 100644 --- a/src/Phpml/Helper/Optimizer/ConjugateGradient.php +++ b/src/Phpml/Helper/Optimizer/ConjugateGradient.php @@ -61,7 +61,7 @@ public function runOptimization(array $samples, array $targets, \Closure $gradie */ protected function gradient(array $theta) : array { - list(, $gradient) = parent::gradient($theta); + [, $gradient] = parent::gradient($theta); return $gradient; } @@ -71,7 +71,7 @@ protected function gradient(array $theta) : array */ protected function cost(array $theta) : float { - list($cost) = parent::gradient($theta); + [$cost] = parent::gradient($theta); return array_sum($cost) / $this->sampleCount; } diff --git a/src/Phpml/Helper/Optimizer/GD.php b/src/Phpml/Helper/Optimizer/GD.php index ae3c6e21..38b4253c 100644 --- a/src/Phpml/Helper/Optimizer/GD.php +++ b/src/Phpml/Helper/Optimizer/GD.php @@ -31,7 +31,7 @@ public function runOptimization(array $samples, array $targets, \Closure $gradie $theta = $this->theta; // Calculate update terms for each sample - list($errors, $updates, $totalPenalty) = $this->gradient($theta); + [$errors, $updates, $totalPenalty] = $this->gradient($theta); $this->updateWeightsWithUpdates($updates, $totalPenalty); @@ -61,7 +61,7 @@ protected function gradient(array $theta) : array $target = $this->targets[$index]; $result = ($this->gradientCb)($theta, $sample, $target); - list($cost, $grad, $penalty) = array_pad($result, 3, 0); + [$cost, $grad, $penalty] = array_pad($result, 3, 0); $costs[] = $cost; $gradient[] = $grad; @@ -73,7 +73,7 @@ protected function gradient(array $theta) : array return [$costs, $gradient, $totalPenalty]; } - protected function updateWeightsWithUpdates(array $updates, float $penalty) + protected function updateWeightsWithUpdates(array $updates, float $penalty): void { // Updates all weights at once for ($i = 0; $i <= $this->dimensions; ++$i) { @@ -96,7 +96,7 @@ protected function updateWeightsWithUpdates(array $updates, float $penalty) /** * Clears the optimizer internal vars after the optimization process. */ - protected function clear() + protected function clear(): void { $this->sampleCount = null; parent::clear(); diff --git a/src/Phpml/Helper/Optimizer/StochasticGD.php b/src/Phpml/Helper/Optimizer/StochasticGD.php index 0a622d42..f1b0979c 100644 --- a/src/Phpml/Helper/Optimizer/StochasticGD.php +++ b/src/Phpml/Helper/Optimizer/StochasticGD.php @@ -191,7 +191,7 @@ protected function updateTheta() : float $result = ($this->gradientCb)($theta, $sample, $target); - list($error, $gradient, $penalty) = array_pad($result, 3, 0); + [$error, $gradient, $penalty] = array_pad($result, 3, 0); // Update bias $this->theta[0] -= $this->learningRate * $gradient; @@ -249,7 +249,7 @@ public function getCostValues() : array /** * Clears the optimizer internal vars after the optimization process. */ - protected function clear() + protected function clear(): void { $this->samples = []; $this->targets = []; diff --git a/src/Phpml/Helper/Trainable.php b/src/Phpml/Helper/Trainable.php index 3d011ac4..86ffaf12 100644 --- a/src/Phpml/Helper/Trainable.php +++ b/src/Phpml/Helper/Trainable.php @@ -20,7 +20,7 @@ trait Trainable * @param array $samples * @param array $targets */ - public function train(array $samples, array $targets) + public function train(array $samples, array $targets): void { $this->samples = array_merge($this->samples, $samples); $this->targets = array_merge($this->targets, $targets); diff --git a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php index b76ddb9f..6261d26b 100644 --- a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -119,7 +119,7 @@ public function __construct(array $Arg) /** * Symmetric Householder reduction to tridiagonal form. */ - private function tred2() + private function tred2(): void { // This is derived from the Algol procedures tred2 by // Bowdler, Martin, Reinsch, and Wilkinson, Handbook for @@ -236,7 +236,7 @@ private function tred2() * Auto. Comp., Vol.ii-Linear Algebra, and the corresponding * Fortran subroutine in EISPACK. */ - private function tql2() + private function tql2(): void { for ($i = 1; $i < $this->n; ++$i) { $this->e[$i - 1] = $this->e[$i]; @@ -343,7 +343,7 @@ private function tql2() * Vol.ii-Linear Algebra, and the corresponding * Fortran subroutines in EISPACK. */ - private function orthes() + private function orthes(): void { $low = 0; $high = $this->n - 1; @@ -428,7 +428,7 @@ private function orthes() * @param int|float $yr * @param int|float $yi */ - private function cdiv($xr, $xi, $yr, $yi) + private function cdiv($xr, $xi, $yr, $yi): void { if (abs($yr) > abs($yi)) { $r = $yi / $yr; @@ -451,7 +451,7 @@ private function cdiv($xr, $xi, $yr, $yi) * Vol.ii-Linear Algebra, and the corresponding * Fortran subroutine in EISPACK. */ - private function hqr2() + private function hqr2(): void { // Initialize $nn = $this->n; diff --git a/src/Phpml/Math/Statistic/Covariance.php b/src/Phpml/Math/Statistic/Covariance.php index 2c72e6ca..627a8a64 100644 --- a/src/Phpml/Math/Statistic/Covariance.php +++ b/src/Phpml/Math/Statistic/Covariance.php @@ -13,7 +13,7 @@ class Covariance * * @throws InvalidArgumentException */ - public static function fromXYArrays(array $x, array $y, bool $sample = true, float $meanX = null, float $meanY = null) : float + public static function fromXYArrays(array $x, array $y, bool $sample = true, ?float $meanX = null, ?float $meanY = null) : float { if (empty($x) || empty($y)) { throw InvalidArgumentException::arrayCantBeEmpty(); @@ -51,7 +51,7 @@ public static function fromXYArrays(array $x, array $y, bool $sample = true, flo * @throws InvalidArgumentException * @throws \Exception */ - public static function fromDataset(array $data, int $i, int $k, bool $sample = true, float $meanX = null, float $meanY = null) : float + public static function fromDataset(array $data, int $i, int $k, bool $sample = true, ?float $meanX = null, ?float $meanY = null) : float { if (empty($data)) { throw InvalidArgumentException::arrayCantBeEmpty(); @@ -112,7 +112,7 @@ public static function fromDataset(array $data, int $i, int $k, bool $sample = t * * @param array|null $means */ - public static function covarianceMatrix(array $data, array $means = null) : array + public static function covarianceMatrix(array $data, ?array $means = null) : array { $n = count($data[0]); diff --git a/src/Phpml/Math/Statistic/Mean.php b/src/Phpml/Math/Statistic/Mean.php index 50b37884..eb1baef2 100644 --- a/src/Phpml/Math/Statistic/Mean.php +++ b/src/Phpml/Math/Statistic/Mean.php @@ -56,7 +56,7 @@ public static function mode(array $numbers) /** * @throws InvalidArgumentException */ - private static function checkArrayLength(array $array) + private static function checkArrayLength(array $array): void { if (empty($array)) { throw InvalidArgumentException::arrayCantBeEmpty(); diff --git a/src/Phpml/Metric/ClassificationReport.php b/src/Phpml/Metric/ClassificationReport.php index f4fdaee7..ae4c11aa 100644 --- a/src/Phpml/Metric/ClassificationReport.php +++ b/src/Phpml/Metric/ClassificationReport.php @@ -76,7 +76,7 @@ public function getAverage() : array return $this->average; } - private function computeMetrics(array $truePositive, array $falsePositive, array $falseNegative) + private function computeMetrics(array $truePositive, array $falsePositive, array $falseNegative): void { foreach ($truePositive as $label => $tp) { $this->precision[$label] = $this->computePrecision($tp, $falsePositive[$label]); @@ -85,7 +85,7 @@ private function computeMetrics(array $truePositive, array $falsePositive, array } } - private function computeAverage() + private function computeAverage(): void { foreach (['precision', 'recall', 'f1score'] as $metric) { $values = array_filter($this->{$metric}); diff --git a/src/Phpml/Metric/ConfusionMatrix.php b/src/Phpml/Metric/ConfusionMatrix.php index 5c7bc0b7..0f0b738f 100644 --- a/src/Phpml/Metric/ConfusionMatrix.php +++ b/src/Phpml/Metric/ConfusionMatrix.php @@ -6,7 +6,7 @@ class ConfusionMatrix { - public static function compute(array $actualLabels, array $predictedLabels, array $labels = null) : array + public static function compute(array $actualLabels, array $predictedLabels, ?array $labels = null) : array { $labels = $labels ? array_flip($labels) : self::getUniqueLabels($actualLabels); $matrix = self::generateMatrixWithZeros($labels); diff --git a/src/Phpml/ModelManager.php b/src/Phpml/ModelManager.php index 28594be7..2fa14f5c 100644 --- a/src/Phpml/ModelManager.php +++ b/src/Phpml/ModelManager.php @@ -9,7 +9,7 @@ class ModelManager { - public function saveToFile(Estimator $estimator, string $filepath) + public function saveToFile(Estimator $estimator, string $filepath): void { if (!is_writable(dirname($filepath))) { throw FileException::cantSaveFile(basename($filepath)); diff --git a/src/Phpml/NeuralNetwork/Layer.php b/src/Phpml/NeuralNetwork/Layer.php index 524bf35c..c70bdb36 100644 --- a/src/Phpml/NeuralNetwork/Layer.php +++ b/src/Phpml/NeuralNetwork/Layer.php @@ -17,7 +17,7 @@ class Layer /** * @throws InvalidArgumentException */ - public function __construct(int $nodesNumber = 0, string $nodeClass = Neuron::class, ActivationFunction $activationFunction = null) + public function __construct(int $nodesNumber = 0, string $nodeClass = Neuron::class, ?ActivationFunction $activationFunction = null) { if (!in_array(Node::class, class_implements($nodeClass))) { throw InvalidArgumentException::invalidLayerNodeClass(); @@ -33,7 +33,7 @@ public function __construct(int $nodesNumber = 0, string $nodeClass = Neuron::cl * * @return Neuron */ - private function createNode(string $nodeClass, ActivationFunction $activationFunction = null) + private function createNode(string $nodeClass, ?ActivationFunction $activationFunction = null) { if (Neuron::class == $nodeClass) { return new Neuron($activationFunction); @@ -42,7 +42,7 @@ private function createNode(string $nodeClass, ActivationFunction $activationFun return new $nodeClass(); } - public function addNode(Node $node) + public function addNode(Node $node): void { $this->nodes[] = $node; } diff --git a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php index 1234c41d..3baa5ac2 100644 --- a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php +++ b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php @@ -16,7 +16,7 @@ abstract class LayeredNetwork implements Network */ protected $layers; - public function addLayer(Layer $layer) + public function addLayer(Layer $layer): void { $this->layers[] = $layer; } @@ -29,7 +29,7 @@ public function getLayers() : array return $this->layers; } - public function removeLayers() + public function removeLayers(): void { unset($this->layers); } diff --git a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php index 530fbef7..9ef3f73e 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -58,7 +58,7 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, /** * @throws InvalidArgumentException */ - public function __construct(int $inputLayerFeatures, array $hiddenLayers, array $classes, int $iterations = 10000, ActivationFunction $activationFunction = null, int $theta = 1) + public function __construct(int $inputLayerFeatures, array $hiddenLayers, array $classes, int $iterations = 10000, ?ActivationFunction $activationFunction = null, int $theta = 1) { if (empty($hiddenLayers)) { throw InvalidArgumentException::invalidLayersNumber(); @@ -78,7 +78,7 @@ public function __construct(int $inputLayerFeatures, array $hiddenLayers, array $this->initNetwork(); } - private function initNetwork() + private function initNetwork(): void { $this->addInputLayer($this->inputLayerFeatures); $this->addNeuronLayers($this->hiddenLayers, $this->activationFunction); @@ -90,7 +90,7 @@ private function initNetwork() $this->backpropagation = new Backpropagation($this->theta); } - public function train(array $samples, array $targets) + public function train(array $samples, array $targets): void { $this->reset(); $this->initNetwork(); @@ -100,7 +100,7 @@ public function train(array $samples, array $targets) /** * @throws InvalidArgumentException */ - public function partialTrain(array $samples, array $targets, array $classes = []) + public function partialTrain(array $samples, array $targets, array $classes = []): void { if (!empty($classes) && array_values($classes) !== $this->classes) { // We require the list of classes in the constructor. @@ -122,24 +122,24 @@ abstract protected function trainSample(array $sample, $target); */ abstract protected function predictSample(array $sample); - protected function reset() + protected function reset(): void { $this->removeLayers(); } - private function addInputLayer(int $nodes) + private function addInputLayer(int $nodes): void { $this->addLayer(new Layer($nodes, Input::class)); } - private function addNeuronLayers(array $layers, ActivationFunction $activationFunction = null) + private function addNeuronLayers(array $layers, ?ActivationFunction $activationFunction = null): void { foreach ($layers as $neurons) { $this->addLayer(new Layer($neurons, Neuron::class, $activationFunction)); } } - private function generateSynapses() + private function generateSynapses(): void { $layersNumber = count($this->layers) - 1; for ($i = 0; $i < $layersNumber; ++$i) { @@ -149,7 +149,7 @@ private function generateSynapses() } } - private function addBiasNodes() + private function addBiasNodes(): void { $biasLayers = count($this->layers) - 1; for ($i = 0; $i < $biasLayers; ++$i) { @@ -157,7 +157,7 @@ private function addBiasNodes() } } - private function generateLayerSynapses(Layer $nextLayer, Layer $currentLayer) + private function generateLayerSynapses(Layer $nextLayer, Layer $currentLayer): void { foreach ($nextLayer->getNodes() as $nextNeuron) { if ($nextNeuron instanceof Neuron) { @@ -166,14 +166,14 @@ private function generateLayerSynapses(Layer $nextLayer, Layer $currentLayer) } } - private function generateNeuronSynapses(Layer $currentLayer, Neuron $nextNeuron) + private function generateNeuronSynapses(Layer $currentLayer, Neuron $nextNeuron): void { foreach ($currentLayer->getNodes() as $currentNeuron) { $nextNeuron->addSynapse(new Synapse($currentNeuron)); } } - private function trainSamples(array $samples, array $targets) + private function trainSamples(array $samples, array $targets): void { foreach ($targets as $key => $target) { $this->trainSample($samples[$key], $target); diff --git a/src/Phpml/NeuralNetwork/Node/Input.php b/src/Phpml/NeuralNetwork/Node/Input.php index 569b303d..8ff78ea4 100644 --- a/src/Phpml/NeuralNetwork/Node/Input.php +++ b/src/Phpml/NeuralNetwork/Node/Input.php @@ -23,7 +23,7 @@ public function getOutput() : float return $this->input; } - public function setInput(float $input) + public function setInput(float $input): void { $this->input = $input; } diff --git a/src/Phpml/NeuralNetwork/Node/Neuron.php b/src/Phpml/NeuralNetwork/Node/Neuron.php index 520c3b26..096d54f0 100644 --- a/src/Phpml/NeuralNetwork/Node/Neuron.php +++ b/src/Phpml/NeuralNetwork/Node/Neuron.php @@ -25,14 +25,14 @@ class Neuron implements Node */ protected $output; - public function __construct(ActivationFunction $activationFunction = null) + public function __construct(?ActivationFunction $activationFunction = null) { $this->activationFunction = $activationFunction ?: new ActivationFunction\Sigmoid(); $this->synapses = []; $this->output = 0; } - public function addSynapse(Synapse $synapse) + public function addSynapse(Synapse $synapse): void { $this->synapses[] = $synapse; } @@ -59,7 +59,7 @@ public function getOutput() : float return $this->output; } - public function reset() + public function reset(): void { $this->output = 0; } diff --git a/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php b/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php index adb4b023..0883f4e6 100644 --- a/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php +++ b/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php @@ -21,7 +21,7 @@ class Synapse /** * @param float|null $weight */ - public function __construct(Node $node, float $weight = null) + public function __construct(Node $node, ?float $weight = null) { $this->node = $node; $this->weight = $weight ?: $this->generateRandomWeight(); @@ -37,7 +37,7 @@ public function getOutput() : float return $this->weight * $this->node->getOutput(); } - public function changeWeight(float $delta) + public function changeWeight(float $delta): void { $this->weight += $delta; } diff --git a/src/Phpml/NeuralNetwork/Training/Backpropagation.php b/src/Phpml/NeuralNetwork/Training/Backpropagation.php index b113454c..98683abc 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -32,7 +32,7 @@ public function __construct(int $theta) /** * @param mixed $targetClass */ - public function backpropagate(array $layers, $targetClass) + public function backpropagate(array $layers, $targetClass): void { $layersNumber = count($layers); diff --git a/src/Phpml/Pipeline.php b/src/Phpml/Pipeline.php index a49fb27d..a72e634e 100644 --- a/src/Phpml/Pipeline.php +++ b/src/Phpml/Pipeline.php @@ -28,12 +28,12 @@ public function __construct(array $transformers, Estimator $estimator) $this->estimator = $estimator; } - public function addTransformer(Transformer $transformer) + public function addTransformer(Transformer $transformer): void { $this->transformers[] = $transformer; } - public function setEstimator(Estimator $estimator) + public function setEstimator(Estimator $estimator): void { $this->estimator = $estimator; } @@ -51,7 +51,7 @@ public function getEstimator(): Estimator return $this->estimator; } - public function train(array $samples, array $targets) + public function train(array $samples, array $targets): void { foreach ($this->transformers as $transformer) { $transformer->fit($samples); @@ -71,7 +71,7 @@ public function predict(array $samples) return $this->estimator->predict($samples); } - private function transformSamples(array &$samples) + private function transformSamples(array &$samples): void { foreach ($this->transformers as $transformer) { $transformer->transform($samples); diff --git a/src/Phpml/Preprocessing/Imputer.php b/src/Phpml/Preprocessing/Imputer.php index cc90c46f..d2dbfcd8 100644 --- a/src/Phpml/Preprocessing/Imputer.php +++ b/src/Phpml/Preprocessing/Imputer.php @@ -8,8 +8,8 @@ class Imputer implements Preprocessor { - const AXIS_COLUMN = 0; - const AXIS_ROW = 1; + public const AXIS_COLUMN = 0; + public const AXIS_ROW = 1; /** * @var mixed @@ -43,19 +43,19 @@ public function __construct($missingValue, Strategy $strategy, int $axis = self: $this->samples = $samples; } - public function fit(array $samples) + public function fit(array $samples): void { $this->samples = $samples; } - public function transform(array &$samples) + public function transform(array &$samples): void { foreach ($samples as &$sample) { $this->preprocessSample($sample); } } - private function preprocessSample(array &$sample) + private function preprocessSample(array &$sample): void { foreach ($sample as $column => &$value) { if ($value === $this->missingValue) { diff --git a/src/Phpml/Preprocessing/Normalizer.php b/src/Phpml/Preprocessing/Normalizer.php index 07777f93..f038345f 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -10,9 +10,9 @@ class Normalizer implements Preprocessor { - const NORM_L1 = 1; - const NORM_L2 = 2; - const NORM_STD = 3; + public const NORM_L1 = 1; + public const NORM_L2 = 2; + public const NORM_STD = 3; /** * @var int @@ -46,7 +46,7 @@ public function __construct(int $norm = self::NORM_L2) $this->norm = $norm; } - public function fit(array $samples) + public function fit(array $samples): void { if ($this->fitted) { return; @@ -64,7 +64,7 @@ public function fit(array $samples) $this->fitted = true; } - public function transform(array &$samples) + public function transform(array &$samples): void { $methods = [ self::NORM_L1 => 'normalizeL1', @@ -80,7 +80,7 @@ public function transform(array &$samples) } } - private function normalizeL1(array &$sample) + private function normalizeL1(array &$sample): void { $norm1 = 0; foreach ($sample as $feature) { @@ -97,7 +97,7 @@ private function normalizeL1(array &$sample) } } - private function normalizeL2(array &$sample) + private function normalizeL2(array &$sample): void { $norm2 = 0; foreach ($sample as $feature) { @@ -114,7 +114,7 @@ private function normalizeL2(array &$sample) } } - private function normalizeSTD(array &$sample) + private function normalizeSTD(array &$sample): void { foreach ($sample as $i => $val) { if ($this->std[$i] != 0) { diff --git a/src/Phpml/Regression/LeastSquares.php b/src/Phpml/Regression/LeastSquares.php index 28602cd4..f8adcb28 100644 --- a/src/Phpml/Regression/LeastSquares.php +++ b/src/Phpml/Regression/LeastSquares.php @@ -30,7 +30,7 @@ class LeastSquares implements Regression */ private $coefficients; - public function train(array $samples, array $targets) + public function train(array $samples, array $targets): void { $this->samples = array_merge($this->samples, $samples); $this->targets = array_merge($this->targets, $targets); @@ -64,7 +64,7 @@ public function getIntercept() : float /** * coefficient(b) = (X'X)-1X'Y. */ - private function computeCoefficients() + private function computeCoefficients(): void { $samplesMatrix = $this->getSamplesMatrix(); $targetsMatrix = $this->getTargetsMatrix(); diff --git a/src/Phpml/Regression/SVR.php b/src/Phpml/Regression/SVR.php index 54215e05..e3032aca 100644 --- a/src/Phpml/Regression/SVR.php +++ b/src/Phpml/Regression/SVR.php @@ -15,7 +15,7 @@ public function __construct( int $degree = 3, float $epsilon = 0.1, float $cost = 1.0, - float $gamma = null, + ?float $gamma = null, float $coef0 = 0.0, float $tolerance = 0.001, int $cacheSize = 100, diff --git a/src/Phpml/SupportVectorMachine/Kernel.php b/src/Phpml/SupportVectorMachine/Kernel.php index 9918a3fc..af76e6db 100644 --- a/src/Phpml/SupportVectorMachine/Kernel.php +++ b/src/Phpml/SupportVectorMachine/Kernel.php @@ -9,20 +9,20 @@ abstract class Kernel /** * u'*v. */ - const LINEAR = 0; + public const LINEAR = 0; /** * (gamma*u'*v + coef0)^degree. */ - const POLYNOMIAL = 1; + public const POLYNOMIAL = 1; /** * exp(-gamma*|u-v|^2). */ - const RBF = 2; + public const RBF = 2; /** * tanh(gamma*u'*v + coef0). */ - const SIGMOID = 3; + public const SIGMOID = 3; } diff --git a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php index 6b6c48d1..cbee23da 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -97,7 +97,7 @@ public function __construct( float $cost = 1.0, float $nu = 0.5, int $degree = 3, - float $gamma = null, + ?float $gamma = null, float $coef0 = 0.0, float $epsilon = 0.1, float $tolerance = 0.001, @@ -124,7 +124,7 @@ public function __construct( $this->varPath = $rootPath.'var'.DIRECTORY_SEPARATOR; } - public function setBinPath(string $binPath) + public function setBinPath(string $binPath): void { $this->ensureDirectorySeparator($binPath); $this->verifyBinPath($binPath); @@ -132,7 +132,7 @@ public function setBinPath(string $binPath) $this->binPath = $binPath; } - public function setVarPath(string $varPath) + public function setVarPath(string $varPath): void { if (!is_writable($varPath)) { throw InvalidArgumentException::pathNotWritable($varPath); @@ -142,7 +142,7 @@ public function setVarPath(string $varPath) $this->varPath = $varPath; } - public function train(array $samples, array $targets) + public function train(array $samples, array $targets): void { $this->samples = array_merge($this->samples, $samples); $this->targets = array_merge($this->targets, $targets); @@ -234,14 +234,14 @@ private function buildTrainCommand(string $trainingSetFileName, string $modelFil ); } - private function ensureDirectorySeparator(string &$path) + private function ensureDirectorySeparator(string &$path): void { if (substr($path, -1) !== DIRECTORY_SEPARATOR) { $path .= DIRECTORY_SEPARATOR; } } - private function verifyBinPath(string $path) + private function verifyBinPath(string $path): void { if (!is_dir($path)) { throw InvalidArgumentException::pathNotFound($path); diff --git a/src/Phpml/SupportVectorMachine/Type.php b/src/Phpml/SupportVectorMachine/Type.php index 1b454a5d..1dea9df0 100644 --- a/src/Phpml/SupportVectorMachine/Type.php +++ b/src/Phpml/SupportVectorMachine/Type.php @@ -9,25 +9,25 @@ abstract class Type /** * classification. */ - const C_SVC = 0; + public const C_SVC = 0; /** * classification. */ - const NU_SVC = 1; + public const NU_SVC = 1; /** * distribution estimation. */ - const ONE_CLASS_SVM = 2; + public const ONE_CLASS_SVM = 2; /** * regression. */ - const EPSILON_SVR = 3; + public const EPSILON_SVR = 3; /** * regression. */ - const NU_SVR = 4; + public const NU_SVR = 4; } diff --git a/tests/Phpml/Association/AprioriTest.php b/tests/Phpml/Association/AprioriTest.php index c715d6f9..3b47483f 100644 --- a/tests/Phpml/Association/AprioriTest.php +++ b/tests/Phpml/Association/AprioriTest.php @@ -40,7 +40,7 @@ class AprioriTest extends TestCase [2, 4], ]; - public function testGreek() + public function testGreek(): void { $apriori = new Apriori(0.5, 0.5); $apriori->train($this->sampleGreek, []); @@ -49,14 +49,14 @@ public function testGreek() $this->assertEquals('alpha', $apriori->predict([['alpha', 'epsilon'], ['beta', 'theta']])[1][0][0]); } - public function testPowerSet() + public function testPowerSet(): void { $apriori = new Apriori(); $this->assertCount(8, $this->invoke($apriori, 'powerSet', [['a', 'b', 'c']])); } - public function testApriori() + public function testApriori(): void { $apriori = new Apriori(3 / 7); $apriori->train($this->sampleBasket, []); @@ -73,7 +73,7 @@ public function testApriori() $this->assertTrue($this->invoke($apriori, 'contains', [$L[2], [3, 4]])); } - public function testGetRules() + public function testGetRules(): void { $apriori = new Apriori(0.4, 0.8); $apriori->train($this->sampleChars, []); @@ -81,21 +81,21 @@ public function testGetRules() $this->assertCount(19, $apriori->getRules()); } - public function testAntecedents() + public function testAntecedents(): void { $apriori = new Apriori(); $this->assertCount(6, $this->invoke($apriori, 'antecedents', [['a', 'b', 'c']])); } - public function testItems() + public function testItems(): void { $apriori = new Apriori(); $apriori->train($this->sampleGreek, []); $this->assertCount(4, $this->invoke($apriori, 'items', [])); } - public function testFrequent() + public function testFrequent(): void { $apriori = new Apriori(0.51); $apriori->train($this->sampleGreek, []); @@ -104,7 +104,7 @@ public function testFrequent() $this->assertCount(2, $this->invoke($apriori, 'frequent', [[['alpha'], ['beta']]])); } - public function testCandidates() + public function testCandidates(): void { $apriori = new Apriori(); $apriori->train($this->sampleGreek, []); @@ -115,7 +115,7 @@ public function testCandidates() $this->assertCount(3, $this->invoke($apriori, 'candidates', [[['alpha'], ['beta'], ['theta']]])); } - public function testConfidence() + public function testConfidence(): void { $apriori = new Apriori(); $apriori->train($this->sampleGreek, []); @@ -124,7 +124,7 @@ public function testConfidence() $this->assertEquals(1, $this->invoke($apriori, 'confidence', [['alpha', 'beta'], ['alpha']])); } - public function testSupport() + public function testSupport(): void { $apriori = new Apriori(); $apriori->train($this->sampleGreek, []); @@ -133,7 +133,7 @@ public function testSupport() $this->assertEquals(0.5, $this->invoke($apriori, 'support', [['epsilon']])); } - public function testFrequency() + public function testFrequency(): void { $apriori = new Apriori(); $apriori->train($this->sampleGreek, []); @@ -142,7 +142,7 @@ public function testFrequency() $this->assertEquals(2, $this->invoke($apriori, 'frequency', [['epsilon']])); } - public function testContains() + public function testContains(): void { $apriori = new Apriori(); @@ -151,7 +151,7 @@ public function testContains() $this->assertFalse($this->invoke($apriori, 'contains', [[['a'], ['b']], ['c']])); } - public function testSubset() + public function testSubset(): void { $apriori = new Apriori(); @@ -160,7 +160,7 @@ public function testSubset() $this->assertFalse($this->invoke($apriori, 'subset', [['a'], ['a', 'b']])); } - public function testEquals() + public function testEquals(): void { $apriori = new Apriori(); @@ -187,7 +187,7 @@ public function invoke(&$object, $method, array $params = []) return $method->invokeArgs($object, $params); } - public function testSaveAndRestore() + public function testSaveAndRestore(): void { $classifier = new Apriori(0.5, 0.5); $classifier->train($this->sampleGreek, []); diff --git a/tests/Phpml/Classification/DecisionTree/DecisionTreeLeafTest.php b/tests/Phpml/Classification/DecisionTree/DecisionTreeLeafTest.php index 854e4e5f..626ed649 100644 --- a/tests/Phpml/Classification/DecisionTree/DecisionTreeLeafTest.php +++ b/tests/Phpml/Classification/DecisionTree/DecisionTreeLeafTest.php @@ -9,7 +9,7 @@ class DecisionTreeLeafTest extends TestCase { - public function testHTMLOutput() + public function testHTMLOutput(): void { $leaf = new DecisionTreeLeaf(); $leaf->value = 1; diff --git a/tests/Phpml/Classification/DecisionTreeTest.php b/tests/Phpml/Classification/DecisionTreeTest.php index f0ebc04b..c7d2d2af 100644 --- a/tests/Phpml/Classification/DecisionTreeTest.php +++ b/tests/Phpml/Classification/DecisionTreeTest.php @@ -35,7 +35,7 @@ class DecisionTreeTest extends TestCase private function getData($input) { $targets = array_column($input, 4); - array_walk($input, function (&$v) { + array_walk($input, function (&$v): void { array_splice($v, 4, 1); }); @@ -44,14 +44,14 @@ private function getData($input) public function testPredictSingleSample() { - list($data, $targets) = $this->getData($this->data); + [$data, $targets] = $this->getData($this->data); $classifier = new DecisionTree(5); $classifier->train($data, $targets); $this->assertEquals('Dont_play', $classifier->predict(['sunny', 78, 72, 'false'])); $this->assertEquals('Play', $classifier->predict(['overcast', 60, 60, 'false'])); $this->assertEquals('Dont_play', $classifier->predict(['rain', 60, 60, 'true'])); - list($data, $targets) = $this->getData($this->extraData); + [$data, $targets] = $this->getData($this->extraData); $classifier->train($data, $targets); $this->assertEquals('Dont_play', $classifier->predict(['scorching', 95, 90, 'true'])); $this->assertEquals('Play', $classifier->predict(['overcast', 60, 60, 'false'])); @@ -59,9 +59,9 @@ public function testPredictSingleSample() return $classifier; } - public function testSaveAndRestore() + public function testSaveAndRestore(): void { - list($data, $targets) = $this->getData($this->data); + [$data, $targets] = $this->getData($this->data); $classifier = new DecisionTree(5); $classifier->train($data, $targets); @@ -78,9 +78,9 @@ public function testSaveAndRestore() $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); } - public function testTreeDepth() + public function testTreeDepth(): void { - list($data, $targets) = $this->getData($this->data); + [$data, $targets] = $this->getData($this->data); $classifier = new DecisionTree(5); $classifier->train($data, $targets); $this->assertTrue(5 >= $classifier->actualDepth); diff --git a/tests/Phpml/Classification/Ensemble/AdaBoostTest.php b/tests/Phpml/Classification/Ensemble/AdaBoostTest.php index 8b8d0db2..2cc8090d 100644 --- a/tests/Phpml/Classification/Ensemble/AdaBoostTest.php +++ b/tests/Phpml/Classification/Ensemble/AdaBoostTest.php @@ -42,7 +42,7 @@ public function testPredictSingleSample() return $classifier; } - public function testSaveAndRestore() + public function testSaveAndRestore(): void { // Instantinate new Percetron trained for OR problem $samples = [[0, 0], [1, 0], [0, 1], [1, 1]]; diff --git a/tests/Phpml/Classification/Ensemble/BaggingTest.php b/tests/Phpml/Classification/Ensemble/BaggingTest.php index afc358af..5bca8de8 100644 --- a/tests/Phpml/Classification/Ensemble/BaggingTest.php +++ b/tests/Phpml/Classification/Ensemble/BaggingTest.php @@ -36,7 +36,7 @@ class BaggingTest extends TestCase public function testPredictSingleSample() { - list($data, $targets) = $this->getData($this->data); + [$data, $targets] = $this->getData($this->data); $classifier = $this->getClassifier(); // Testing with default options $classifier->train($data, $targets); @@ -44,7 +44,7 @@ public function testPredictSingleSample() $this->assertEquals('Play', $classifier->predict(['overcast', 60, 60, 'false'])); $this->assertEquals('Dont_play', $classifier->predict(['rain', 60, 60, 'true'])); - list($data, $targets) = $this->getData($this->extraData); + [$data, $targets] = $this->getData($this->extraData); $classifier->train($data, $targets); $this->assertEquals('Dont_play', $classifier->predict(['scorching', 95, 90, 'true'])); $this->assertEquals('Play', $classifier->predict(['overcast', 60, 60, 'false'])); @@ -52,9 +52,9 @@ public function testPredictSingleSample() return $classifier; } - public function testSaveAndRestore() + public function testSaveAndRestore(): void { - list($data, $targets) = $this->getData($this->data); + [$data, $targets] = $this->getData($this->data); $classifier = $this->getClassifier(5); $classifier->train($data, $targets); @@ -71,9 +71,9 @@ public function testSaveAndRestore() $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); } - public function testBaseClassifiers() + public function testBaseClassifiers(): void { - list($data, $targets) = $this->getData($this->data); + [$data, $targets] = $this->getData($this->data); $baseClassifiers = $this->getAvailableBaseClassifiers(); foreach ($baseClassifiers as $base => $params) { @@ -119,7 +119,7 @@ private function getData($input) } shuffle($populated); $targets = array_column($populated, 4); - array_walk($populated, function (&$v) { + array_walk($populated, function (&$v): void { array_splice($v, 4, 1); }); diff --git a/tests/Phpml/Classification/Ensemble/RandomForestTest.php b/tests/Phpml/Classification/Ensemble/RandomForestTest.php index 3ff51e07..e4ca9e50 100644 --- a/tests/Phpml/Classification/Ensemble/RandomForestTest.php +++ b/tests/Phpml/Classification/Ensemble/RandomForestTest.php @@ -23,7 +23,7 @@ protected function getAvailableBaseClassifiers() return [DecisionTree::class => ['depth' => 5]]; } - public function testOtherBaseClassifier() + public function testOtherBaseClassifier(): void { try { $classifier = new RandomForest(); diff --git a/tests/Phpml/Classification/KNearestNeighborsTest.php b/tests/Phpml/Classification/KNearestNeighborsTest.php index aeee5ef8..7ef61827 100644 --- a/tests/Phpml/Classification/KNearestNeighborsTest.php +++ b/tests/Phpml/Classification/KNearestNeighborsTest.php @@ -11,7 +11,7 @@ class KNearestNeighborsTest extends TestCase { - public function testPredictSingleSampleWithDefaultK() + public function testPredictSingleSampleWithDefaultK(): void { $samples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; $labels = ['a', 'a', 'a', 'b', 'b', 'b']; @@ -30,7 +30,7 @@ public function testPredictSingleSampleWithDefaultK() $this->assertEquals('a', $classifier->predict([3, 10])); } - public function testPredictArrayOfSamples() + public function testPredictArrayOfSamples(): void { $trainSamples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; $trainLabels = ['a', 'a', 'a', 'b', 'b', 'b']; @@ -45,7 +45,7 @@ public function testPredictArrayOfSamples() $this->assertEquals($testLabels, $predicted); } - public function testPredictArrayOfSamplesUsingChebyshevDistanceMetric() + public function testPredictArrayOfSamplesUsingChebyshevDistanceMetric(): void { $trainSamples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; $trainLabels = ['a', 'a', 'a', 'b', 'b', 'b']; @@ -60,7 +60,7 @@ public function testPredictArrayOfSamplesUsingChebyshevDistanceMetric() $this->assertEquals($testLabels, $predicted); } - public function testSaveAndRestore() + public function testSaveAndRestore(): void { $trainSamples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; $trainLabels = ['a', 'a', 'a', 'b', 'b', 'b']; diff --git a/tests/Phpml/Classification/Linear/AdalineTest.php b/tests/Phpml/Classification/Linear/AdalineTest.php index 59894626..46f1a346 100644 --- a/tests/Phpml/Classification/Linear/AdalineTest.php +++ b/tests/Phpml/Classification/Linear/AdalineTest.php @@ -10,7 +10,7 @@ class AdalineTest extends TestCase { - public function testPredictSingleSample() + public function testPredictSingleSample(): void { // AND problem $samples = [[0, 0], [1, 0], [0, 1], [1, 1]]; @@ -64,7 +64,7 @@ public function testPredictSingleSample() $this->assertEquals(1, $classifier->predict([3.0, 9.5])); } - public function testSaveAndRestore() + public function testSaveAndRestore(): void { // Instantinate new Percetron trained for OR problem $samples = [[0, 0], [1, 0], [0, 1], [1, 1]]; diff --git a/tests/Phpml/Classification/Linear/DecisionStumpTest.php b/tests/Phpml/Classification/Linear/DecisionStumpTest.php index 678544ea..fa522ada 100644 --- a/tests/Phpml/Classification/Linear/DecisionStumpTest.php +++ b/tests/Phpml/Classification/Linear/DecisionStumpTest.php @@ -53,7 +53,7 @@ public function testPredictSingleSample() return $classifier; } - public function testSaveAndRestore() + public function testSaveAndRestore(): void { // Instantinate new Percetron trained for OR problem $samples = [[0, 0], [1, 0], [0, 1], [1, 1]]; diff --git a/tests/Phpml/Classification/Linear/PerceptronTest.php b/tests/Phpml/Classification/Linear/PerceptronTest.php index c01da336..27210672 100644 --- a/tests/Phpml/Classification/Linear/PerceptronTest.php +++ b/tests/Phpml/Classification/Linear/PerceptronTest.php @@ -10,7 +10,7 @@ class PerceptronTest extends TestCase { - public function testPredictSingleSample() + public function testPredictSingleSample(): void { // AND problem $samples = [[0, 0], [1, 0], [0, 1], [1, 1], [0.6, 0.6]]; @@ -67,7 +67,7 @@ public function testPredictSingleSample() $this->assertEquals(1, $classifier->predict([3.0, 9.5])); } - public function testSaveAndRestore() + public function testSaveAndRestore(): void { // Instantinate new Percetron trained for OR problem $samples = [[0, 0], [1, 0], [0, 1], [1, 1]]; diff --git a/tests/Phpml/Classification/MLPClassifierTest.php b/tests/Phpml/Classification/MLPClassifierTest.php index 1745bc62..9f9c9a92 100644 --- a/tests/Phpml/Classification/MLPClassifierTest.php +++ b/tests/Phpml/Classification/MLPClassifierTest.php @@ -11,7 +11,7 @@ class MLPClassifierTest extends TestCase { - public function testMLPClassifierLayersInitialization() + public function testMLPClassifierLayersInitialization(): void { $mlp = new MLPClassifier(2, [2], [0, 1]); @@ -32,7 +32,7 @@ public function testMLPClassifierLayersInitialization() $this->assertContainsOnly(Neuron::class, $layers[2]->getNodes()); } - public function testSynapsesGeneration() + public function testSynapsesGeneration(): void { $mlp = new MLPClassifier(2, [2], [0, 1]); $layers = $mlp->getLayers(); @@ -50,7 +50,7 @@ public function testSynapsesGeneration() } } - public function testBackpropagationLearning() + public function testBackpropagationLearning(): void { // Single layer 2 classes. $network = new MLPClassifier(2, [2], ['a', 'b']); @@ -65,7 +65,7 @@ public function testBackpropagationLearning() $this->assertEquals('b', $network->predict([0, 0])); } - public function testBackpropagationTrainingReset() + public function testBackpropagationTrainingReset(): void { // Single layer 2 classes. $network = new MLPClassifier(2, [2], ['a', 'b'], 1000); @@ -86,7 +86,7 @@ public function testBackpropagationTrainingReset() $this->assertEquals('a', $network->predict([0, 1])); } - public function testBackpropagationPartialTraining() + public function testBackpropagationPartialTraining(): void { // Single layer 2 classes. $network = new MLPClassifier(2, [2], ['a', 'b'], 1000); @@ -109,7 +109,7 @@ public function testBackpropagationPartialTraining() $this->assertEquals('b', $network->predict([0, 0])); } - public function testBackpropagationLearningMultilayer() + public function testBackpropagationLearningMultilayer(): void { // Multi-layer 2 classes. $network = new MLPClassifier(5, [3, 2], ['a', 'b']); @@ -124,7 +124,7 @@ public function testBackpropagationLearningMultilayer() $this->assertEquals('b', $network->predict([0, 0, 0, 0, 0])); } - public function testBackpropagationLearningMulticlass() + public function testBackpropagationLearningMulticlass(): void { // Multi-layer more than 2 classes. $network = new MLPClassifier(5, [3, 2], ['a', 'b', 4]); @@ -140,7 +140,7 @@ public function testBackpropagationLearningMulticlass() $this->assertEquals(4, $network->predict([0, 0, 0, 0, 0])); } - public function testSaveAndRestore() + public function testSaveAndRestore(): void { // Instantinate new Percetron trained for OR problem $samples = [[0, 0], [1, 0], [0, 1], [1, 1]]; @@ -163,7 +163,7 @@ public function testSaveAndRestore() /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnInvalidLayersNumber() + public function testThrowExceptionOnInvalidLayersNumber(): void { new MLPClassifier(2, [], [0, 1]); } @@ -171,7 +171,7 @@ public function testThrowExceptionOnInvalidLayersNumber() /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnInvalidPartialTrainingClasses() + public function testThrowExceptionOnInvalidPartialTrainingClasses(): void { $classifier = new MLPClassifier(2, [2], [0, 1]); $classifier->partialTrain( @@ -184,7 +184,7 @@ public function testThrowExceptionOnInvalidPartialTrainingClasses() /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnInvalidClassesNumber() + public function testThrowExceptionOnInvalidClassesNumber(): void { new MLPClassifier(2, [2], [0]); } diff --git a/tests/Phpml/Classification/NaiveBayesTest.php b/tests/Phpml/Classification/NaiveBayesTest.php index 82c69eb5..5b14f7e1 100644 --- a/tests/Phpml/Classification/NaiveBayesTest.php +++ b/tests/Phpml/Classification/NaiveBayesTest.php @@ -10,7 +10,7 @@ class NaiveBayesTest extends TestCase { - public function testPredictSingleSample() + public function testPredictSingleSample(): void { $samples = [[5, 1, 1], [1, 5, 1], [1, 1, 5]]; $labels = ['a', 'b', 'c']; @@ -23,7 +23,7 @@ public function testPredictSingleSample() $this->assertEquals('c', $classifier->predict([1, 1, 6])); } - public function testPredictArrayOfSamples() + public function testPredictArrayOfSamples(): void { $trainSamples = [[5, 1, 1], [1, 5, 1], [1, 1, 5]]; $trainLabels = ['a', 'b', 'c']; @@ -47,7 +47,7 @@ public function testPredictArrayOfSamples() $this->assertEquals($testLabels, $classifier->predict($testSamples)); } - public function testSaveAndRestore() + public function testSaveAndRestore(): void { $trainSamples = [[5, 1, 1], [1, 5, 1], [1, 1, 5]]; $trainLabels = ['a', 'b', 'c']; diff --git a/tests/Phpml/Classification/SVCTest.php b/tests/Phpml/Classification/SVCTest.php index 1b529d06..f7342970 100644 --- a/tests/Phpml/Classification/SVCTest.php +++ b/tests/Phpml/Classification/SVCTest.php @@ -11,7 +11,7 @@ class SVCTest extends TestCase { - public function testPredictSingleSampleWithLinearKernel() + public function testPredictSingleSampleWithLinearKernel(): void { $samples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; $labels = ['a', 'a', 'a', 'b', 'b', 'b']; @@ -30,7 +30,7 @@ public function testPredictSingleSampleWithLinearKernel() $this->assertEquals('a', $classifier->predict([3, 10])); } - public function testPredictArrayOfSamplesWithLinearKernel() + public function testPredictArrayOfSamplesWithLinearKernel(): void { $trainSamples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; $trainLabels = ['a', 'a', 'a', 'b', 'b', 'b']; @@ -45,7 +45,7 @@ public function testPredictArrayOfSamplesWithLinearKernel() $this->assertEquals($testLabels, $predictions); } - public function testSaveAndRestore() + public function testSaveAndRestore(): void { $trainSamples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; $trainLabels = ['a', 'a', 'a', 'b', 'b', 'b']; diff --git a/tests/Phpml/Clustering/DBSCANTest.php b/tests/Phpml/Clustering/DBSCANTest.php index a093b202..d8fb0fea 100644 --- a/tests/Phpml/Clustering/DBSCANTest.php +++ b/tests/Phpml/Clustering/DBSCANTest.php @@ -9,7 +9,7 @@ class DBSCANTest extends TestCase { - public function testDBSCANSamplesClustering() + public function testDBSCANSamplesClustering(): void { $samples = [[1, 1], [8, 7], [1, 2], [7, 8], [2, 1], [8, 9]]; $clustered = [ @@ -32,7 +32,7 @@ public function testDBSCANSamplesClustering() $this->assertEquals($clustered, $dbscan->cluster($samples)); } - public function testDBSCANSamplesClusteringAssociative() + public function testDBSCANSamplesClusteringAssociative(): void { $samples = ['a' => [1, 1], 'b' => [9, 9], 'c' => [1, 2], 'd' => [9, 8], 'e' => [7, 7], 'f' => [8, 7]]; $clustered = [ diff --git a/tests/Phpml/Clustering/FuzzyCMeansTest.php b/tests/Phpml/Clustering/FuzzyCMeansTest.php index 39f26438..5aed678c 100644 --- a/tests/Phpml/Clustering/FuzzyCMeansTest.php +++ b/tests/Phpml/Clustering/FuzzyCMeansTest.php @@ -25,7 +25,7 @@ public function testFCMSamplesClustering() return $fcm; } - public function testMembershipMatrix() + public function testMembershipMatrix(): void { $fcm = $this->testFCMSamplesClustering(); $clusterCount = 2; diff --git a/tests/Phpml/Clustering/KMeansTest.php b/tests/Phpml/Clustering/KMeansTest.php index 43d41ee8..e665c9fc 100644 --- a/tests/Phpml/Clustering/KMeansTest.php +++ b/tests/Phpml/Clustering/KMeansTest.php @@ -9,7 +9,7 @@ class KMeansTest extends TestCase { - public function testKMeansSamplesClustering() + public function testKMeansSamplesClustering(): void { $samples = [[1, 1], [8, 7], [1, 2], [7, 8], [2, 1], [8, 9]]; @@ -26,7 +26,7 @@ public function testKMeansSamplesClustering() $this->assertCount(0, $samples); } - public function testKMeansInitializationMethods() + public function testKMeansInitializationMethods(): void { $samples = [ [180, 155], [186, 159], [119, 185], [141, 147], [157, 158], @@ -53,7 +53,7 @@ public function testKMeansInitializationMethods() /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnInvalidClusterNumber() + public function testThrowExceptionOnInvalidClusterNumber(): void { new KMeans(0); } diff --git a/tests/Phpml/CrossValidation/RandomSplitTest.php b/tests/Phpml/CrossValidation/RandomSplitTest.php index b2717fd7..070e36bc 100644 --- a/tests/Phpml/CrossValidation/RandomSplitTest.php +++ b/tests/Phpml/CrossValidation/RandomSplitTest.php @@ -13,7 +13,7 @@ class RandomSplitTest extends TestCase /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnToSmallTestSize() + public function testThrowExceptionOnToSmallTestSize(): void { new RandomSplit(new ArrayDataset([], []), 0); } @@ -21,12 +21,12 @@ public function testThrowExceptionOnToSmallTestSize() /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnToBigTestSize() + public function testThrowExceptionOnToBigTestSize(): void { new RandomSplit(new ArrayDataset([], []), 1); } - public function testDatasetRandomSplitWithoutSeed() + public function testDatasetRandomSplitWithoutSeed(): void { $dataset = new ArrayDataset( $samples = [[1], [2], [3], [4]], @@ -44,7 +44,7 @@ public function testDatasetRandomSplitWithoutSeed() $this->assertCount(3, $randomSplit2->getTrainSamples()); } - public function testDatasetRandomSplitWithSameSeed() + public function testDatasetRandomSplitWithSameSeed(): void { $dataset = new ArrayDataset( $samples = [[1], [2], [3], [4], [5], [6], [7], [8]], @@ -62,7 +62,7 @@ public function testDatasetRandomSplitWithSameSeed() $this->assertEquals($randomSplit1->getTrainSamples(), $randomSplit2->getTrainSamples()); } - public function testDatasetRandomSplitWithDifferentSeed() + public function testDatasetRandomSplitWithDifferentSeed(): void { $dataset = new ArrayDataset( $samples = [[1], [2], [3], [4], [5], [6], [7], [8]], @@ -78,7 +78,7 @@ public function testDatasetRandomSplitWithDifferentSeed() $this->assertNotEquals($randomSplit1->getTrainSamples(), $randomSplit2->getTrainSamples()); } - public function testRandomSplitCorrectSampleAndLabelPosition() + public function testRandomSplitCorrectSampleAndLabelPosition(): void { $dataset = new ArrayDataset( $samples = [[1], [2], [3], [4]], diff --git a/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php b/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php index 57023d38..e953ca7f 100644 --- a/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php +++ b/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php @@ -10,7 +10,7 @@ class StratifiedRandomSplitTest extends TestCase { - public function testDatasetStratifiedRandomSplitWithEvenDistribution() + public function testDatasetStratifiedRandomSplitWithEvenDistribution(): void { $dataset = new ArrayDataset( $samples = [[1], [2], [3], [4], [5], [6], [7], [8]], @@ -28,7 +28,7 @@ public function testDatasetStratifiedRandomSplitWithEvenDistribution() $this->assertEquals(1, $this->countSamplesByTarget($split->getTestLabels(), 'b')); } - public function testDatasetStratifiedRandomSplitWithEvenDistributionAndNumericTargets() + public function testDatasetStratifiedRandomSplitWithEvenDistributionAndNumericTargets(): void { $dataset = new ArrayDataset( $samples = [[1], [2], [3], [4], [5], [6], [7], [8]], diff --git a/tests/Phpml/Dataset/ArrayDatasetTest.php b/tests/Phpml/Dataset/ArrayDatasetTest.php index 0b13b51d..41e037be 100644 --- a/tests/Phpml/Dataset/ArrayDatasetTest.php +++ b/tests/Phpml/Dataset/ArrayDatasetTest.php @@ -12,12 +12,12 @@ class ArrayDatasetTest extends TestCase /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnInvalidArgumentsSize() + public function testThrowExceptionOnInvalidArgumentsSize(): void { new ArrayDataset([0, 1], [0]); } - public function testArrayDataset() + public function testArrayDataset(): void { $dataset = new ArrayDataset( $samples = [[1], [2], [3], [4]], diff --git a/tests/Phpml/Dataset/CsvDatasetTest.php b/tests/Phpml/Dataset/CsvDatasetTest.php index a3a377e2..f5cc8518 100644 --- a/tests/Phpml/Dataset/CsvDatasetTest.php +++ b/tests/Phpml/Dataset/CsvDatasetTest.php @@ -12,12 +12,12 @@ class CsvDatasetTest extends TestCase /** * @expectedException \Phpml\Exception\FileException */ - public function testThrowExceptionOnMissingFile() + public function testThrowExceptionOnMissingFile(): void { new CsvDataset('missingFile', 3); } - public function testSampleCsvDatasetWithHeaderRow() + public function testSampleCsvDatasetWithHeaderRow(): void { $filePath = dirname(__FILE__).'/Resources/dataset.csv'; @@ -27,7 +27,7 @@ public function testSampleCsvDatasetWithHeaderRow() $this->assertCount(10, $dataset->getTargets()); } - public function testSampleCsvDatasetWithoutHeaderRow() + public function testSampleCsvDatasetWithoutHeaderRow(): void { $filePath = dirname(__FILE__).'/Resources/dataset.csv'; @@ -37,7 +37,7 @@ public function testSampleCsvDatasetWithoutHeaderRow() $this->assertCount(11, $dataset->getTargets()); } - public function testLongCsvDataset() + public function testLongCsvDataset(): void { $filePath = dirname(__FILE__).'/Resources/longdataset.csv'; diff --git a/tests/Phpml/Dataset/Demo/GlassDatasetTest.php b/tests/Phpml/Dataset/Demo/GlassDatasetTest.php index 5e3f94ca..8feef452 100644 --- a/tests/Phpml/Dataset/Demo/GlassDatasetTest.php +++ b/tests/Phpml/Dataset/Demo/GlassDatasetTest.php @@ -9,7 +9,7 @@ class GlassDatasetTest extends TestCase { - public function testLoadingWineDataset() + public function testLoadingWineDataset(): void { $glass = new GlassDataset(); diff --git a/tests/Phpml/Dataset/Demo/IrisDatasetTest.php b/tests/Phpml/Dataset/Demo/IrisDatasetTest.php index 18ad647a..faa48c67 100644 --- a/tests/Phpml/Dataset/Demo/IrisDatasetTest.php +++ b/tests/Phpml/Dataset/Demo/IrisDatasetTest.php @@ -9,7 +9,7 @@ class IrisDatasetTest extends TestCase { - public function testLoadingIrisDataset() + public function testLoadingIrisDataset(): void { $iris = new IrisDataset(); diff --git a/tests/Phpml/Dataset/Demo/WineDatasetTest.php b/tests/Phpml/Dataset/Demo/WineDatasetTest.php index a79ed8ce..e0324b41 100644 --- a/tests/Phpml/Dataset/Demo/WineDatasetTest.php +++ b/tests/Phpml/Dataset/Demo/WineDatasetTest.php @@ -9,7 +9,7 @@ class WineDatasetTest extends TestCase { - public function testLoadingWineDataset() + public function testLoadingWineDataset(): void { $wine = new WineDataset(); diff --git a/tests/Phpml/Dataset/FilesDatasetTest.php b/tests/Phpml/Dataset/FilesDatasetTest.php index d12477fa..0592d061 100644 --- a/tests/Phpml/Dataset/FilesDatasetTest.php +++ b/tests/Phpml/Dataset/FilesDatasetTest.php @@ -12,12 +12,12 @@ class FilesDatasetTest extends TestCase /** * @expectedException \Phpml\Exception\DatasetException */ - public function testThrowExceptionOnMissingRootFolder() + public function testThrowExceptionOnMissingRootFolder(): void { new FilesDataset('some/not/existed/path'); } - public function testLoadFilesDatasetWithBBCData() + public function testLoadFilesDatasetWithBBCData(): void { $rootPath = dirname(__FILE__).'/Resources/bbc'; diff --git a/tests/Phpml/DimensionReduction/KernelPCATest.php b/tests/Phpml/DimensionReduction/KernelPCATest.php index e586fc88..66b4622f 100644 --- a/tests/Phpml/DimensionReduction/KernelPCATest.php +++ b/tests/Phpml/DimensionReduction/KernelPCATest.php @@ -9,7 +9,7 @@ class KernelPCATest extends TestCase { - public function testKernelPCA() + public function testKernelPCA(): void { // Acceptable error $epsilon = 0.001; @@ -37,7 +37,7 @@ public function testKernelPCA() // Due to the fact that the sign of values can be flipped // during the calculation of eigenValues, we have to compare // absolute value of the values - array_map(function ($val1, $val2) use ($epsilon) { + array_map(function ($val1, $val2) use ($epsilon): void { $this->assertEquals(abs($val1), abs($val2), '', $epsilon); }, $transformed, $reducedData); diff --git a/tests/Phpml/DimensionReduction/LDATest.php b/tests/Phpml/DimensionReduction/LDATest.php index 4498ea5e..19124a00 100644 --- a/tests/Phpml/DimensionReduction/LDATest.php +++ b/tests/Phpml/DimensionReduction/LDATest.php @@ -10,7 +10,7 @@ class LDATest extends TestCase { - public function testLDA() + public function testLDA(): void { // Acceptable error $epsilon = 0.001; @@ -43,7 +43,7 @@ public function testLDA() $control = array_merge($control, array_slice($transformed, 0, 3)); $control = array_merge($control, array_slice($transformed, -3)); - $check = function ($row1, $row2) use ($epsilon) { + $check = function ($row1, $row2) use ($epsilon): void { // Due to the fact that the sign of values can be flipped // during the calculation of eigenValues, we have to compare // absolute value of the values diff --git a/tests/Phpml/DimensionReduction/PCATest.php b/tests/Phpml/DimensionReduction/PCATest.php index c26782af..0a60004e 100644 --- a/tests/Phpml/DimensionReduction/PCATest.php +++ b/tests/Phpml/DimensionReduction/PCATest.php @@ -9,7 +9,7 @@ class PCATest extends TestCase { - public function testPCA() + public function testPCA(): void { // Acceptable error $epsilon = 0.001; @@ -39,7 +39,7 @@ public function testPCA() // Due to the fact that the sign of values can be flipped // during the calculation of eigenValues, we have to compare // absolute value of the values - array_map(function ($val1, $val2) use ($epsilon) { + array_map(function ($val1, $val2) use ($epsilon): void { $this->assertEquals(abs($val1), abs($val2), '', $epsilon); }, $transformed, $reducedData); @@ -49,7 +49,7 @@ public function testPCA() $newRow = [[$transformed[$i]]]; $newRow2 = $pca->transform($row); - array_map(function ($val1, $val2) use ($epsilon) { + array_map(function ($val1, $val2) use ($epsilon): void { $this->assertEquals(abs($val1), abs($val2), '', $epsilon); }, $newRow, $newRow2); } diff --git a/tests/Phpml/FeatureExtraction/StopWordsTest.php b/tests/Phpml/FeatureExtraction/StopWordsTest.php index 49798604..4b715ef5 100644 --- a/tests/Phpml/FeatureExtraction/StopWordsTest.php +++ b/tests/Phpml/FeatureExtraction/StopWordsTest.php @@ -9,7 +9,7 @@ class StopWordsTest extends TestCase { - public function testCustomStopWords() + public function testCustomStopWords(): void { $stopWords = new StopWords(['lorem', 'ipsum', 'dolor']); @@ -25,12 +25,12 @@ public function testCustomStopWords() /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnInvalidLanguage() + public function testThrowExceptionOnInvalidLanguage(): void { StopWords::factory('Lorem'); } - public function testEnglishStopWords() + public function testEnglishStopWords(): void { $stopWords = StopWords::factory('English'); @@ -38,7 +38,7 @@ public function testEnglishStopWords() $this->assertFalse($stopWords->isStopWord('strategy')); } - public function testPolishStopWords() + public function testPolishStopWords(): void { $stopWords = StopWords::factory('Polish'); @@ -46,7 +46,7 @@ public function testPolishStopWords() $this->assertFalse($stopWords->isStopWord('transhumanizm')); } - public function testFrenchStopWords() + public function testFrenchStopWords(): void { $stopWords = StopWords::factory('French'); diff --git a/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php b/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php index 90aa107b..0e1adbc2 100644 --- a/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php +++ b/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php @@ -9,7 +9,7 @@ class TfIdfTransformerTest extends TestCase { - public function testTfIdfTransformation() + public function testTfIdfTransformation(): void { // https://en.wikipedia.org/wiki/Tf-idf diff --git a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php index a25d5a5a..3419a29f 100644 --- a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php +++ b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php @@ -11,7 +11,7 @@ class TokenCountVectorizerTest extends TestCase { - public function testTransformationWithWhitespaceTokenizer() + public function testTransformationWithWhitespaceTokenizer(): void { $samples = [ 'Lorem ipsum dolor sit amet dolor', @@ -47,7 +47,7 @@ public function testTransformationWithWhitespaceTokenizer() $this->assertSame($tokensCounts, $samples); } - public function testTransformationWithMinimumDocumentTokenCountFrequency() + public function testTransformationWithMinimumDocumentTokenCountFrequency(): void { // word at least in half samples $samples = [ @@ -100,7 +100,7 @@ public function testTransformationWithMinimumDocumentTokenCountFrequency() $this->assertSame($tokensCounts, $samples); } - public function testTransformationWithStopWords() + public function testTransformationWithStopWords(): void { $samples = [ 'Lorem ipsum dolor sit amet dolor', diff --git a/tests/Phpml/Math/ComparisonTest.php b/tests/Phpml/Math/ComparisonTest.php index 8fff3cb2..d31b1ae0 100644 --- a/tests/Phpml/Math/ComparisonTest.php +++ b/tests/Phpml/Math/ComparisonTest.php @@ -15,7 +15,7 @@ class ComparisonTest extends TestCase * * @dataProvider provideData */ - public function testResult($a, $b, string $operator, bool $expected) + public function testResult($a, $b, string $operator, bool $expected): void { $result = Comparison::compare($a, $b, $operator); @@ -26,7 +26,7 @@ public function testResult($a, $b, string $operator, bool $expected) * @expectedException \Phpml\Exception\InvalidArgumentException * @expectedExceptionMessage Invalid operator "~=" provided */ - public function testThrowExceptionWhenOperatorIsInvalid() + public function testThrowExceptionWhenOperatorIsInvalid(): void { Comparison::compare(1, 1, '~='); } diff --git a/tests/Phpml/Math/Distance/ChebyshevTest.php b/tests/Phpml/Math/Distance/ChebyshevTest.php index 8806c747..893c0007 100644 --- a/tests/Phpml/Math/Distance/ChebyshevTest.php +++ b/tests/Phpml/Math/Distance/ChebyshevTest.php @@ -14,7 +14,7 @@ class ChebyshevTest extends TestCase */ private $distanceMetric; - public function setUp() + public function setUp(): void { $this->distanceMetric = new Chebyshev(); } @@ -22,7 +22,7 @@ public function setUp() /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnInvalidArguments() + public function testThrowExceptionOnInvalidArguments(): void { $a = [0, 1, 2]; $b = [0, 2]; @@ -30,7 +30,7 @@ public function testThrowExceptionOnInvalidArguments() $this->distanceMetric->distance($a, $b); } - public function testCalculateDistanceForOneDimension() + public function testCalculateDistanceForOneDimension(): void { $a = [4]; $b = [2]; @@ -41,7 +41,7 @@ public function testCalculateDistanceForOneDimension() $this->assertEquals($expectedDistance, $actualDistance); } - public function testCalculateDistanceForTwoDimensions() + public function testCalculateDistanceForTwoDimensions(): void { $a = [4, 6]; $b = [2, 5]; @@ -52,7 +52,7 @@ public function testCalculateDistanceForTwoDimensions() $this->assertEquals($expectedDistance, $actualDistance); } - public function testCalculateDistanceForThreeDimensions() + public function testCalculateDistanceForThreeDimensions(): void { $a = [6, 10, 3]; $b = [2, 5, 5]; diff --git a/tests/Phpml/Math/Distance/EuclideanTest.php b/tests/Phpml/Math/Distance/EuclideanTest.php index 38cb6b75..03bf7f3c 100644 --- a/tests/Phpml/Math/Distance/EuclideanTest.php +++ b/tests/Phpml/Math/Distance/EuclideanTest.php @@ -14,7 +14,7 @@ class EuclideanTest extends TestCase */ private $distanceMetric; - public function setUp() + public function setUp(): void { $this->distanceMetric = new Euclidean(); } @@ -22,7 +22,7 @@ public function setUp() /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnInvalidArguments() + public function testThrowExceptionOnInvalidArguments(): void { $a = [0, 1, 2]; $b = [0, 2]; @@ -30,7 +30,7 @@ public function testThrowExceptionOnInvalidArguments() $this->distanceMetric->distance($a, $b); } - public function testCalculateDistanceForOneDimension() + public function testCalculateDistanceForOneDimension(): void { $a = [4]; $b = [2]; @@ -41,7 +41,7 @@ public function testCalculateDistanceForOneDimension() $this->assertEquals($expectedDistance, $actualDistance); } - public function testCalculateDistanceForTwoDimensions() + public function testCalculateDistanceForTwoDimensions(): void { $a = [4, 6]; $b = [2, 5]; @@ -52,7 +52,7 @@ public function testCalculateDistanceForTwoDimensions() $this->assertEquals($expectedDistance, $actualDistance); } - public function testCalculateDistanceForThreeDimensions() + public function testCalculateDistanceForThreeDimensions(): void { $a = [6, 10, 3]; $b = [2, 5, 5]; diff --git a/tests/Phpml/Math/Distance/ManhattanTest.php b/tests/Phpml/Math/Distance/ManhattanTest.php index 0e22a155..9c20edd1 100644 --- a/tests/Phpml/Math/Distance/ManhattanTest.php +++ b/tests/Phpml/Math/Distance/ManhattanTest.php @@ -14,7 +14,7 @@ class ManhattanTest extends TestCase */ private $distanceMetric; - public function setUp() + public function setUp(): void { $this->distanceMetric = new Manhattan(); } @@ -22,7 +22,7 @@ public function setUp() /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnInvalidArguments() + public function testThrowExceptionOnInvalidArguments(): void { $a = [0, 1, 2]; $b = [0, 2]; @@ -30,7 +30,7 @@ public function testThrowExceptionOnInvalidArguments() $this->distanceMetric->distance($a, $b); } - public function testCalculateDistanceForOneDimension() + public function testCalculateDistanceForOneDimension(): void { $a = [4]; $b = [2]; @@ -41,7 +41,7 @@ public function testCalculateDistanceForOneDimension() $this->assertEquals($expectedDistance, $actualDistance); } - public function testCalculateDistanceForTwoDimensions() + public function testCalculateDistanceForTwoDimensions(): void { $a = [4, 6]; $b = [2, 5]; @@ -52,7 +52,7 @@ public function testCalculateDistanceForTwoDimensions() $this->assertEquals($expectedDistance, $actualDistance); } - public function testCalculateDistanceForThreeDimensions() + public function testCalculateDistanceForThreeDimensions(): void { $a = [6, 10, 3]; $b = [2, 5, 5]; diff --git a/tests/Phpml/Math/Distance/MinkowskiTest.php b/tests/Phpml/Math/Distance/MinkowskiTest.php index d8931d0d..81ecd975 100644 --- a/tests/Phpml/Math/Distance/MinkowskiTest.php +++ b/tests/Phpml/Math/Distance/MinkowskiTest.php @@ -14,7 +14,7 @@ class MinkowskiTest extends TestCase */ private $distanceMetric; - public function setUp() + public function setUp(): void { $this->distanceMetric = new Minkowski(); } @@ -22,7 +22,7 @@ public function setUp() /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnInvalidArguments() + public function testThrowExceptionOnInvalidArguments(): void { $a = [0, 1, 2]; $b = [0, 2]; @@ -30,7 +30,7 @@ public function testThrowExceptionOnInvalidArguments() $this->distanceMetric->distance($a, $b); } - public function testCalculateDistanceForOneDimension() + public function testCalculateDistanceForOneDimension(): void { $a = [4]; $b = [2]; @@ -41,7 +41,7 @@ public function testCalculateDistanceForOneDimension() $this->assertEquals($expectedDistance, $actualDistance); } - public function testCalculateDistanceForTwoDimensions() + public function testCalculateDistanceForTwoDimensions(): void { $a = [4, 6]; $b = [2, 5]; @@ -52,7 +52,7 @@ public function testCalculateDistanceForTwoDimensions() $this->assertEquals($expectedDistance, $actualDistance, '', $delta = 0.001); } - public function testCalculateDistanceForThreeDimensions() + public function testCalculateDistanceForThreeDimensions(): void { $a = [6, 10, 3]; $b = [2, 5, 5]; @@ -63,7 +63,7 @@ public function testCalculateDistanceForThreeDimensions() $this->assertEquals($expectedDistance, $actualDistance, '', $delta = 0.001); } - public function testCalculateDistanceForThreeDimensionsWithDifferentLambda() + public function testCalculateDistanceForThreeDimensionsWithDifferentLambda(): void { $distanceMetric = new Minkowski($lambda = 5); diff --git a/tests/Phpml/Math/Kernel/RBFTest.php b/tests/Phpml/Math/Kernel/RBFTest.php index 80768274..3ed7017d 100644 --- a/tests/Phpml/Math/Kernel/RBFTest.php +++ b/tests/Phpml/Math/Kernel/RBFTest.php @@ -9,7 +9,7 @@ class RBFTest extends TestCase { - public function testComputeRBFKernelFunction() + public function testComputeRBFKernelFunction(): void { $rbf = new RBF($gamma = 0.001); diff --git a/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php b/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php index 1db2c66b..688874cd 100644 --- a/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php +++ b/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php @@ -10,7 +10,7 @@ class EigenDecompositionTest extends TestCase { - public function testSymmetricMatrixEigenPairs() + public function testSymmetricMatrixEigenPairs(): void { // Acceptable error $epsilon = 0.001; diff --git a/tests/Phpml/Math/MatrixTest.php b/tests/Phpml/Math/MatrixTest.php index 0285b618..cd9fff28 100644 --- a/tests/Phpml/Math/MatrixTest.php +++ b/tests/Phpml/Math/MatrixTest.php @@ -12,12 +12,12 @@ class MatrixTest extends TestCase /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnInvalidMatrixSupplied() + public function testThrowExceptionOnInvalidMatrixSupplied(): void { new Matrix([[1, 2], [3]]); } - public function testCreateMatrixFromFlatArray() + public function testCreateMatrixFromFlatArray(): void { $flatArray = [1, 2, 3, 4]; $matrix = Matrix::fromFlatArray($flatArray); @@ -32,7 +32,7 @@ public function testCreateMatrixFromFlatArray() /** * @expectedException \Phpml\Exception\MatrixException */ - public function testThrowExceptionOnInvalidColumnNumber() + public function testThrowExceptionOnInvalidColumnNumber(): void { $matrix = new Matrix([[1, 2, 3], [4, 5, 6]]); $matrix->getColumnValues(4); @@ -41,13 +41,13 @@ public function testThrowExceptionOnInvalidColumnNumber() /** * @expectedException \Phpml\Exception\MatrixException */ - public function testThrowExceptionOnGetDeterminantIfArrayIsNotSquare() + public function testThrowExceptionOnGetDeterminantIfArrayIsNotSquare(): void { $matrix = new Matrix([[1, 2, 3], [4, 5, 6]]); $matrix->getDeterminant(); } - public function testGetMatrixDeterminant() + public function testGetMatrixDeterminant(): void { //http://matrix.reshish.com/determinant.php $matrix = new Matrix([ @@ -68,7 +68,7 @@ public function testGetMatrixDeterminant() $this->assertEquals(1116.5035, $matrix->getDeterminant(), '', $delta = 0.0001); } - public function testMatrixTranspose() + public function testMatrixTranspose(): void { $matrix = new Matrix([ [3, 3, 3], @@ -88,7 +88,7 @@ public function testMatrixTranspose() /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnMultiplyWhenInconsistentMatrixSupplied() + public function testThrowExceptionOnMultiplyWhenInconsistentMatrixSupplied(): void { $matrix1 = new Matrix([[1, 2, 3], [4, 5, 6]]); $matrix2 = new Matrix([[3, 2, 1], [6, 5, 4]]); @@ -96,7 +96,7 @@ public function testThrowExceptionOnMultiplyWhenInconsistentMatrixSupplied() $matrix1->multiply($matrix2); } - public function testMatrixMultiplyByMatrix() + public function testMatrixMultiplyByMatrix(): void { $matrix1 = new Matrix([ [1, 2, 3], @@ -117,7 +117,7 @@ public function testMatrixMultiplyByMatrix() $this->assertEquals($product, $matrix1->multiply($matrix2)->toArray()); } - public function testDivideByScalar() + public function testDivideByScalar(): void { $matrix = new Matrix([ [4, 6, 8], @@ -135,7 +135,7 @@ public function testDivideByScalar() /** * @expectedException \Phpml\Exception\MatrixException */ - public function testThrowExceptionWhenInverseIfArrayIsNotSquare() + public function testThrowExceptionWhenInverseIfArrayIsNotSquare(): void { $matrix = new Matrix([[1, 2, 3], [4, 5, 6]]); $matrix->inverse(); @@ -144,7 +144,7 @@ public function testThrowExceptionWhenInverseIfArrayIsNotSquare() /** * @expectedException \Phpml\Exception\MatrixException */ - public function testThrowExceptionWhenInverseIfMatrixIsSingular() + public function testThrowExceptionWhenInverseIfMatrixIsSingular(): void { $matrix = new Matrix([ [0, 0, 0], @@ -155,7 +155,7 @@ public function testThrowExceptionWhenInverseIfMatrixIsSingular() $matrix->inverse(); } - public function testInverseMatrix() + public function testInverseMatrix(): void { //http://ncalculators.com/matrix/inverse-matrix.htm $matrix = new Matrix([ @@ -173,7 +173,7 @@ public function testInverseMatrix() $this->assertEquals($inverseMatrix, $matrix->inverse()->toArray(), '', $delta = 0.0001); } - public function testCrossOutMatrix() + public function testCrossOutMatrix(): void { $matrix = new Matrix([ [3, 4, 2], @@ -189,14 +189,14 @@ public function testCrossOutMatrix() $this->assertEquals($crossOuted, $matrix->crossOut(1, 1)->toArray()); } - public function testToScalar() + public function testToScalar(): void { $matrix = new Matrix([[1, 2, 3], [3, 2, 3]]); $this->assertEquals($matrix->toScalar(), 1); } - public function testMultiplyByScalar() + public function testMultiplyByScalar(): void { $matrix = new Matrix([ [4, 6, 8], @@ -211,7 +211,7 @@ public function testMultiplyByScalar() $this->assertEquals($result, $matrix->multiplyByScalar(-2)->toArray()); } - public function testAdd() + public function testAdd(): void { $array1 = [1, 1, 1]; $array2 = [2, 2, 2]; @@ -223,7 +223,7 @@ public function testAdd() $this->assertEquals($result, $m1->add($m2)->toArray()[0]); } - public function testSubtract() + public function testSubtract(): void { $array1 = [1, 1, 1]; $array2 = [2, 2, 2]; @@ -235,7 +235,7 @@ public function testSubtract() $this->assertEquals($result, $m1->subtract($m2)->toArray()[0]); } - public function testTransposeArray() + public function testTransposeArray(): void { $array = [ [1, 1, 1], @@ -250,7 +250,7 @@ public function testTransposeArray() $this->assertEquals($transposed, Matrix::transposeArray($array)); } - public function testDot() + public function testDot(): void { $vect1 = [2, 2, 2]; $vect2 = [3, 3, 3]; diff --git a/tests/Phpml/Math/ProductTest.php b/tests/Phpml/Math/ProductTest.php index 50ced9ee..9c3a4b4c 100644 --- a/tests/Phpml/Math/ProductTest.php +++ b/tests/Phpml/Math/ProductTest.php @@ -9,7 +9,7 @@ class ProductTest extends TestCase { - public function testScalarProduct() + public function testScalarProduct(): void { $this->assertEquals(10, Product::scalar([2, 3], [-1, 4])); $this->assertEquals(-0.1, Product::scalar([1, 4, 1], [-2, 0.5, -0.1])); diff --git a/tests/Phpml/Math/SetTest.php b/tests/Phpml/Math/SetTest.php index 101763c5..a572a42a 100644 --- a/tests/Phpml/Math/SetTest.php +++ b/tests/Phpml/Math/SetTest.php @@ -9,7 +9,7 @@ class SetTest extends TestCase { - public function testUnion() + public function testUnion(): void { $union = Set::union(new Set([3, 1]), new Set([3, 2, 2])); @@ -18,7 +18,7 @@ public function testUnion() $this->assertEquals(3, $union->cardinality()); } - public function testIntersection() + public function testIntersection(): void { $intersection = Set::intersection(new Set(['C', 'A']), new Set(['B', 'C'])); @@ -27,7 +27,7 @@ public function testIntersection() $this->assertEquals(1, $intersection->cardinality()); } - public function testDifference() + public function testDifference(): void { $difference = Set::difference(new Set(['C', 'A', 'B']), new Set(['A'])); @@ -36,7 +36,7 @@ public function testDifference() $this->assertEquals(2, $difference->cardinality()); } - public function testPower() + public function testPower(): void { $power = Set::power(new Set(['A', 'B'])); @@ -45,7 +45,7 @@ public function testPower() $this->assertCount(4, $power); } - public function testCartesian() + public function testCartesian(): void { $cartesian = Set::cartesian(new Set(['A']), new Set([1, 2])); @@ -54,7 +54,7 @@ public function testCartesian() $this->assertCount(2, $cartesian); } - public function testContains() + public function testContains(): void { $set = new Set(['B', 'A', 2, 1]); @@ -65,21 +65,21 @@ public function testContains() $this->assertFalse($set->containsAll(['A', 'B', 'C'])); } - public function testRemove() + public function testRemove(): void { $set = new Set(['B', 'A', 2, 1]); $this->assertEquals((new Set([1, 2, 2, 2, 'B']))->toArray(), $set->remove('A')->toArray()); } - public function testAdd() + public function testAdd(): void { $set = new Set(['B', 'A', 2, 1]); $set->addAll(['foo', 'bar']); $this->assertEquals(6, $set->cardinality()); } - public function testEmpty() + public function testEmpty(): void { $set = new Set([1, 2]); $set->removeAll([2, 1]); @@ -87,7 +87,7 @@ public function testEmpty() $this->assertTrue($set->isEmpty()); } - public function testToArray() + public function testToArray(): void { $set = new Set([1, 2, 2, 3, 'A', false, '', 1.1, -1, -10, 'B']); diff --git a/tests/Phpml/Math/Statistic/CorrelationTest.php b/tests/Phpml/Math/Statistic/CorrelationTest.php index dd01b8b6..7fd2cec0 100644 --- a/tests/Phpml/Math/Statistic/CorrelationTest.php +++ b/tests/Phpml/Math/Statistic/CorrelationTest.php @@ -9,7 +9,7 @@ class CorrelationTest extends TestCase { - public function testPearsonCorrelation() + public function testPearsonCorrelation(): void { //http://www.stat.wmich.edu/s216/book/node126.html $delta = 0.001; @@ -32,7 +32,7 @@ public function testPearsonCorrelation() /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnInvalidArgumentsForPearsonCorrelation() + public function testThrowExceptionOnInvalidArgumentsForPearsonCorrelation(): void { Correlation::pearson([1, 2, 4], [3, 5]); } diff --git a/tests/Phpml/Math/Statistic/CovarianceTest.php b/tests/Phpml/Math/Statistic/CovarianceTest.php index d2abe7b5..3a8c9d38 100644 --- a/tests/Phpml/Math/Statistic/CovarianceTest.php +++ b/tests/Phpml/Math/Statistic/CovarianceTest.php @@ -10,7 +10,7 @@ class CovarianceTest extends TestCase { - public function testSimpleCovariance() + public function testSimpleCovariance(): void { // Acceptable error $epsilon = 0.001; diff --git a/tests/Phpml/Math/Statistic/GaussianTest.php b/tests/Phpml/Math/Statistic/GaussianTest.php index a0c9700f..8030f859 100644 --- a/tests/Phpml/Math/Statistic/GaussianTest.php +++ b/tests/Phpml/Math/Statistic/GaussianTest.php @@ -9,7 +9,7 @@ class GaussianTest extends TestCase { - public function testPdf() + public function testPdf(): void { $std = 1.0; $mean = 0.0; diff --git a/tests/Phpml/Math/Statistic/MeanTest.php b/tests/Phpml/Math/Statistic/MeanTest.php index 8f6a8fe5..86553e06 100644 --- a/tests/Phpml/Math/Statistic/MeanTest.php +++ b/tests/Phpml/Math/Statistic/MeanTest.php @@ -12,12 +12,12 @@ class MeanTest extends TestCase /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testArithmeticThrowExceptionOnEmptyArray() + public function testArithmeticThrowExceptionOnEmptyArray(): void { Mean::arithmetic([]); } - public function testArithmeticMean() + public function testArithmeticMean(): void { $delta = 0.01; $this->assertEquals(3.5, Mean::arithmetic([2, 5]), '', $delta); @@ -28,19 +28,19 @@ public function testArithmeticMean() /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testMedianThrowExceptionOnEmptyArray() + public function testMedianThrowExceptionOnEmptyArray(): void { Mean::median([]); } - public function testMedianOnOddLengthArray() + public function testMedianOnOddLengthArray(): void { $numbers = [5, 2, 6, 1, 3]; $this->assertEquals(3, Mean::median($numbers)); } - public function testMedianOnEvenLengthArray() + public function testMedianOnEvenLengthArray(): void { $numbers = [5, 2, 6, 1, 3, 4]; @@ -50,12 +50,12 @@ public function testMedianOnEvenLengthArray() /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testModeThrowExceptionOnEmptyArray() + public function testModeThrowExceptionOnEmptyArray(): void { Mean::mode([]); } - public function testModeOnArray() + public function testModeOnArray(): void { $numbers = [5, 2, 6, 1, 3, 4, 6, 6, 5]; diff --git a/tests/Phpml/Math/Statistic/StandardDeviationTest.php b/tests/Phpml/Math/Statistic/StandardDeviationTest.php index 35850c40..ead67b5d 100644 --- a/tests/Phpml/Math/Statistic/StandardDeviationTest.php +++ b/tests/Phpml/Math/Statistic/StandardDeviationTest.php @@ -9,7 +9,7 @@ class StandardDeviationTest extends TestCase { - public function testStandardDeviationOfPopulationSample() + public function testStandardDeviationOfPopulationSample(): void { //https://pl.wikipedia.org/wiki/Odchylenie_standardowe $delta = 0.001; @@ -28,7 +28,7 @@ public function testStandardDeviationOfPopulationSample() /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnEmptyArrayIfNotSample() + public function testThrowExceptionOnEmptyArrayIfNotSample(): void { StandardDeviation::population([], false); } @@ -36,7 +36,7 @@ public function testThrowExceptionOnEmptyArrayIfNotSample() /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnToSmallArray() + public function testThrowExceptionOnToSmallArray(): void { StandardDeviation::population([1]); } diff --git a/tests/Phpml/Metric/AccuracyTest.php b/tests/Phpml/Metric/AccuracyTest.php index 02876e0d..885893d6 100644 --- a/tests/Phpml/Metric/AccuracyTest.php +++ b/tests/Phpml/Metric/AccuracyTest.php @@ -16,7 +16,7 @@ class AccuracyTest extends TestCase /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnInvalidArguments() + public function testThrowExceptionOnInvalidArguments(): void { $actualLabels = ['a', 'b', 'a', 'b']; $predictedLabels = ['a', 'a']; @@ -24,7 +24,7 @@ public function testThrowExceptionOnInvalidArguments() Accuracy::score($actualLabels, $predictedLabels); } - public function testCalculateNormalizedScore() + public function testCalculateNormalizedScore(): void { $actualLabels = ['a', 'b', 'a', 'b']; $predictedLabels = ['a', 'a', 'b', 'b']; @@ -32,7 +32,7 @@ public function testCalculateNormalizedScore() $this->assertEquals(0.5, Accuracy::score($actualLabels, $predictedLabels)); } - public function testCalculateNotNormalizedScore() + public function testCalculateNotNormalizedScore(): void { $actualLabels = ['a', 'b', 'a', 'b']; $predictedLabels = ['a', 'b', 'b', 'b']; @@ -40,7 +40,7 @@ public function testCalculateNotNormalizedScore() $this->assertEquals(3, Accuracy::score($actualLabels, $predictedLabels, false)); } - public function testAccuracyOnDemoDataset() + public function testAccuracyOnDemoDataset(): void { $dataset = new RandomSplit(new IrisDataset(), 0.5, 123); diff --git a/tests/Phpml/Metric/ClassificationReportTest.php b/tests/Phpml/Metric/ClassificationReportTest.php index 7b142cf7..b7ff02d3 100644 --- a/tests/Phpml/Metric/ClassificationReportTest.php +++ b/tests/Phpml/Metric/ClassificationReportTest.php @@ -9,7 +9,7 @@ class ClassificationReportTest extends TestCase { - public function testClassificationReportGenerateWithStringLabels() + public function testClassificationReportGenerateWithStringLabels(): void { $labels = ['cat', 'ant', 'bird', 'bird', 'bird']; $predicted = ['cat', 'cat', 'bird', 'bird', 'ant']; @@ -29,7 +29,7 @@ public function testClassificationReportGenerateWithStringLabels() $this->assertEquals($average, $report->getAverage(), '', 0.01); } - public function testClassificationReportGenerateWithNumericLabels() + public function testClassificationReportGenerateWithNumericLabels(): void { $labels = [0, 1, 2, 2, 2]; $predicted = [0, 0, 2, 2, 1]; @@ -49,7 +49,7 @@ public function testClassificationReportGenerateWithNumericLabels() $this->assertEquals($average, $report->getAverage(), '', 0.01); } - public function testPreventDivideByZeroWhenTruePositiveAndFalsePositiveSumEqualsZero() + public function testPreventDivideByZeroWhenTruePositiveAndFalsePositiveSumEqualsZero(): void { $labels = [1, 2]; $predicted = [2, 2]; @@ -59,7 +59,7 @@ public function testPreventDivideByZeroWhenTruePositiveAndFalsePositiveSumEquals $this->assertEquals([1 => 0.0, 2 => 0.5], $report->getPrecision(), '', 0.01); } - public function testPreventDivideByZeroWhenTruePositiveAndFalseNegativeSumEqualsZero() + public function testPreventDivideByZeroWhenTruePositiveAndFalseNegativeSumEqualsZero(): void { $labels = [2, 2, 1]; $predicted = [2, 2, 3]; @@ -69,7 +69,7 @@ public function testPreventDivideByZeroWhenTruePositiveAndFalseNegativeSumEquals $this->assertEquals([1 => 0.0, 2 => 1, 3 => 0], $report->getPrecision(), '', 0.01); } - public function testPreventDividedByZeroWhenPredictedLabelsAllNotMatch() + public function testPreventDividedByZeroWhenPredictedLabelsAllNotMatch(): void { $labels = [1, 2, 3, 4, 5]; $predicted = [2, 3, 4, 5, 6]; diff --git a/tests/Phpml/Metric/ConfusionMatrixTest.php b/tests/Phpml/Metric/ConfusionMatrixTest.php index 1fa31304..f958bdff 100644 --- a/tests/Phpml/Metric/ConfusionMatrixTest.php +++ b/tests/Phpml/Metric/ConfusionMatrixTest.php @@ -9,7 +9,7 @@ class ConfusionMatrixTest extends TestCase { - public function testComputeConfusionMatrixOnNumericLabels() + public function testComputeConfusionMatrixOnNumericLabels(): void { $actualLabels = [2, 0, 2, 2, 0, 1]; $predictedLabels = [0, 0, 2, 2, 0, 2]; @@ -23,7 +23,7 @@ public function testComputeConfusionMatrixOnNumericLabels() $this->assertEquals($confusionMatrix, ConfusionMatrix::compute($actualLabels, $predictedLabels)); } - public function testComputeConfusionMatrixOnStringLabels() + public function testComputeConfusionMatrixOnStringLabels(): void { $actualLabels = ['cat', 'ant', 'cat', 'cat', 'ant', 'bird']; $predictedLabels = ['ant', 'ant', 'cat', 'cat', 'ant', 'cat']; @@ -37,7 +37,7 @@ public function testComputeConfusionMatrixOnStringLabels() $this->assertEquals($confusionMatrix, ConfusionMatrix::compute($actualLabels, $predictedLabels)); } - public function testComputeConfusionMatrixOnLabelsWithSubset() + public function testComputeConfusionMatrixOnLabelsWithSubset(): void { $actualLabels = ['cat', 'ant', 'cat', 'cat', 'ant', 'bird']; $predictedLabels = ['ant', 'ant', 'cat', 'cat', 'ant', 'cat']; diff --git a/tests/Phpml/ModelManagerTest.php b/tests/Phpml/ModelManagerTest.php index 400b6d1b..e44a32ac 100644 --- a/tests/Phpml/ModelManagerTest.php +++ b/tests/Phpml/ModelManagerTest.php @@ -10,7 +10,7 @@ class ModelManagerTest extends TestCase { - public function testSaveAndRestore() + public function testSaveAndRestore(): void { $filename = uniqid(); $filepath = sys_get_temp_dir().DIRECTORY_SEPARATOR.$filename; @@ -26,7 +26,7 @@ public function testSaveAndRestore() /** * @expectedException \Phpml\Exception\FileException */ - public function testRestoreWrongFile() + public function testRestoreWrongFile(): void { $filepath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'unexisting'; $modelManager = new ModelManager(); diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php index a62b1c97..95e95cbb 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php @@ -12,7 +12,7 @@ class BinaryStepTest extends TestCase /** * @dataProvider binaryStepProvider */ - public function testBinaryStepActivationFunction($expected, $value) + public function testBinaryStepActivationFunction($expected, $value): void { $binaryStep = new BinaryStep(); diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php index 19b6cca3..f7af7c0d 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php @@ -12,7 +12,7 @@ class GaussianTest extends TestCase /** * @dataProvider gaussianProvider */ - public function testGaussianActivationFunction($expected, $value) + public function testGaussianActivationFunction($expected, $value): void { $gaussian = new Gaussian(); diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php index b27dfa0a..95f437f5 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php @@ -12,7 +12,7 @@ class HyperboliTangentTest extends TestCase /** * @dataProvider tanhProvider */ - public function testHyperbolicTangentActivationFunction($beta, $expected, $value) + public function testHyperbolicTangentActivationFunction($beta, $expected, $value): void { $tanh = new HyperbolicTangent($beta); diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php index 5e14ce88..873520ef 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php @@ -12,7 +12,7 @@ class PReLUTest extends TestCase /** * @dataProvider preluProvider */ - public function testPReLUActivationFunction($beta, $expected, $value) + public function testPReLUActivationFunction($beta, $expected, $value): void { $prelu = new PReLU($beta); diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php index 68940060..096a3769 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php @@ -12,7 +12,7 @@ class SigmoidTest extends TestCase /** * @dataProvider sigmoidProvider */ - public function testSigmoidActivationFunction($beta, $expected, $value) + public function testSigmoidActivationFunction($beta, $expected, $value): void { $sigmoid = new Sigmoid($beta); diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php index 571a1971..1800d7b0 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php @@ -12,7 +12,7 @@ class ThresholdedReLUTest extends TestCase /** * @dataProvider thresholdProvider */ - public function testThresholdedReLUActivationFunction($theta, $expected, $value) + public function testThresholdedReLUActivationFunction($theta, $expected, $value): void { $thresholdedReLU = new ThresholdedReLU($theta); diff --git a/tests/Phpml/NeuralNetwork/LayerTest.php b/tests/Phpml/NeuralNetwork/LayerTest.php index 4105b6f9..284b1ebf 100644 --- a/tests/Phpml/NeuralNetwork/LayerTest.php +++ b/tests/Phpml/NeuralNetwork/LayerTest.php @@ -11,14 +11,14 @@ class LayerTest extends TestCase { - public function testLayerInitialization() + public function testLayerInitialization(): void { $layer = new Layer(); $this->assertEquals([], $layer->getNodes()); } - public function testLayerInitializationWithDefaultNodesType() + public function testLayerInitializationWithDefaultNodesType(): void { $layer = new Layer($number = 5); @@ -28,7 +28,7 @@ public function testLayerInitializationWithDefaultNodesType() } } - public function testLayerInitializationWithExplicitNodesType() + public function testLayerInitializationWithExplicitNodesType(): void { $layer = new Layer($number = 5, $class = Bias::class); @@ -41,12 +41,12 @@ public function testLayerInitializationWithExplicitNodesType() /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnInvalidNodeClass() + public function testThrowExceptionOnInvalidNodeClass(): void { new Layer(1, \stdClass::class); } - public function testAddNodesToLayer() + public function testAddNodesToLayer(): void { $layer = new Layer(); $layer->addNode($node1 = new Neuron()); diff --git a/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php b/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php index 8b3f0e0f..48bee66e 100644 --- a/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php +++ b/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php @@ -11,7 +11,7 @@ class LayeredNetworkTest extends TestCase { - public function testLayersSettersAndGetters() + public function testLayersSettersAndGetters(): void { $network = $this->getLayeredNetworkMock(); @@ -21,7 +21,7 @@ public function testLayersSettersAndGetters() $this->assertEquals([$layer1, $layer2], $network->getLayers()); } - public function testGetLastLayerAsOutputLayer() + public function testGetLastLayerAsOutputLayer(): void { $network = $this->getLayeredNetworkMock(); $network->addLayer($layer1 = new Layer()); @@ -32,7 +32,7 @@ public function testGetLastLayerAsOutputLayer() $this->assertEquals($layer2, $network->getOutputLayer()); } - public function testSetInputAndGetOutput() + public function testSetInputAndGetOutput(): void { $network = $this->getLayeredNetworkMock(); $network->addLayer(new Layer(2, Input::class)); diff --git a/tests/Phpml/NeuralNetwork/Node/BiasTest.php b/tests/Phpml/NeuralNetwork/Node/BiasTest.php index 8f01d52f..ee311f5a 100644 --- a/tests/Phpml/NeuralNetwork/Node/BiasTest.php +++ b/tests/Phpml/NeuralNetwork/Node/BiasTest.php @@ -9,7 +9,7 @@ class BiasTest extends TestCase { - public function testBiasOutput() + public function testBiasOutput(): void { $bias = new Bias(); diff --git a/tests/Phpml/NeuralNetwork/Node/InputTest.php b/tests/Phpml/NeuralNetwork/Node/InputTest.php index 31d86c97..2d3be71f 100644 --- a/tests/Phpml/NeuralNetwork/Node/InputTest.php +++ b/tests/Phpml/NeuralNetwork/Node/InputTest.php @@ -9,7 +9,7 @@ class InputTest extends TestCase { - public function testInputInitialization() + public function testInputInitialization(): void { $input = new Input(); $this->assertEquals(0.0, $input->getOutput()); @@ -18,7 +18,7 @@ public function testInputInitialization() $this->assertEquals($value, $input->getOutput()); } - public function testSetInput() + public function testSetInput(): void { $input = new Input(); $input->setInput($value = 6.9); diff --git a/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php b/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php index 126d70db..02d6dfa2 100644 --- a/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php +++ b/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php @@ -10,7 +10,7 @@ class SynapseTest extends TestCase { - public function testSynapseInitialization() + public function testSynapseInitialization(): void { $node = $this->getNodeMock($nodeOutput = 0.5); @@ -25,7 +25,7 @@ public function testSynapseInitialization() $this->assertInternalType('float', $synapse->getWeight()); } - public function testSynapseWeightChange() + public function testSynapseWeightChange(): void { $node = $this->getNodeMock(); $synapse = new Synapse($node, $weight = 0.75); diff --git a/tests/Phpml/NeuralNetwork/Node/NeuronTest.php b/tests/Phpml/NeuralNetwork/Node/NeuronTest.php index a7bc6fe5..89f1ca1b 100644 --- a/tests/Phpml/NeuralNetwork/Node/NeuronTest.php +++ b/tests/Phpml/NeuralNetwork/Node/NeuronTest.php @@ -11,7 +11,7 @@ class NeuronTest extends TestCase { - public function testNeuronInitialization() + public function testNeuronInitialization(): void { $neuron = new Neuron(); @@ -19,7 +19,7 @@ public function testNeuronInitialization() $this->assertEquals(0.5, $neuron->getOutput()); } - public function testNeuronActivationFunction() + public function testNeuronActivationFunction(): void { $activationFunction = $this->getMockBuilder(BinaryStep::class)->getMock(); $activationFunction->method('compute')->with(0)->willReturn($output = 0.69); @@ -29,7 +29,7 @@ public function testNeuronActivationFunction() $this->assertEquals($output, $neuron->getOutput()); } - public function testNeuronWithSynapse() + public function testNeuronWithSynapse(): void { $neuron = new Neuron(); $neuron->addSynapse($synapse = $this->getSynapseMock()); @@ -38,7 +38,7 @@ public function testNeuronWithSynapse() $this->assertEquals(0.88, $neuron->getOutput(), '', 0.01); } - public function testNeuronRefresh() + public function testNeuronRefresh(): void { $neuron = new Neuron(); $neuron->getOutput(); diff --git a/tests/Phpml/PipelineTest.php b/tests/Phpml/PipelineTest.php index 7fc35451..fc06e56f 100644 --- a/tests/Phpml/PipelineTest.php +++ b/tests/Phpml/PipelineTest.php @@ -17,7 +17,7 @@ class PipelineTest extends TestCase { - public function testPipelineConstruction() + public function testPipelineConstruction(): void { $transformers = [ new TfIdfTransformer(), @@ -30,7 +30,7 @@ public function testPipelineConstruction() $this->assertEquals($estimator, $pipeline->getEstimator()); } - public function testPipelineEstimatorSetter() + public function testPipelineEstimatorSetter(): void { $pipeline = new Pipeline([new TfIdfTransformer()], new SVC()); @@ -40,7 +40,7 @@ public function testPipelineEstimatorSetter() $this->assertEquals($estimator, $pipeline->getEstimator()); } - public function testPipelineWorkflow() + public function testPipelineWorkflow(): void { $transformers = [ new Imputer(null, new MostFrequentStrategy()), @@ -68,7 +68,7 @@ public function testPipelineWorkflow() $this->assertEquals(4, $predicted[0]); } - public function testPipelineTransformers() + public function testPipelineTransformers(): void { $transformers = [ new TokenCountVectorizer(new WordTokenizer()), diff --git a/tests/Phpml/Preprocessing/ImputerTest.php b/tests/Phpml/Preprocessing/ImputerTest.php index 2f5c31b2..658b4545 100644 --- a/tests/Phpml/Preprocessing/ImputerTest.php +++ b/tests/Phpml/Preprocessing/ImputerTest.php @@ -12,7 +12,7 @@ class ImputerTest extends TestCase { - public function testComplementsMissingValuesWithMeanStrategyOnColumnAxis() + public function testComplementsMissingValuesWithMeanStrategyOnColumnAxis(): void { $data = [ [1, null, 3, 4], @@ -34,7 +34,7 @@ public function testComplementsMissingValuesWithMeanStrategyOnColumnAxis() $this->assertEquals($imputeData, $data, '', $delta = 0.01); } - public function testComplementsMissingValuesWithMeanStrategyOnRowAxis() + public function testComplementsMissingValuesWithMeanStrategyOnRowAxis(): void { $data = [ [1, null, 3, 4], @@ -56,7 +56,7 @@ public function testComplementsMissingValuesWithMeanStrategyOnRowAxis() $this->assertEquals($imputeData, $data, '', $delta = 0.01); } - public function testComplementsMissingValuesWithMediaStrategyOnColumnAxis() + public function testComplementsMissingValuesWithMediaStrategyOnColumnAxis(): void { $data = [ [1, null, 3, 4], @@ -78,7 +78,7 @@ public function testComplementsMissingValuesWithMediaStrategyOnColumnAxis() $this->assertEquals($imputeData, $data, '', $delta = 0.01); } - public function testComplementsMissingValuesWithMediaStrategyOnRowAxis() + public function testComplementsMissingValuesWithMediaStrategyOnRowAxis(): void { $data = [ [1, null, 3, 4], @@ -100,7 +100,7 @@ public function testComplementsMissingValuesWithMediaStrategyOnRowAxis() $this->assertEquals($imputeData, $data, '', $delta = 0.01); } - public function testComplementsMissingValuesWithMostFrequentStrategyOnColumnAxis() + public function testComplementsMissingValuesWithMostFrequentStrategyOnColumnAxis(): void { $data = [ [1, null, 3, 4], @@ -124,7 +124,7 @@ public function testComplementsMissingValuesWithMostFrequentStrategyOnColumnAxis $this->assertEquals($imputeData, $data); } - public function testComplementsMissingValuesWithMostFrequentStrategyOnRowAxis() + public function testComplementsMissingValuesWithMostFrequentStrategyOnRowAxis(): void { $data = [ [1, null, 3, 4, 3], @@ -148,7 +148,7 @@ public function testComplementsMissingValuesWithMostFrequentStrategyOnRowAxis() $this->assertEquals($imputeData, $data); } - public function testImputerWorksOnFitSamples() + public function testImputerWorksOnFitSamples(): void { $trainData = [ [1, 3, 4], diff --git a/tests/Phpml/Preprocessing/NormalizerTest.php b/tests/Phpml/Preprocessing/NormalizerTest.php index 542eef57..22ed1bdb 100644 --- a/tests/Phpml/Preprocessing/NormalizerTest.php +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -12,12 +12,12 @@ class NormalizerTest extends TestCase /** * @expectedException \Phpml\Exception\NormalizerException */ - public function testThrowExceptionOnInvalidNorm() + public function testThrowExceptionOnInvalidNorm(): void { new Normalizer(99); } - public function testNormalizeSamplesWithL2Norm() + public function testNormalizeSamplesWithL2Norm(): void { $samples = [ [1, -1, 2], @@ -37,7 +37,7 @@ public function testNormalizeSamplesWithL2Norm() $this->assertEquals($normalized, $samples, '', $delta = 0.01); } - public function testNormalizeSamplesWithL1Norm() + public function testNormalizeSamplesWithL1Norm(): void { $samples = [ [1, -1, 2], @@ -57,7 +57,7 @@ public function testNormalizeSamplesWithL1Norm() $this->assertEquals($normalized, $samples, '', $delta = 0.01); } - public function testFitNotChangeNormalizerBehavior() + public function testFitNotChangeNormalizerBehavior(): void { $samples = [ [1, -1, 2], @@ -81,7 +81,7 @@ public function testFitNotChangeNormalizerBehavior() $this->assertEquals($normalized, $samples, '', $delta = 0.01); } - public function testL1NormWithZeroSumCondition() + public function testL1NormWithZeroSumCondition(): void { $samples = [ [0, 0, 0], @@ -101,7 +101,7 @@ public function testL1NormWithZeroSumCondition() $this->assertEquals($normalized, $samples, '', $delta = 0.01); } - public function testStandardNorm() + public function testStandardNorm(): void { // Generate 10 random vectors of length 3 $samples = []; diff --git a/tests/Phpml/Regression/LeastSquaresTest.php b/tests/Phpml/Regression/LeastSquaresTest.php index ac457a97..7d835a2d 100644 --- a/tests/Phpml/Regression/LeastSquaresTest.php +++ b/tests/Phpml/Regression/LeastSquaresTest.php @@ -10,7 +10,7 @@ class LeastSquaresTest extends TestCase { - public function testPredictSingleFeatureSamples() + public function testPredictSingleFeatureSamples(): void { $delta = 0.01; @@ -37,7 +37,7 @@ public function testPredictSingleFeatureSamples() $this->assertEquals(278.66, $regression->predict([153260]), '', $delta); } - public function testPredictSingleFeatureSamplesWithMatrixTargets() + public function testPredictSingleFeatureSamplesWithMatrixTargets(): void { $delta = 0.01; @@ -51,7 +51,7 @@ public function testPredictSingleFeatureSamplesWithMatrixTargets() $this->assertEquals(4.06, $regression->predict([64]), '', $delta); } - public function testPredictMultiFeaturesSamples() + public function testPredictMultiFeaturesSamples(): void { $delta = 0.01; @@ -68,7 +68,7 @@ public function testPredictMultiFeaturesSamples() $this->assertEquals(5711.40, $regression->predict([60000, 2000]), '', $delta); } - public function testSaveAndRestore() + public function testSaveAndRestore(): void { //https://www.easycalculation.com/analytical/learn-least-square-regression.php $samples = [[60], [61], [62], [63], [65]]; diff --git a/tests/Phpml/Regression/SVRTest.php b/tests/Phpml/Regression/SVRTest.php index 4ea19a3f..3cd0ee55 100644 --- a/tests/Phpml/Regression/SVRTest.php +++ b/tests/Phpml/Regression/SVRTest.php @@ -11,7 +11,7 @@ class SVRTest extends TestCase { - public function testPredictSingleFeatureSamples() + public function testPredictSingleFeatureSamples(): void { $delta = 0.01; @@ -24,7 +24,7 @@ public function testPredictSingleFeatureSamples() $this->assertEquals(4.03, $regression->predict([64]), '', $delta); } - public function testPredictMultiFeaturesSamples() + public function testPredictMultiFeaturesSamples(): void { $delta = 0.01; @@ -37,7 +37,7 @@ public function testPredictMultiFeaturesSamples() $this->assertEquals([4109.82, 4112.28], $regression->predict([[60000, 1996], [60000, 2000]]), '', $delta); } - public function testSaveAndRestore() + public function testSaveAndRestore(): void { $samples = [[60], [61], [62], [63], [65]]; $targets = [3.1, 3.6, 3.8, 4, 4.1]; diff --git a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php b/tests/Phpml/SupportVectorMachine/DataTransformerTest.php index 9aa15584..b1f85229 100644 --- a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php +++ b/tests/Phpml/SupportVectorMachine/DataTransformerTest.php @@ -9,7 +9,7 @@ class DataTransformerTest extends TestCase { - public function testTransformDatasetToTrainingSet() + public function testTransformDatasetToTrainingSet(): void { $samples = [[1, 1], [2, 1], [3, 2], [4, 5]]; $labels = ['a', 'a', 'b', 'b']; @@ -24,7 +24,7 @@ public function testTransformDatasetToTrainingSet() $this->assertEquals($trainingSet, DataTransformer::trainingSet($samples, $labels)); } - public function testTransformSamplesToTestSet() + public function testTransformSamplesToTestSet(): void { $samples = [[1, 1], [2, 1], [3, 2], [4, 5]]; diff --git a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php index 4cc6d577..ebbf99f2 100644 --- a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php @@ -11,7 +11,7 @@ class SupportVectorMachineTest extends TestCase { - public function testTrainCSVCModelWithLinearKernel() + public function testTrainCSVCModelWithLinearKernel(): void { $samples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; $labels = ['a', 'a', 'a', 'b', 'b', 'b']; @@ -35,7 +35,7 @@ public function testTrainCSVCModelWithLinearKernel() $this->assertEquals($model, $svm->getModel()); } - public function testPredictSampleWithLinearKernel() + public function testPredictSampleWithLinearKernel(): void { $samples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; $labels = ['a', 'a', 'a', 'b', 'b', 'b']; @@ -54,7 +54,7 @@ public function testPredictSampleWithLinearKernel() $this->assertEquals('b', $predictions[2]); } - public function testPredictSampleFromMultipleClassWithRbfKernel() + public function testPredictSampleFromMultipleClassWithRbfKernel(): void { $samples = [ [1, 3], [1, 4], [1, 4], @@ -85,7 +85,7 @@ public function testPredictSampleFromMultipleClassWithRbfKernel() * @expectedException \Phpml\Exception\InvalidArgumentException * @expectedExceptionMessage is not writable */ - public function testThrowExceptionWhenVarPathIsNotWritable() + public function testThrowExceptionWhenVarPathIsNotWritable(): void { $svm = new SupportVectorMachine(Type::C_SVC, Kernel::RBF); $svm->setVarPath('var-path'); @@ -95,7 +95,7 @@ public function testThrowExceptionWhenVarPathIsNotWritable() * @expectedException \Phpml\Exception\InvalidArgumentException * @expectedExceptionMessage does not exist */ - public function testThrowExceptionWhenBinPathDoesNotExist() + public function testThrowExceptionWhenBinPathDoesNotExist(): void { $svm = new SupportVectorMachine(Type::C_SVC, Kernel::RBF); $svm->setBinPath('bin-path'); @@ -105,7 +105,7 @@ public function testThrowExceptionWhenBinPathDoesNotExist() * @expectedException \Phpml\Exception\InvalidArgumentException * @expectedExceptionMessage not found */ - public function testThrowExceptionWhenFileIsNotFoundInBinPath() + public function testThrowExceptionWhenFileIsNotFoundInBinPath(): void { $svm = new SupportVectorMachine(Type::C_SVC, Kernel::RBF); $svm->setBinPath('var'); diff --git a/tests/Phpml/Tokenization/WhitespaceTokenizerTest.php b/tests/Phpml/Tokenization/WhitespaceTokenizerTest.php index 97fa8330..f9d7c731 100644 --- a/tests/Phpml/Tokenization/WhitespaceTokenizerTest.php +++ b/tests/Phpml/Tokenization/WhitespaceTokenizerTest.php @@ -9,7 +9,7 @@ class WhitespaceTokenizerTest extends TestCase { - public function testTokenizationOnAscii() + public function testTokenizationOnAscii(): void { $tokenizer = new WhitespaceTokenizer(); @@ -24,7 +24,7 @@ public function testTokenizationOnAscii() $this->assertEquals($tokens, $tokenizer->tokenize($text)); } - public function testTokenizationOnUtf8() + public function testTokenizationOnUtf8(): void { $tokenizer = new WhitespaceTokenizer(); diff --git a/tests/Phpml/Tokenization/WordTokenizerTest.php b/tests/Phpml/Tokenization/WordTokenizerTest.php index 09db2225..607c327f 100644 --- a/tests/Phpml/Tokenization/WordTokenizerTest.php +++ b/tests/Phpml/Tokenization/WordTokenizerTest.php @@ -9,7 +9,7 @@ class WordTokenizerTest extends TestCase { - public function testTokenizationOnAscii() + public function testTokenizationOnAscii(): void { $tokenizer = new WordTokenizer(); @@ -24,7 +24,7 @@ public function testTokenizationOnAscii() $this->assertEquals($tokens, $tokenizer->tokenize($text)); } - public function testTokenizationOnUtf8() + public function testTokenizationOnUtf8(): void { $tokenizer = new WordTokenizer(); From e33992dddea255bf563602e0418e3c1e30ce0c34 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 14 Nov 2017 21:40:46 +0100 Subject: [PATCH 205/328] Update changelog (#152) --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7829691a..a87d3db2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,32 @@ CHANGELOG This changelog references the relevant changes done in PHP-ML library. +* 0.5.0 (2017-11-14) + * general [php] Upgrade to PHP 7.1 (#150) + * general [coding standard] fix imports order and drop unused docs typehints + * feature [NeuralNetwork] Add PReLU activation function (#128) + * feature [NeuralNetwork] Add ThresholdedReLU activation function (#129) + * feature [Dataset] Support CSV with long lines (#119) + * feature [NeuralNetwork] Neural networks partial training and persistency (#91) + * feature Add french stopwords (#92) + * feature New methods: setBinPath, setVarPath in SupportVectorMachine (#73) + * feature Linear Discrimant Analysis (LDA) (#82) + * feature Linear algebra operations, Dimensionality reduction and some other minor changes (#81) + * feature Partial training base (#78) + * feature Add delimiter option for CsvDataset (#66) + * feature LogisticRegression classifier & Optimization methods (#63) + * feature Additional training for SVR (#59) + * optimization Comparison - replace eval (#130) + * optimization Use C-style casts (#124) + * optimization Speed up DataTransformer (#122) + * bug DBSCAN fix for associative keys and array_merge performance optimization (#139) + * bug Ensure user-provided SupportVectorMachine paths are valid (#126) + * bug [DecisionTree] Fix string cast #120 (#121) + * bug fix invalid typehint for subs method (#110) + * bug Fix samples transformation in Pipeline training (#94) + * bug Fix division by 0 error during normalization (#83) + * bug Fix wrong docs references (#79) + * 0.4.0 (2017-02-23) * feature [Classification] - Ensemble Classifiers : Bagging and RandomForest by Mustafa Karabulut * feature [Classification] - RandomForest::getFeatureImportances() method by Mustafa Karabulut From a11e3f69c38cbd7f87fa98a80e720f00d4b83253 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 15 Nov 2017 11:08:51 +0100 Subject: [PATCH 206/328] Add support for coveralls.io (#153) * Add support for coveralls.io * Generate coverage report only on php 7.2 build * Fix osx travis build and move tools to bin dir * Update php version badge * Fix travis conditional statement * Fix travis conditional statement * :facepalm: fix bin path --- .gitignore | 2 + .travis.yml | 15 +- README.md | 3 +- {tools => bin}/code-coverage.sh | 0 {tools => bin}/handle_brew_pkg.sh | 0 {tools => bin}/prepare_osx_env.sh | 0 composer.json | 3 +- composer.lock | 275 +++++++++++++++++++++++++++++- docs/index.md | 3 +- 9 files changed, 293 insertions(+), 8 deletions(-) rename {tools => bin}/code-coverage.sh (100%) rename {tools => bin}/handle_brew_pkg.sh (100%) rename {tools => bin}/prepare_osx_env.sh (100%) diff --git a/.gitignore b/.gitignore index bac27473..bed47c40 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ humbuglog.* .coverage .php_cs.cache /bin/php-cs-fixer +/bin/coveralls +/build diff --git a/.travis.yml b/.travis.yml index 48162fda..80bd5d8c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,9 +6,11 @@ matrix: include: - os: linux php: '7.1' + env: DISABLE_XDEBUG="true" - os: linux php: '7.2' + env: PHPUNIT_FLAGS="--coverage-clover build/logs/clover.xml" - os: osx osx_image: xcode7.3 @@ -18,12 +20,19 @@ matrix: - _PHP: php71 before_install: - - if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then /usr/bin/env bash tools/prepare_osx_env.sh ; fi + - if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then /usr/bin/env bash bin/prepare_osx_env.sh ; fi + - if [[ DISABLE_XDEBUG == "true" ]]; then phpenv config-rm xdebug.ini; fi install: - - if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then /usr/bin/env bash tools/handle_brew_pkg.sh "${_PHP}" ; fi + - if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then /usr/bin/env bash bin/handle_brew_pkg.sh "${_PHP}" ; fi - curl -s http://getcomposer.org/installer | php - php composer.phar install --dev --no-interaction --ignore-platform-reqs script: - - bin/phpunit + - bin/phpunit $PHPUNIT_FLAGS + +after_success: + - | + if [[ $PHPUNIT_FLAGS != "" ]]; then + php bin/coveralls -v + fi diff --git a/README.md b/README.md index f3ee24c4..69f0eb7e 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ # PHP-ML - Machine Learning library for PHP -[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.0-8892BF.svg)](https://php.net/) +[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.1-8892BF.svg)](https://php.net/) [![Latest Stable Version](https://img.shields.io/packagist/v/php-ai/php-ml.svg)](https://packagist.org/packages/php-ai/php-ml) [![Build Status](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/build.png?b=master)](https://scrutinizer-ci.com/g/php-ai/php-ml/build-status/master) [![Documentation Status](https://readthedocs.org/projects/php-ml/badge/?version=master)](http://php-ml.readthedocs.org/) [![Total Downloads](https://poser.pugx.org/php-ai/php-ml/downloads.svg)](https://packagist.org/packages/php-ai/php-ml) [![License](https://poser.pugx.org/php-ai/php-ml/license.svg)](https://packagist.org/packages/php-ai/php-ml) +[![Coverage Status](https://coveralls.io/repos/github/php-ai/php-ml/badge.svg?branch=coveralls)](https://coveralls.io/github/php-ai/php-ml?branch=coveralls) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/php-ai/php-ml/?branch=master) diff --git a/tools/code-coverage.sh b/bin/code-coverage.sh similarity index 100% rename from tools/code-coverage.sh rename to bin/code-coverage.sh diff --git a/tools/handle_brew_pkg.sh b/bin/handle_brew_pkg.sh similarity index 100% rename from tools/handle_brew_pkg.sh rename to bin/handle_brew_pkg.sh diff --git a/tools/prepare_osx_env.sh b/bin/prepare_osx_env.sh similarity index 100% rename from tools/prepare_osx_env.sh rename to bin/prepare_osx_env.sh diff --git a/composer.json b/composer.json index 8db49347..58e79a67 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,8 @@ }, "require-dev": { "phpunit/phpunit": "^6.0", - "friendsofphp/php-cs-fixer": "^2.4" + "friendsofphp/php-cs-fixer": "^2.4", + "php-coveralls/php-coveralls": "^1.0" }, "config": { "bin-dir": "bin" diff --git a/composer.lock b/composer.lock index 69794979..1384e6e4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "c3243c3586715dde5b7c8fc237d91a4a", + "content-hash": "c4d5b319f041c2d65249c7852a7f2fc1", "packages": [], "packages-dev": [ { @@ -305,6 +305,99 @@ ], "time": "2017-06-20T11:22:48+00:00" }, + { + "name": "guzzle/guzzle", + "version": "v3.8.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/4de0618a01b34aa1c8c33a3f13f396dcd3882eba", + "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": ">=2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "*", + "monolog/monolog": "1.*", + "phpunit/phpunit": "3.7.*", + "psr/log": "1.0.*", + "symfony/class-loader": "*", + "zendframework/zend-cache": "<2.3", + "zendframework/zend-log": "<2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.8-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "abandoned": "guzzlehttp/guzzle", + "time": "2014-01-28T22:29:15+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.6.0", @@ -395,6 +488,67 @@ ], "time": "2017-03-13T16:27:32+00:00" }, + { + "name": "php-coveralls/php-coveralls", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-coveralls/php-coveralls.git", + "reference": "9c07b63acbc9709344948b6fd4f63a32b2ef4127" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-coveralls/php-coveralls/zipball/9c07b63acbc9709344948b6fd4f63a32b2ef4127", + "reference": "9c07b63acbc9709344948b6fd4f63a32b2ef4127", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-simplexml": "*", + "guzzle/guzzle": "^2.8 || ^3.0", + "php": "^5.3.3 || ^7.0", + "psr/log": "^1.0", + "symfony/config": "^2.1 || ^3.0 || ^4.0", + "symfony/console": "^2.1 || ^3.0 || ^4.0", + "symfony/stopwatch": "^2.0 || ^3.0 || ^4.0", + "symfony/yaml": "^2.0 || ^3.0 || ^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0" + }, + "suggest": { + "symfony/http-kernel": "Allows Symfony integration" + }, + "bin": [ + "bin/coveralls" + ], + "type": "library", + "autoload": { + "psr-4": { + "Satooshi\\": "src/Satooshi/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kitamura Satoshi", + "email": "with.no.parachute@gmail.com", + "homepage": "https://www.facebook.com/satooshi.jp" + } + ], + "description": "PHP client library for Coveralls API", + "homepage": "https://github.com/php-coveralls/php-coveralls", + "keywords": [ + "ci", + "coverage", + "github", + "test" + ], + "time": "2017-10-14T23:15:34+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "1.0", @@ -1554,6 +1708,68 @@ "homepage": "https://github.com/sebastianbergmann/version", "time": "2016-10-03T07:35:21+00:00" }, + { + "name": "symfony/config", + "version": "v3.3.12", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "8d2649077dc54dfbaf521d31f217383d82303c5f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/8d2649077dc54dfbaf521d31f217383d82303c5f", + "reference": "8d2649077dc54dfbaf521d31f217383d82303c5f", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/filesystem": "~2.8|~3.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.3", + "symfony/finder": "<3.3" + }, + "require-dev": { + "symfony/dependency-injection": "~3.3", + "symfony/finder": "~3.3", + "symfony/yaml": "~3.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "time": "2017-11-07T14:16:22+00:00" + }, { "name": "symfony/console", "version": "v3.3.5", @@ -2165,6 +2381,61 @@ "homepage": "https://symfony.com", "time": "2017-04-12T14:14:56+00:00" }, + { + "name": "symfony/yaml", + "version": "v3.3.12", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "0938408c4faa518d95230deabb5f595bf0de31b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/0938408c4faa518d95230deabb5f595bf0de31b9", + "reference": "0938408c4faa518d95230deabb5f595bf0de31b9", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "require-dev": { + "symfony/console": "~2.8|~3.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2017-11-10T18:26:04+00:00" + }, { "name": "webmozart/assert", "version": "1.2.0", @@ -2222,7 +2493,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=7.0.0" + "php": "^7.1" }, "platform-dev": [] } diff --git a/docs/index.md b/docs/index.md index 0798c2e0..ef3a0e9b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,11 +1,12 @@ # PHP-ML - Machine Learning library for PHP -[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.0-8892BF.svg)](https://php.net/) +[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.1-8892BF.svg)](https://php.net/) [![Latest Stable Version](https://img.shields.io/packagist/v/php-ai/php-ml.svg)](https://packagist.org/packages/php-ai/php-ml) [![Build Status](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/build.png?b=master)](https://scrutinizer-ci.com/g/php-ai/php-ml/build-status/master) [![Documentation Status](https://readthedocs.org/projects/php-ml/badge/?version=master)](http://php-ml.readthedocs.org/) [![Total Downloads](https://poser.pugx.org/php-ai/php-ml/downloads.svg)](https://packagist.org/packages/php-ai/php-ml) [![License](https://poser.pugx.org/php-ai/php-ml/license.svg)](https://packagist.org/packages/php-ai/php-ml) +[![Coverage Status](https://coveralls.io/repos/github/php-ai/php-ml/badge.svg?branch=coveralls)](https://coveralls.io/github/php-ai/php-ml?branch=coveralls) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/php-ai/php-ml/?branch=master) From f7537c049af8ecf77f317b1ed29af49791731eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Monlla=C3=B3?= Date: Thu, 16 Nov 2017 21:40:11 +0100 Subject: [PATCH 207/328] documentation add tokenizer->fit required to build the dictionary (#155) --- .../feature-extraction/token-count-vectorizer.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/machine-learning/feature-extraction/token-count-vectorizer.md b/docs/machine-learning/feature-extraction/token-count-vectorizer.md index 83c6aaa3..c4ede683 100644 --- a/docs/machine-learning/feature-extraction/token-count-vectorizer.md +++ b/docs/machine-learning/feature-extraction/token-count-vectorizer.md @@ -26,13 +26,18 @@ $samples = [ ]; $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer()); -$vectorizer->transform($samples) -// return $vector = [ + +// Build the dictionary. +$vectorizer->fit($samples); + +// Transform the provided text samples into a vectorized list. +$vectorizer->transform($samples); +// return $samples = [ // [0 => 1, 1 => 1, 2 => 2, 3 => 1, 4 => 1], // [5 => 1, 6 => 1, 1 => 1, 2 => 1], // [5 => 1, 7 => 2, 8 => 1, 9 => 1], //]; - + ``` ### Vocabulary From ff80af2044fb46b05ea997b468f83461b176620f Mon Sep 17 00:00:00 2001 From: Qingshan Luo Date: Fri, 17 Nov 2017 04:45:35 +0800 Subject: [PATCH 208/328] code style Update Phpml\Math\Distance\Manhattan::distance() method. (#154) I think this will be better. --- src/Phpml/Math/Distance/Manhattan.php | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Phpml/Math/Distance/Manhattan.php b/src/Phpml/Math/Distance/Manhattan.php index 72a9d1fd..6d10b71c 100644 --- a/src/Phpml/Math/Distance/Manhattan.php +++ b/src/Phpml/Math/Distance/Manhattan.php @@ -18,13 +18,8 @@ public function distance(array $a, array $b) : float throw InvalidArgumentException::arraySizeNotMatch(); } - $distance = 0; - $count = count($a); - - for ($i = 0; $i < $count; ++$i) { - $distance += abs($a[$i] - $b[$i]); - } - - return $distance; + return array_sum(array_map(function ($m, $n) { + return abs($m - $n); + }, $a, $b)); } } From 333598b47204dfc666a5e3baba93cb611a05cea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Monlla=C3=B3?= Date: Mon, 20 Nov 2017 23:11:21 +0100 Subject: [PATCH 209/328] Fix backpropagation random error (#157) --- tests/Phpml/Classification/MLPClassifierTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Phpml/Classification/MLPClassifierTest.php b/tests/Phpml/Classification/MLPClassifierTest.php index 9f9c9a92..519dc905 100644 --- a/tests/Phpml/Classification/MLPClassifierTest.php +++ b/tests/Phpml/Classification/MLPClassifierTest.php @@ -112,16 +112,16 @@ public function testBackpropagationPartialTraining(): void public function testBackpropagationLearningMultilayer(): void { // Multi-layer 2 classes. - $network = new MLPClassifier(5, [3, 2], ['a', 'b']); + $network = new MLPClassifier(5, [3, 2], ['a', 'b', 'c']); $network->train( [[1, 0, 0, 0, 0], [0, 1, 1, 0, 0], [1, 1, 1, 1, 1], [0, 0, 0, 0, 0]], - ['a', 'b', 'a', 'b'] + ['a', 'b', 'a', 'c'] ); $this->assertEquals('a', $network->predict([1, 0, 0, 0, 0])); $this->assertEquals('b', $network->predict([0, 1, 1, 0, 0])); $this->assertEquals('a', $network->predict([1, 1, 1, 1, 1])); - $this->assertEquals('b', $network->predict([0, 0, 0, 0, 0])); + $this->assertEquals('c', $network->predict([0, 0, 0, 0, 0])); } public function testBackpropagationLearningMulticlass(): void From b1d40bfa30578fc2fb52351fbb20587f44a32955 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Monlla=C3=B3?= Date: Mon, 20 Nov 2017 23:39:50 +0100 Subject: [PATCH 210/328] Change from theta to learning rate var name in NN (#159) --- .../neural-network/multilayer-perceptron-classifier.md | 2 +- .../NeuralNetwork/Network/MultilayerPerceptron.php | 10 +++++----- src/Phpml/NeuralNetwork/Training/Backpropagation.php | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md b/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md index d2f746df..a6b060a4 100644 --- a/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md +++ b/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md @@ -8,7 +8,7 @@ A multilayer perceptron (MLP) is a feedforward artificial neural network model t * $hiddenLayers (array) - array with the hidden layers configuration, each value represent number of neurons in each layers * $classes (array) - array with the different training set classes (array keys are ignored) * $iterations (int) - number of training iterations -* $theta (int) - network theta parameter +* $learningRate (float) - the learning rate * $activationFunction (ActivationFunction) - neuron activation function ``` diff --git a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php index 9ef3f73e..94a84231 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -46,9 +46,9 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, protected $activationFunction; /** - * @var int + * @var float */ - private $theta; + private $learningRate; /** * @var Backpropagation @@ -58,7 +58,7 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, /** * @throws InvalidArgumentException */ - public function __construct(int $inputLayerFeatures, array $hiddenLayers, array $classes, int $iterations = 10000, ?ActivationFunction $activationFunction = null, int $theta = 1) + public function __construct(int $inputLayerFeatures, array $hiddenLayers, array $classes, int $iterations = 10000, ?ActivationFunction $activationFunction = null, float $learningRate = 1) { if (empty($hiddenLayers)) { throw InvalidArgumentException::invalidLayersNumber(); @@ -73,7 +73,7 @@ public function __construct(int $inputLayerFeatures, array $hiddenLayers, array $this->inputLayerFeatures = $inputLayerFeatures; $this->hiddenLayers = $hiddenLayers; $this->activationFunction = $activationFunction; - $this->theta = $theta; + $this->learningRate = $learningRate; $this->initNetwork(); } @@ -87,7 +87,7 @@ private function initNetwork(): void $this->addBiasNodes(); $this->generateSynapses(); - $this->backpropagation = new Backpropagation($this->theta); + $this->backpropagation = new Backpropagation($this->learningRate); } public function train(array $samples, array $targets): void diff --git a/src/Phpml/NeuralNetwork/Training/Backpropagation.php b/src/Phpml/NeuralNetwork/Training/Backpropagation.php index 98683abc..6722bd14 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -10,9 +10,9 @@ class Backpropagation { /** - * @var int + * @var float */ - private $theta; + private $learningRate; /** * @var array @@ -24,9 +24,9 @@ class Backpropagation */ private $prevSigmas = null; - public function __construct(int $theta) + public function __construct(float $learningRate) { - $this->theta = $theta; + $this->learningRate = $learningRate; } /** @@ -43,7 +43,7 @@ public function backpropagate(array $layers, $targetClass): void if ($neuron instanceof Neuron) { $sigma = $this->getSigma($neuron, $targetClass, $key, $i == $layersNumber); foreach ($neuron->getSynapses() as $synapse) { - $synapse->changeWeight($this->theta * $sigma * $synapse->getNode()->getOutput()); + $synapse->changeWeight($this->learningRate * $sigma * $synapse->getNode()->getOutput()); } } } From 726cf4cddf0553d3c935317984899552af5488bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Votruba?= Date: Wed, 22 Nov 2017 22:16:10 +0100 Subject: [PATCH 211/328] Added EasyCodingStandard + lots of code fixes (#156) * travis: move coveralls here, decouple from package * composer: use PSR4 * phpunit: simpler config * travis: add ecs run * composer: add ecs dev * use standard vendor/bin directory for dependency bins, confuses with local bins and require gitignore handling * ecs: add PSR2 * [cs] PSR2 spacing fixes * [cs] PSR2 class name fix * [cs] PHP7 fixes - return semicolon spaces, old rand functions, typehints * [cs] fix less strict typehints * fix typehints to make tests pass * ecs: ignore typehint-less elements * [cs] standardize arrays * [cs] standardize docblock, remove unused comments * [cs] use self where possible * [cs] sort class elements, from public to private * [cs] do not use yoda (found less yoda-cases, than non-yoda) * space * [cs] do not assign in condition * [cs] use namespace imports if possible * [cs] use ::class over strings * [cs] fix defaults for arrays properties, properties and constants single spacing * cleanup ecs comments * [cs] use item per line in multi-items array * missing line * misc * rebase --- .gitignore | 4 - .travis.yml | 10 +- composer.json | 11 +- composer.lock | 2266 +++++++++++++---- easy-coding-standard.neon | 39 + phpunit.xml | 8 +- src/Phpml/Association/Apriori.php | 35 +- src/Phpml/Classification/DecisionTree.php | 270 +- .../DecisionTree/DecisionTreeLeaf.php | 22 +- .../Classification/Ensemble/AdaBoost.php | 50 +- src/Phpml/Classification/Ensemble/Bagging.php | 31 +- .../Classification/Ensemble/RandomForest.php | 12 +- .../Classification/KNearestNeighbors.php | 4 +- src/Phpml/Classification/Linear/Adaline.php | 4 +- .../Classification/Linear/DecisionStump.php | 95 +- .../Linear/LogisticRegression.php | 28 +- .../Classification/Linear/Perceptron.php | 45 +- src/Phpml/Classification/MLPClassifier.php | 2 +- src/Phpml/Classification/NaiveBayes.php | 56 +- .../Classification/WeightedClassifier.php | 2 +- src/Phpml/Clustering/Clusterer.php | 2 +- src/Phpml/Clustering/DBSCAN.php | 13 +- src/Phpml/Clustering/FuzzyCMeans.php | 113 +- src/Phpml/Clustering/KMeans.php | 3 +- src/Phpml/Clustering/KMeans/Cluster.php | 11 +- src/Phpml/Clustering/KMeans/Point.php | 6 +- src/Phpml/Clustering/KMeans/Space.php | 40 +- src/Phpml/CrossValidation/Split.php | 17 +- .../CrossValidation/StratifiedRandomSplit.php | 4 +- src/Phpml/Dataset/ArrayDataset.php | 4 +- src/Phpml/Dataset/CsvDataset.php | 7 +- src/Phpml/Dataset/Dataset.php | 4 +- .../EigenTransformerBase.php | 2 +- src/Phpml/DimensionReduction/KernelPCA.php | 65 +- src/Phpml/DimensionReduction/LDA.php | 69 +- src/Phpml/DimensionReduction/PCA.php | 56 +- src/Phpml/Exception/DatasetException.php | 6 +- src/Phpml/Exception/FileException.php | 10 +- .../Exception/InvalidArgumentException.php | 40 +- src/Phpml/Exception/MatrixException.php | 10 +- src/Phpml/Exception/NormalizerException.php | 6 +- src/Phpml/Exception/SerializeException.php | 8 +- src/Phpml/FeatureExtraction/StopWords.php | 6 +- .../FeatureExtraction/TfIdfTransformer.php | 2 +- .../TokenCountVectorizer.php | 13 +- src/Phpml/Helper/OneVsRest.php | 73 +- .../Helper/Optimizer/ConjugateGradient.php | 46 +- src/Phpml/Helper/Optimizer/GD.php | 6 +- src/Phpml/Helper/Optimizer/Optimizer.php | 11 +- src/Phpml/Helper/Optimizer/StochasticGD.php | 25 +- src/Phpml/Math/Distance.php | 2 +- src/Phpml/Math/Distance/Chebyshev.php | 2 +- src/Phpml/Math/Distance/Euclidean.php | 4 +- src/Phpml/Math/Distance/Manhattan.php | 2 +- src/Phpml/Math/Distance/Minkowski.php | 2 +- src/Phpml/Math/Kernel.php | 6 +- src/Phpml/Math/Kernel/RBF.php | 5 +- .../LinearAlgebra/EigenvalueDecomposition.php | 230 +- .../Math/LinearAlgebra/LUDecomposition.php | 20 +- src/Phpml/Math/Matrix.php | 97 +- src/Phpml/Math/Set.php | 67 +- src/Phpml/Math/Statistic/Correlation.php | 2 +- src/Phpml/Math/Statistic/Covariance.php | 11 +- src/Phpml/Math/Statistic/Gaussian.php | 2 +- src/Phpml/Math/Statistic/Mean.php | 4 +- .../Math/Statistic/StandardDeviation.php | 2 +- src/Phpml/Metric/ClassificationReport.php | 24 +- src/Phpml/Metric/ConfusionMatrix.php | 6 +- src/Phpml/ModelManager.php | 2 +- .../NeuralNetwork/ActivationFunction.php | 2 +- .../ActivationFunction/BinaryStep.php | 2 +- .../ActivationFunction/Gaussian.php | 2 +- .../ActivationFunction/HyperbolicTangent.php | 2 +- .../ActivationFunction/PReLU.php | 2 +- .../ActivationFunction/Sigmoid.php | 2 +- .../ActivationFunction/ThresholdedReLU.php | 2 +- src/Phpml/NeuralNetwork/Layer.php | 28 +- src/Phpml/NeuralNetwork/Network.php | 11 +- .../NeuralNetwork/Network/LayeredNetwork.php | 8 +- .../Network/MultilayerPerceptron.php | 47 +- src/Phpml/NeuralNetwork/Node.php | 2 +- src/Phpml/NeuralNetwork/Node/Bias.php | 2 +- src/Phpml/NeuralNetwork/Node/Input.php | 2 +- src/Phpml/NeuralNetwork/Node/Neuron.php | 9 +- .../NeuralNetwork/Node/Neuron/Synapse.php | 14 +- .../Training/Backpropagation.php | 6 +- .../Training/Backpropagation/Sigma.php | 4 +- src/Phpml/Pipeline.php | 4 +- src/Phpml/Preprocessing/Imputer.php | 5 +- .../Imputer/Strategy/MeanStrategy.php | 2 +- .../Imputer/Strategy/MedianStrategy.php | 2 +- src/Phpml/Preprocessing/Normalizer.php | 13 +- src/Phpml/Regression/LeastSquares.php | 10 +- .../SupportVectorMachine/DataTransformer.php | 4 +- .../SupportVectorMachine.php | 2 +- src/Phpml/Tokenization/Tokenizer.php | 2 +- .../Tokenization/WhitespaceTokenizer.php | 2 +- src/Phpml/Tokenization/WordTokenizer.php | 2 +- tests/Phpml/Association/AprioriTest.php | 6 +- .../Phpml/Classification/DecisionTreeTest.php | 26 +- .../Classification/Ensemble/AdaBoostTest.php | 2 +- .../Classification/Ensemble/BaggingTest.php | 7 +- .../Ensemble/RandomForestTest.php | 22 +- .../Classification/KNearestNeighborsTest.php | 2 +- .../Classification/Linear/AdalineTest.php | 6 +- .../Linear/DecisionStumpTest.php | 4 +- .../Classification/Linear/PerceptronTest.php | 6 +- .../Classification/MLPClassifierTest.php | 4 +- tests/Phpml/Classification/NaiveBayesTest.php | 2 +- tests/Phpml/Classification/SVCTest.php | 2 +- tests/Phpml/Clustering/DBSCANTest.php | 21 +- tests/Phpml/Clustering/FuzzyCMeansTest.php | 2 + tests/Phpml/Clustering/KMeansTest.php | 1 + .../DimensionReduction/KernelPCATest.php | 14 +- tests/Phpml/DimensionReduction/LDATest.php | 4 +- tests/Phpml/DimensionReduction/PCATest.php | 4 +- .../TfIdfTransformerTest.php | 36 +- .../TokenCountVectorizerTest.php | 140 +- tests/Phpml/Math/ComparisonTest.php | 11 +- .../LinearAlgebra/EigenDecompositionTest.php | 4 +- tests/Phpml/Math/MatrixTest.php | 4 +- tests/Phpml/Math/ProductTest.php | 3 +- tests/Phpml/Math/SetTest.php | 6 +- tests/Phpml/Math/Statistic/CovarianceTest.php | 2 +- .../Phpml/Metric/ClassificationReportTest.php | 71 +- .../ActivationFunction/BinaryStepTest.php | 5 +- .../ActivationFunction/GaussianTest.php | 5 +- .../HyperboliTangentTest.php | 5 +- .../ActivationFunction/PReLUTest.php | 5 +- .../ActivationFunction/SigmoidTest.php | 5 +- .../ThresholdedReLUTest.php | 7 +- tests/Phpml/NeuralNetwork/LayerTest.php | 3 +- .../Network/LayeredNetworkTest.php | 5 +- .../NeuralNetwork/Node/Neuron/SynapseTest.php | 7 +- tests/Phpml/NeuralNetwork/Node/NeuronTest.php | 7 +- tests/Phpml/PipelineTest.php | 2 +- tests/Phpml/Preprocessing/NormalizerTest.php | 3 +- tests/Phpml/Regression/LeastSquaresTest.php | 2 +- tests/Phpml/Regression/SVRTest.php | 2 +- 139 files changed, 3199 insertions(+), 1633 deletions(-) create mode 100644 easy-coding-standard.neon diff --git a/.gitignore b/.gitignore index bed47c40..38ef5a0b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,4 @@ /vendor/ humbuglog.* -/bin/phpunit -.coverage .php_cs.cache -/bin/php-cs-fixer -/bin/coveralls /build diff --git a/.travis.yml b/.travis.yml index 80bd5d8c..8f8a68ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ matrix: include: - os: linux php: '7.1' - env: DISABLE_XDEBUG="true" + env: DISABLE_XDEBUG="true" STATIC_ANALYSIS="true" - os: linux php: '7.2' @@ -21,7 +21,7 @@ matrix: before_install: - if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then /usr/bin/env bash bin/prepare_osx_env.sh ; fi - - if [[ DISABLE_XDEBUG == "true" ]]; then phpenv config-rm xdebug.ini; fi + - if [[ $DISABLE_XDEBUG == "true" ]]; then phpenv config-rm xdebug.ini; fi install: - if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then /usr/bin/env bash bin/handle_brew_pkg.sh "${_PHP}" ; fi @@ -29,10 +29,12 @@ install: - php composer.phar install --dev --no-interaction --ignore-platform-reqs script: - - bin/phpunit $PHPUNIT_FLAGS + - vendor/bin/phpunit $PHPUNIT_FLAGS + - if [[ $STATIC_ANALYSIS != "" ]]; then vendor/bin/ecs check src tests; fi after_success: - | if [[ $PHPUNIT_FLAGS != "" ]]; then - php bin/coveralls -v + wget https://github.com/satooshi/php-coveralls/releases/download/v1.0.1/coveralls.phar; + php coveralls.phar --verbose; fi diff --git a/composer.json b/composer.json index 58e79a67..0563f3fe 100644 --- a/composer.json +++ b/composer.json @@ -12,8 +12,8 @@ } ], "autoload": { - "psr-0": { - "Phpml": "src/" + "psr-4": { + "Phpml\\": "src/Phpml" } }, "require": { @@ -22,9 +22,8 @@ "require-dev": { "phpunit/phpunit": "^6.0", "friendsofphp/php-cs-fixer": "^2.4", - "php-coveralls/php-coveralls": "^1.0" - }, - "config": { - "bin-dir": "bin" + "symplify/easy-coding-standard": "dev-master as 2.5", + "symplify/coding-standard": "dev-master as 2.5", + "symplify/package-builder": "dev-master#3604bea as 2.5" } } diff --git a/composer.lock b/composer.lock index 1384e6e4..e10b2d84 100644 --- a/composer.lock +++ b/composer.lock @@ -4,9 +4,71 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "c4d5b319f041c2d65249c7852a7f2fc1", + "content-hash": "0b709f785c1e62498755f557e49e1ba4", "packages": [], "packages-dev": [ + { + "name": "composer/semver", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573", + "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.5 || ^5.0.5", + "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "time": "2016-08-30T16:08:34+00:00" + }, { "name": "doctrine/annotations", "version": "v1.5.0", @@ -77,32 +139,32 @@ }, { "name": "doctrine/instantiator", - "version": "1.0.5", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", "shasum": "" }, "require": { - "php": ">=5.3,<8.0-DEV" + "php": "^7.1" }, "require-dev": { "athletic/athletic": "~0.1.8", "ext-pdo": "*", "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" + "phpunit/phpunit": "^6.2.3", + "squizlabs/php_codesniffer": "^3.0.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { @@ -127,7 +189,7 @@ "constructor", "instantiate" ], - "time": "2015-06-14T21:17:01+00:00" + "time": "2017-07-22T11:58:36+00:00" }, { "name": "doctrine/lexer", @@ -185,44 +247,46 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v2.4.0", + "version": "v2.8.1", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "63661f3add3609e90e4ab8115113e189ae547bb4" + "reference": "04f71e56e03ba2627e345e8c949c80dcef0e683e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/63661f3add3609e90e4ab8115113e189ae547bb4", - "reference": "63661f3add3609e90e4ab8115113e189ae547bb4", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/04f71e56e03ba2627e345e8c949c80dcef0e683e", + "reference": "04f71e56e03ba2627e345e8c949c80dcef0e683e", "shasum": "" }, "require": { + "composer/semver": "^1.4", "doctrine/annotations": "^1.2", "ext-json": "*", "ext-tokenizer": "*", - "gecko-packages/gecko-php-unit": "^2.0", - "php": "^5.6 || >=7.0 <7.2", - "sebastian/diff": "^1.4", - "symfony/console": "^3.0", - "symfony/event-dispatcher": "^3.0", - "symfony/filesystem": "^3.0", - "symfony/finder": "^3.0", - "symfony/options-resolver": "^3.0", + "gecko-packages/gecko-php-unit": "^2.0 || ^3.0", + "php": "^5.6 || >=7.0 <7.3", + "php-cs-fixer/diff": "^1.2", + "symfony/console": "^3.2 || ^4.0", + "symfony/event-dispatcher": "^3.0 || ^4.0", + "symfony/filesystem": "^3.0 || ^4.0", + "symfony/finder": "^3.0 || ^4.0", + "symfony/options-resolver": "^3.0 || ^4.0", "symfony/polyfill-php70": "^1.0", "symfony/polyfill-php72": "^1.4", - "symfony/process": "^3.0", - "symfony/stopwatch": "^3.0" + "symfony/process": "^3.0 || ^4.0", + "symfony/stopwatch": "^3.0 || ^4.0" }, "conflict": { "hhvm": "*" }, "require-dev": { - "johnkary/phpunit-speedtrap": "^1.1", + "johnkary/phpunit-speedtrap": "^1.1 || ^2.0@dev", "justinrainbow/json-schema": "^5.0", - "phpunit/phpunit": "^4.8.35 || ^5.4.3", - "satooshi/php-coveralls": "^1.0", - "symfony/phpunit-bridge": "^3.2.2" + "php-coveralls/php-coveralls": "^1.0.2", + "php-cs-fixer/accessible-object": "^1.0", + "phpunit/phpunit": "^5.7.23 || ^6.4.3", + "symfony/phpunit-bridge": "^3.2.2 || ^4.0" }, "suggest": { "ext-mbstring": "For handling non-UTF8 characters in cache signature.", @@ -232,11 +296,6 @@ "php-cs-fixer" ], "type": "application", - "extra": { - "branch-alias": { - "dev-master": "2.4-dev" - } - }, "autoload": { "psr-4": { "PhpCsFixer\\": "src/" @@ -264,173 +323,93 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2017-07-18T15:35:40+00:00" + "time": "2017-11-09T13:31:39+00:00" }, { "name": "gecko-packages/gecko-php-unit", - "version": "v2.1", + "version": "v3.0", "source": { "type": "git", "url": "https://github.com/GeckoPackages/GeckoPHPUnit.git", - "reference": "5b9e9622c7efd3b22655270b80c03f9e52878a6e" + "reference": "6a866551dffc2154c1b091bae3a7877d39c25ca3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GeckoPackages/GeckoPHPUnit/zipball/5b9e9622c7efd3b22655270b80c03f9e52878a6e", - "reference": "5b9e9622c7efd3b22655270b80c03f9e52878a6e", + "url": "https://api.github.com/repos/GeckoPackages/GeckoPHPUnit/zipball/6a866551dffc2154c1b091bae3a7877d39c25ca3", + "reference": "6a866551dffc2154c1b091bae3a7877d39c25ca3", "shasum": "" }, "require": { - "php": "^5.3.6 || ^7.0" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.4.3" - }, - "type": "library", - "autoload": { - "psr-4": { - "GeckoPackages\\PHPUnit\\": "src\\PHPUnit" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Additional PHPUnit tests.", - "homepage": "https://github.com/GeckoPackages", - "keywords": [ - "extension", - "filesystem", - "phpunit" - ], - "time": "2017-06-20T11:22:48+00:00" - }, - { - "name": "guzzle/guzzle", - "version": "v3.8.1", - "source": { - "type": "git", - "url": "https://github.com/guzzle/guzzle.git", - "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/4de0618a01b34aa1c8c33a3f13f396dcd3882eba", - "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba", - "shasum": "" - }, - "require": { - "ext-curl": "*", - "php": ">=5.3.3", - "symfony/event-dispatcher": ">=2.1" - }, - "replace": { - "guzzle/batch": "self.version", - "guzzle/cache": "self.version", - "guzzle/common": "self.version", - "guzzle/http": "self.version", - "guzzle/inflection": "self.version", - "guzzle/iterator": "self.version", - "guzzle/log": "self.version", - "guzzle/parser": "self.version", - "guzzle/plugin": "self.version", - "guzzle/plugin-async": "self.version", - "guzzle/plugin-backoff": "self.version", - "guzzle/plugin-cache": "self.version", - "guzzle/plugin-cookie": "self.version", - "guzzle/plugin-curlauth": "self.version", - "guzzle/plugin-error-response": "self.version", - "guzzle/plugin-history": "self.version", - "guzzle/plugin-log": "self.version", - "guzzle/plugin-md5": "self.version", - "guzzle/plugin-mock": "self.version", - "guzzle/plugin-oauth": "self.version", - "guzzle/service": "self.version", - "guzzle/stream": "self.version" + "phpunit/phpunit": "^6.0" }, - "require-dev": { - "doctrine/cache": "*", - "monolog/monolog": "1.*", - "phpunit/phpunit": "3.7.*", - "psr/log": "1.0.*", - "symfony/class-loader": "*", - "zendframework/zend-cache": "<2.3", - "zendframework/zend-log": "<2.3" + "suggest": { + "ext-dom": "When testing with xml.", + "ext-libxml": "When testing with xml.", + "phpunit/phpunit": "This is an extension for it so make sure you have it some way." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.8-dev" + "dev-master": "3.0-dev" } }, "autoload": { - "psr-0": { - "Guzzle": "src/", - "Guzzle\\Tests": "tests/" + "psr-4": { + "GeckoPackages\\PHPUnit\\": "src/PHPUnit" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Guzzle Community", - "homepage": "https://github.com/guzzle/guzzle/contributors" - } - ], - "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", - "homepage": "http://guzzlephp.org/", + "description": "Additional PHPUnit asserts and constraints.", + "homepage": "https://github.com/GeckoPackages", "keywords": [ - "client", - "curl", - "framework", - "http", - "http client", - "rest", - "web service" + "extension", + "filesystem", + "phpunit" ], - "abandoned": "guzzlehttp/guzzle", - "time": "2014-01-28T22:29:15+00:00" + "time": "2017-08-23T07:46:41+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.6.0", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe" + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/5a5a9fc8025a08d8919be87d6884d5a92520cefe", - "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", "shasum": "" }, "require": { - "php": ">=5.4.0" + "php": "^5.6 || ^7.0" }, "require-dev": { - "doctrine/collections": "1.*", - "phpunit/phpunit": "~4.1" + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^4.1" }, "type": "library", "autoload": { "psr-4": { "DeepCopy\\": "src/DeepCopy/" - } + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "description": "Create deep copies (clones) of your objects", - "homepage": "https://github.com/myclabs/DeepCopy", "keywords": [ "clone", "copy", @@ -438,362 +417,906 @@ "object", "object graph" ], - "time": "2017-01-26T22:05:40+00:00" + "time": "2017-10-19T19:58:43+00:00" }, { - "name": "paragonie/random_compat", - "version": "v2.0.10", + "name": "nette/caching", + "version": "v2.5.6", "source": { "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d" + "url": "https://github.com/nette/caching.git", + "reference": "1231735b5135ca02bd381b70482c052d2a90bdc9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/634bae8e911eefa89c1abfbf1b66da679ac8f54d", - "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d", + "url": "https://api.github.com/repos/nette/caching/zipball/1231735b5135ca02bd381b70482c052d2a90bdc9", + "reference": "1231735b5135ca02bd381b70482c052d2a90bdc9", "shasum": "" }, "require": { - "php": ">=5.2.0" + "nette/finder": "^2.2 || ~3.0.0", + "nette/utils": "^2.4 || ~3.0.0", + "php": ">=5.6.0" + }, + "conflict": { + "nette/nette": "<2.2" }, "require-dev": { - "phpunit/phpunit": "4.*|5.*" + "latte/latte": "^2.4", + "nette/di": "^2.4 || ~3.0.0", + "nette/tester": "^2.0", + "tracy/tracy": "^2.4" }, "suggest": { - "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + "ext-pdo_sqlite": "to use SQLiteStorage or SQLiteJournal" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, "autoload": { - "files": [ - "lib/random.php" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" ], "authors": [ { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com" + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" } ], - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "description": "⏱ Nette Caching: library with easy-to-use API and many cache backends.", + "homepage": "https://nette.org", "keywords": [ - "csprng", - "pseudorandom", - "random" + "cache", + "journal", + "memcached", + "nette", + "sqlite" ], - "time": "2017-03-13T16:27:32+00:00" + "time": "2017-08-30T12:12:25+00:00" }, { - "name": "php-coveralls/php-coveralls", - "version": "v1.0.2", + "name": "nette/di", + "version": "v2.4.10", "source": { "type": "git", - "url": "https://github.com/php-coveralls/php-coveralls.git", - "reference": "9c07b63acbc9709344948b6fd4f63a32b2ef4127" + "url": "https://github.com/nette/di.git", + "reference": "a4b3be935b755f23aebea1ce33d7e3c832cdff98" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-coveralls/php-coveralls/zipball/9c07b63acbc9709344948b6fd4f63a32b2ef4127", - "reference": "9c07b63acbc9709344948b6fd4f63a32b2ef4127", + "url": "https://api.github.com/repos/nette/di/zipball/a4b3be935b755f23aebea1ce33d7e3c832cdff98", + "reference": "a4b3be935b755f23aebea1ce33d7e3c832cdff98", "shasum": "" }, "require": { - "ext-json": "*", - "ext-simplexml": "*", - "guzzle/guzzle": "^2.8 || ^3.0", - "php": "^5.3.3 || ^7.0", - "psr/log": "^1.0", - "symfony/config": "^2.1 || ^3.0 || ^4.0", - "symfony/console": "^2.1 || ^3.0 || ^4.0", - "symfony/stopwatch": "^2.0 || ^3.0 || ^4.0", - "symfony/yaml": "^2.0 || ^3.0 || ^4.0" + "ext-tokenizer": "*", + "nette/neon": "^2.3.3 || ~3.0.0", + "nette/php-generator": "^2.6.1 || ~3.0.0", + "nette/utils": "^2.4.3 || ~3.0.0", + "php": ">=5.6.0" }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0" + "conflict": { + "nette/bootstrap": "<2.4", + "nette/nette": "<2.2" }, - "suggest": { - "symfony/http-kernel": "Allows Symfony integration" + "require-dev": { + "nette/tester": "^2.0", + "tracy/tracy": "^2.3" }, - "bin": [ - "bin/coveralls" - ], "type": "library", - "autoload": { - "psr-4": { - "Satooshi\\": "src/Satooshi/" + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" } }, + "autoload": { + "classmap": [ + "src/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" ], "authors": [ { - "name": "Kitamura Satoshi", - "email": "with.no.parachute@gmail.com", - "homepage": "https://www.facebook.com/satooshi.jp" + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" } ], - "description": "PHP client library for Coveralls API", - "homepage": "https://github.com/php-coveralls/php-coveralls", + "description": "💎 Nette Dependency Injection Container: Flexible, compiled and full-featured DIC with perfectly usable autowiring and support for all new PHP 7.1 features.", + "homepage": "https://nette.org", "keywords": [ - "ci", - "coverage", - "github", - "test" - ], - "time": "2017-10-14T23:15:34+00:00" + "compiled", + "di", + "dic", + "factory", + "ioc", + "nette", + "static" + ], + "time": "2017-08-31T22:42:00+00:00" }, { - "name": "phpdocumentor/reflection-common", - "version": "1.0", + "name": "nette/finder", + "version": "v2.4.1", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" + "url": "https://github.com/nette/finder.git", + "reference": "4d43a66d072c57d585bf08a3ef68d3587f7e9547" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", - "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "url": "https://api.github.com/repos/nette/finder/zipball/4d43a66d072c57d585bf08a3ef68d3587f7e9547", + "reference": "4d43a66d072c57d585bf08a3ef68d3587f7e9547", "shasum": "" }, "require": { - "php": ">=5.5" + "nette/utils": "^2.4 || ~3.0.0", + "php": ">=5.6.0" + }, + "conflict": { + "nette/nette": "<2.2" }, "require-dev": { - "phpunit/phpunit": "^4.6" + "nette/tester": "^2.0", + "tracy/tracy": "^2.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.4-dev" } }, "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src" - ] - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" ], "authors": [ { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" } ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ], - "time": "2015-12-27T11:43:31+00:00" + "description": "Nette Finder: Files Searching", + "homepage": "https://nette.org", + "time": "2017-07-10T23:47:08+00:00" }, { - "name": "phpdocumentor/reflection-docblock", - "version": "3.1.1", + "name": "nette/neon", + "version": "v2.4.2", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e" + "url": "https://github.com/nette/neon.git", + "reference": "9eacd50553b26b53a3977bfb2fea2166d4331622" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e", - "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e", + "url": "https://api.github.com/repos/nette/neon/zipball/9eacd50553b26b53a3977bfb2fea2166d4331622", + "reference": "9eacd50553b26b53a3977bfb2fea2166d4331622", "shasum": "" }, "require": { - "php": ">=5.5", - "phpdocumentor/reflection-common": "^1.0@dev", - "phpdocumentor/type-resolver": "^0.2.0", - "webmozart/assert": "^1.0" + "ext-iconv": "*", + "ext-json": "*", + "php": ">=5.6.0" }, "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^4.4" + "nette/tester": "~2.0", + "tracy/tracy": "^2.3" }, "type": "library", - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" } }, + "autoload": { + "classmap": [ + "src/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" ], "authors": [ { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" } ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2016-09-30T07:12:33+00:00" + "description": "Nette NEON: parser & generator for Nette Object Notation", + "homepage": "http://ne-on.org", + "time": "2017-07-11T18:29:08+00:00" }, { - "name": "phpdocumentor/type-resolver", - "version": "0.2.1", + "name": "nette/php-generator", + "version": "v3.0.1", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb" + "url": "https://github.com/nette/php-generator.git", + "reference": "eb2dbc9c3409e9db40568109ca4994d51373b60c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", - "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", + "url": "https://api.github.com/repos/nette/php-generator/zipball/eb2dbc9c3409e9db40568109ca4994d51373b60c", + "reference": "eb2dbc9c3409e9db40568109ca4994d51373b60c", "shasum": "" }, "require": { - "php": ">=5.5", - "phpdocumentor/reflection-common": "^1.0" + "nette/utils": "^2.4.2 || ~3.0.0", + "php": ">=7.0" + }, + "conflict": { + "nette/nette": "<2.2" }, "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^5.2||^4.8.24" + "nette/tester": "^2.0", + "tracy/tracy": "^2.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" ], "authors": [ { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" } ], - "time": "2016-11-25T06:54:22+00:00" + "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 7.1 features.", + "homepage": "https://nette.org", + "keywords": [ + "code", + "nette", + "php", + "scaffolding" + ], + "time": "2017-07-11T19:07:13+00:00" }, { - "name": "phpspec/prophecy", - "version": "v1.7.0", + "name": "nette/robot-loader", + "version": "v3.0.2", "source": { "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "93d39f1f7f9326d746203c7c056f300f7f126073" + "url": "https://github.com/nette/robot-loader.git", + "reference": "b703b4f5955831b0bcaacbd2f6af76021b056826" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073", - "reference": "93d39f1f7f9326d746203c7c056f300f7f126073", + "url": "https://api.github.com/repos/nette/robot-loader/zipball/b703b4f5955831b0bcaacbd2f6af76021b056826", + "reference": "b703b4f5955831b0bcaacbd2f6af76021b056826", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", - "sebastian/comparator": "^1.1|^2.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0" + "ext-tokenizer": "*", + "nette/finder": "^2.3 || ^3.0", + "nette/utils": "^2.4 || ^3.0", + "php": ">=5.6.0" + }, + "conflict": { + "nette/nette": "<2.2" }, "require-dev": { - "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8 || ^5.6.5" + "nette/tester": "^2.0", + "tracy/tracy": "^2.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.6.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { - "psr-0": { - "Prophecy\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" ], "authors": [ { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" + "name": "David Grudl", + "homepage": "https://davidgrudl.com" }, { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" + "name": "Nette Community", + "homepage": "https://nette.org/contributors" } ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", + "description": "🍀 Nette RobotLoader: high performance and comfortable autoloader that will search and autoload classes within your application.", + "homepage": "https://nette.org", "keywords": [ - "Double", - "Dummy", - "fake", + "autoload", + "class", + "interface", + "nette", + "trait" + ], + "time": "2017-07-18T00:09:56+00:00" + }, + { + "name": "nette/utils", + "version": "v2.4.8", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "f1584033b5af945b470533b466b81a789d532034" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/f1584033b5af945b470533b466b81a789d532034", + "reference": "f1584033b5af945b470533b466b81a789d532034", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "conflict": { + "nette/nette": "<2.2" + }, + "require-dev": { + "nette/tester": "~2.0", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize() and toAscii()", + "ext-intl": "for script transliteration in Strings::webalize() and toAscii()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "time": "2017-08-20T17:32:29+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v2.0.11", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/5da4d3c796c275c55f057af5a643ae297d96b4d8", + "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "pseudorandom", + "random" + ], + "time": "2017-09-27T21:40:39+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^1.0.1", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2017-03-05T18:14:27+00:00" + }, + { + "name": "phar-io/version", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2017-03-05T17:38:23+00:00" + }, + { + "name": "php-cs-fixer/diff", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/diff.git", + "reference": "f0ef6133d674137e902fdf8a6f2e8e97e14a087b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/f0ef6133d674137e902fdf8a6f2e8e97e14a087b", + "reference": "f0ef6133d674137e902fdf8a6f2e8e97e14a087b", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.4.3", + "symfony/process": "^3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "SpacePossum" + } + ], + "description": "sebastian/diff v2 backport support for PHP5.6", + "homepage": "https://github.com/PHP-CS-Fixer", + "keywords": [ + "diff" + ], + "time": "2017-10-19T09:58:18+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2017-09-11T18:02:19+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "4.1.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "2d3d238c433cf69caeb4842e97a3223a116f94b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/2d3d238c433cf69caeb4842e97a3223a116f94b2", + "reference": "2d3d238c433cf69caeb4842e97a3223a116f94b2", + "shasum": "" + }, + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0@dev", + "phpdocumentor/type-resolver": "^0.4.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2017-08-30T18:51:59+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2017-07-14T14:27:02+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.7.2", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", + "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8 || ^5.6.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", "mock", "spy", "stub" ], - "time": "2017-03-02T20:05:34+00:00" + "time": "2017-09-04T11:05:03+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "5.0.2", + "version": "5.2.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "531553c4795a1df54114342d68ca337d5d81c8a0" + "reference": "8e1d2397d8adf59a3f12b2878a3aaa66d1ab189d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/531553c4795a1df54114342d68ca337d5d81c8a0", - "reference": "531553c4795a1df54114342d68ca337d5d81c8a0", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/8e1d2397d8adf59a3f12b2878a3aaa66d1ab189d", + "reference": "8e1d2397d8adf59a3f12b2878a3aaa66d1ab189d", "shasum": "" }, "require": { "ext-dom": "*", "ext-xmlwriter": "*", "php": "^7.0", - "phpunit/php-file-iterator": "^1.3", - "phpunit/php-text-template": "^1.2", - "phpunit/php-token-stream": "^1.4.11 || ^2.0", - "sebastian/code-unit-reverse-lookup": "^1.0", - "sebastian/environment": "^2.0", - "sebastian/version": "^2.0" + "phpunit/php-file-iterator": "^1.4.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^2.0", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^3.0", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" }, "require-dev": { "ext-xdebug": "^2.5", "phpunit/phpunit": "^6.0" }, "suggest": { - "ext-xdebug": "^2.5.1" + "ext-xdebug": "^2.5.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0.x-dev" + "dev-master": "5.2.x-dev" } }, "autoload": { @@ -819,7 +1342,7 @@ "testing", "xunit" ], - "time": "2017-03-01T09:14:18+00:00" + "time": "2017-11-03T13:47:33+00:00" }, { "name": "phpunit/php-file-iterator", @@ -960,29 +1483,29 @@ }, { "name": "phpunit/php-token-stream", - "version": "1.4.11", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7" + "reference": "9a02332089ac48e704c70f6cefed30c224e3c0b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7", - "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/9a02332089ac48e704c70f6cefed30c224e3c0b0", + "reference": "9a02332089ac48e704c70f6cefed30c224e3c0b0", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": ">=5.3.3" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.2" + "phpunit/phpunit": "^6.2.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -1005,20 +1528,20 @@ "keywords": [ "tokenizer" ], - "time": "2017-02-27T10:12:30+00:00" + "time": "2017-08-20T05:47:52+00:00" }, { "name": "phpunit/phpunit", - "version": "6.0.8", + "version": "6.4.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "47ee3fa1bca5c50f1d25105201eb20df777bd7b6" + "reference": "562f7dc75d46510a4ed5d16189ae57fbe45a9932" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/47ee3fa1bca5c50f1d25105201eb20df777bd7b6", - "reference": "47ee3fa1bca5c50f1d25105201eb20df777bd7b6", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/562f7dc75d46510a4ed5d16189ae57fbe45a9932", + "reference": "562f7dc75d46510a4ed5d16189ae57fbe45a9932", "shasum": "" }, "require": { @@ -1027,22 +1550,24 @@ "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", - "myclabs/deep-copy": "^1.3", + "myclabs/deep-copy": "^1.6.1", + "phar-io/manifest": "^1.0.1", + "phar-io/version": "^1.0", "php": "^7.0", - "phpspec/prophecy": "^1.6.2", - "phpunit/php-code-coverage": "^5.0", - "phpunit/php-file-iterator": "^1.4", - "phpunit/php-text-template": "^1.2", - "phpunit/php-timer": "^1.0.6", - "phpunit/phpunit-mock-objects": "^4.0", - "sebastian/comparator": "^1.2.4 || ^2.0", - "sebastian/diff": "^1.2", - "sebastian/environment": "^2.0", - "sebastian/exporter": "^2.0 || ^3.0", - "sebastian/global-state": "^1.1 || ^2.0", - "sebastian/object-enumerator": "^2.0 || ^3.0", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^5.2.2", + "phpunit/php-file-iterator": "^1.4.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^1.0.9", + "phpunit/phpunit-mock-objects": "^4.0.3", + "sebastian/comparator": "^2.0.2", + "sebastian/diff": "^2.0", + "sebastian/environment": "^3.1", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^2.0", + "sebastian/object-enumerator": "^3.0.3", "sebastian/resource-operations": "^1.0", - "sebastian/version": "^2.0" + "sebastian/version": "^2.0.1" }, "conflict": { "phpdocumentor/reflection-docblock": "3.0.2", @@ -1061,7 +1586,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.0.x-dev" + "dev-master": "6.4.x-dev" } }, "autoload": { @@ -1087,26 +1612,26 @@ "testing", "xunit" ], - "time": "2017-03-02T15:24:03+00:00" + "time": "2017-11-08T11:26:09+00:00" }, { "name": "phpunit/phpunit-mock-objects", - "version": "4.0.1", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "eabce450df194817a7d7e27e19013569a903a2bf" + "reference": "2f789b59ab89669015ad984afa350c4ec577ade0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/eabce450df194817a7d7e27e19013569a903a2bf", - "reference": "eabce450df194817a7d7e27e19013569a903a2bf", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/2f789b59ab89669015ad984afa350c4ec577ade0", + "reference": "2f789b59ab89669015ad984afa350c4ec577ade0", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.0.2", + "doctrine/instantiator": "^1.0.5", "php": "^7.0", - "phpunit/php-text-template": "^1.2", + "phpunit/php-text-template": "^1.2.1", "sebastian/exporter": "^3.0" }, "conflict": { @@ -1146,7 +1671,56 @@ "mock", "xunit" ], - "time": "2017-03-03T06:30:20+00:00" + "time": "2017-08-03T14:08:16+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" }, { "name": "psr/log", @@ -1242,30 +1816,30 @@ }, { "name": "sebastian/comparator", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "20f84f468cb67efee293246e6a09619b891f55f0" + "reference": "1174d9018191e93cb9d719edec01257fc05f8158" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/20f84f468cb67efee293246e6a09619b891f55f0", - "reference": "20f84f468cb67efee293246e6a09619b891f55f0", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1174d9018191e93cb9d719edec01257fc05f8158", + "reference": "1174d9018191e93cb9d719edec01257fc05f8158", "shasum": "" }, "require": { "php": "^7.0", - "sebastian/diff": "^1.2", - "sebastian/exporter": "^3.0" + "sebastian/diff": "^2.0", + "sebastian/exporter": "^3.1" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^6.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "2.1.x-dev" } }, "autoload": { @@ -1296,38 +1870,38 @@ } ], "description": "Provides the functionality to compare PHP values for equality", - "homepage": "http://www.github.com/sebastianbergmann/comparator", + "homepage": "https://github.com/sebastianbergmann/comparator", "keywords": [ "comparator", "compare", "equality" ], - "time": "2017-03-03T06:26:08+00:00" + "time": "2017-11-03T07:16:52+00:00" }, { "name": "sebastian/diff", - "version": "1.4.1", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.8" + "phpunit/phpunit": "^6.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -1354,32 +1928,32 @@ "keywords": [ "diff" ], - "time": "2015-12-08T07:14:41+00:00" + "time": "2017-08-03T08:09:46+00:00" }, { "name": "sebastian/environment", - "version": "2.0.0", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", - "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^5.0" + "phpunit/phpunit": "^6.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.1.x-dev" } }, "autoload": { @@ -1404,20 +1978,20 @@ "environment", "hhvm" ], - "time": "2016-11-26T07:53:53+00:00" + "time": "2017-07-01T08:51:00+00:00" }, { "name": "sebastian/exporter", - "version": "3.0.0", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "b82d077cb3459e393abcf4867ae8f7230dcb51f6" + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/b82d077cb3459e393abcf4867ae8f7230dcb51f6", - "reference": "b82d077cb3459e393abcf4867ae8f7230dcb51f6", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", "shasum": "" }, "require": { @@ -1431,7 +2005,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "3.1.x-dev" } }, "autoload": { @@ -1471,35 +2045,85 @@ "export", "exporter" ], - "time": "2017-03-03T06:25:06+00:00" + "time": "2017-04-03T13:19:02+00:00" }, { "name": "sebastian/global-state", - "version": "1.1.1", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2017-04-27T15:39:26+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" }, "require-dev": { - "phpunit/phpunit": "~4.2" - }, - "suggest": { - "ext-uopz": "*" + "phpunit/phpunit": "^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { @@ -1517,30 +2141,26 @@ "email": "sebastian@phpunit.de" } ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "time": "2015-10-12T03:26:01+00:00" + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-08-03T12:35:26+00:00" }, { - "name": "sebastian/object-enumerator", - "version": "3.0.0", + "name": "sebastian/object-reflector", + "version": "1.1.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "de6e32f7192dfea2e4bedc892434f4830b5c5794" + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/de6e32f7192dfea2e4bedc892434f4830b5c5794", - "reference": "de6e32f7192dfea2e4bedc892434f4830b5c5794", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", "shasum": "" }, "require": { - "php": "^7.0", - "sebastian/recursion-context": "^3.0" + "php": "^7.0" }, "require-dev": { "phpunit/phpunit": "^6.0" @@ -1548,7 +2168,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "1.1-dev" } }, "autoload": { @@ -1566,9 +2186,9 @@ "email": "sebastian@phpunit.de" } ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2017-03-03T06:21:01+00:00" + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2017-03-29T09:07:27+00:00" }, { "name": "sebastian/recursion-context", @@ -1708,6 +2328,94 @@ "homepage": "https://github.com/sebastianbergmann/version", "time": "2016-10-03T07:35:21+00:00" }, + { + "name": "slevomat/coding-standard", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "bab653d0f7f2e3ed13796f7803067d252f00a25a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/bab653d0f7f2e3ed13796f7803067d252f00a25a", + "reference": "bab653d0f7f2e3ed13796f7803067d252f00a25a", + "shasum": "" + }, + "require": { + "php": "^7.0", + "squizlabs/php_codesniffer": "^3.0.1" + }, + "require-dev": { + "jakub-onderka/php-parallel-lint": "0.9.2", + "phing/phing": "2.16", + "phpstan/phpstan": "0.8.4", + "phpunit/phpunit": "6.3.0" + }, + "type": "phpcodesniffer-standard", + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "time": "2017-09-15T17:47:36+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "d667e245d5dcd4d7bf80f26f2c947d476b66213e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/d667e245d5dcd4d7bf80f26f2c947d476b66213e", + "reference": "d667e245d5dcd4d7bf80f26f2c947d476b66213e", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2017-10-16T22:40:25+00:00" + }, { "name": "symfony/config", "version": "v3.3.12", @@ -1731,12 +2439,206 @@ "symfony/finder": "<3.3" }, "require-dev": { - "symfony/dependency-injection": "~3.3", - "symfony/finder": "~3.3", - "symfony/yaml": "~3.0" + "symfony/dependency-injection": "~3.3", + "symfony/finder": "~3.3", + "symfony/yaml": "~3.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "time": "2017-11-07T14:16:22+00:00" + }, + { + "name": "symfony/console", + "version": "v3.3.12", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "099302cc53e57cbb7414fd9f3ace40e5e2767c0b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/099302cc53e57cbb7414fd9f3ace40e5e2767c0b", + "reference": "099302cc53e57cbb7414fd9f3ace40e5e2767c0b", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/debug": "~2.8|~3.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.3", + "symfony/dependency-injection": "~3.3", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/filesystem": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/filesystem": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2017-11-12T16:53:41+00:00" + }, + { + "name": "symfony/debug", + "version": "v3.3.12", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "74557880e2846b5c84029faa96b834da37e29810" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/74557880e2846b5c84029faa96b834da37e29810", + "reference": "74557880e2846b5c84029faa96b834da37e29810", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/http-kernel": "~2.8|~3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "time": "2017-11-10T16:38:39+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v3.3.12", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "4e84f5af2c2d51ee3dee72df40b7fc08f49b4ab8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/4e84f5af2c2d51ee3dee72df40b7fc08f49b4ab8", + "reference": "4e84f5af2c2d51ee3dee72df40b7fc08f49b4ab8", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "psr/container": "^1.0" + }, + "conflict": { + "symfony/config": "<3.3.1", + "symfony/finder": "<3.3", + "symfony/yaml": "<3.3" + }, + "provide": { + "psr/container-implementation": "1.0" + }, + "require-dev": { + "symfony/config": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/yaml": "~3.3" }, "suggest": { - "symfony/yaml": "To use the yaml reference dumper" + "symfony/config": "", + "symfony/expression-language": "For using expressions in service container configuration", + "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", + "symfony/yaml": "" }, "type": "library", "extra": { @@ -1746,7 +2648,7 @@ }, "autoload": { "psr-4": { - "Symfony\\Component\\Config\\": "" + "Symfony\\Component\\DependencyInjection\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -1766,46 +2668,40 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Config Component", + "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2017-11-07T14:16:22+00:00" + "time": "2017-11-13T18:10:32+00:00" }, { - "name": "symfony/console", - "version": "v3.3.5", + "name": "symfony/event-dispatcher", + "version": "v3.3.12", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "a97e45d98c59510f085fa05225a1acb74dfe0546" + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "271d8c27c3ec5ecee6e2ac06016232e249d638d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/a97e45d98c59510f085fa05225a1acb74dfe0546", - "reference": "a97e45d98c59510f085fa05225a1acb74dfe0546", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/271d8c27c3ec5ecee6e2ac06016232e249d638d9", + "reference": "271d8c27c3ec5ecee6e2ac06016232e249d638d9", "shasum": "" }, "require": { - "php": ">=5.5.9", - "symfony/debug": "~2.8|~3.0", - "symfony/polyfill-mbstring": "~1.0" + "php": "^5.5.9|>=7.0.8" }, "conflict": { "symfony/dependency-injection": "<3.3" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~3.3", + "symfony/config": "~2.8|~3.0", "symfony/dependency-injection": "~3.3", - "symfony/event-dispatcher": "~2.8|~3.0", - "symfony/filesystem": "~2.8|~3.0", - "symfony/http-kernel": "~2.8|~3.0", - "symfony/process": "~2.8|~3.0" + "symfony/expression-language": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0" }, "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/filesystem": "", - "symfony/process": "" + "symfony/dependency-injection": "", + "symfony/http-kernel": "" }, "type": "library", "extra": { @@ -1815,7 +2711,7 @@ }, "autoload": { "psr-4": { - "Symfony\\Component\\Console\\": "" + "Symfony\\Component\\EventDispatcher\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -1835,33 +2731,26 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Console Component", + "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2017-07-03T13:19:36+00:00" + "time": "2017-11-05T15:47:03+00:00" }, { - "name": "symfony/debug", - "version": "v3.3.5", + "name": "symfony/filesystem", + "version": "v3.3.12", "source": { "type": "git", - "url": "https://github.com/symfony/debug.git", - "reference": "63b85a968486d95ff9542228dc2e4247f16f9743" + "url": "https://github.com/symfony/filesystem.git", + "reference": "77db266766b54db3ee982fe51868328b887ce15c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/63b85a968486d95ff9542228dc2e4247f16f9743", - "reference": "63b85a968486d95ff9542228dc2e4247f16f9743", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/77db266766b54db3ee982fe51868328b887ce15c", + "reference": "77db266766b54db3ee982fe51868328b887ce15c", "shasum": "" }, "require": { - "php": ">=5.5.9", - "psr/log": "~1.0" - }, - "conflict": { - "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" - }, - "require-dev": { - "symfony/http-kernel": "~2.8|~3.0" + "php": "^5.5.9|>=7.0.8" }, "type": "library", "extra": { @@ -1871,7 +2760,7 @@ }, "autoload": { "psr-4": { - "Symfony\\Component\\Debug\\": "" + "Symfony\\Component\\Filesystem\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -1891,40 +2780,26 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Debug Component", + "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2017-07-05T13:02:37+00:00" + "time": "2017-11-07T14:12:55+00:00" }, { - "name": "symfony/event-dispatcher", - "version": "v3.3.5", + "name": "symfony/finder", + "version": "v3.3.12", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "67535f1e3fd662bdc68d7ba317c93eecd973617e" + "url": "https://github.com/symfony/finder.git", + "reference": "138af5ec075d4b1d1bd19de08c38a34bb2d7d880" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/67535f1e3fd662bdc68d7ba317c93eecd973617e", - "reference": "67535f1e3fd662bdc68d7ba317c93eecd973617e", + "url": "https://api.github.com/repos/symfony/finder/zipball/138af5ec075d4b1d1bd19de08c38a34bb2d7d880", + "reference": "138af5ec075d4b1d1bd19de08c38a34bb2d7d880", "shasum": "" }, "require": { - "php": ">=5.5.9" - }, - "conflict": { - "symfony/dependency-injection": "<3.3" - }, - "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~2.8|~3.0", - "symfony/dependency-injection": "~3.3", - "symfony/expression-language": "~2.8|~3.0", - "symfony/stopwatch": "~2.8|~3.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" + "php": "^5.5.9|>=7.0.8" }, "type": "library", "extra": { @@ -1934,7 +2809,7 @@ }, "autoload": { "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" + "Symfony\\Component\\Finder\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -1954,26 +2829,30 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony EventDispatcher Component", + "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2017-06-09T14:53:08+00:00" + "time": "2017-11-05T15:47:03+00:00" }, { - "name": "symfony/filesystem", - "version": "v3.3.5", + "name": "symfony/http-foundation", + "version": "v3.3.12", "source": { "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "427987eb4eed764c3b6e38d52a0f87989e010676" + "url": "https://github.com/symfony/http-foundation.git", + "reference": "5943f0f19817a7e05992d20a90729b0dc93faf36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/427987eb4eed764c3b6e38d52a0f87989e010676", - "reference": "427987eb4eed764c3b6e38d52a0f87989e010676", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/5943f0f19817a7e05992d20a90729b0dc93faf36", + "reference": "5943f0f19817a7e05992d20a90729b0dc93faf36", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-mbstring": "~1.1" + }, + "require-dev": { + "symfony/expression-language": "~2.8|~3.0" }, "type": "library", "extra": { @@ -1983,7 +2862,7 @@ }, "autoload": { "psr-4": { - "Symfony\\Component\\Filesystem\\": "" + "Symfony\\Component\\HttpFoundation\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -2003,26 +2882,63 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Filesystem Component", + "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2017-07-11T07:17:58+00:00" + "time": "2017-11-13T18:13:16+00:00" }, { - "name": "symfony/finder", - "version": "v3.3.5", + "name": "symfony/http-kernel", + "version": "v3.3.12", "source": { "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4" + "url": "https://github.com/symfony/http-kernel.git", + "reference": "371ed63691c1ee8749613a6b48cf0e0cfa2b01e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/baea7f66d30854ad32988c11a09d7ffd485810c4", - "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/371ed63691c1ee8749613a6b48cf0e0cfa2b01e7", + "reference": "371ed63691c1ee8749613a6b48cf0e0cfa2b01e7", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^5.5.9|>=7.0.8", + "psr/log": "~1.0", + "symfony/debug": "~2.8|~3.0", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/http-foundation": "^3.3.11" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.3", + "symfony/var-dumper": "<3.3", + "twig/twig": "<1.34|<2.4,>=2" + }, + "require-dev": { + "psr/cache": "~1.0", + "symfony/browser-kit": "~2.8|~3.0", + "symfony/class-loader": "~2.8|~3.0", + "symfony/config": "~2.8|~3.0", + "symfony/console": "~2.8|~3.0", + "symfony/css-selector": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/dom-crawler": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/finder": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0", + "symfony/routing": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0", + "symfony/templating": "~2.8|~3.0", + "symfony/translation": "~2.8|~3.0", + "symfony/var-dumper": "~3.3" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/class-loader": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "", + "symfony/var-dumper": "" }, "type": "library", "extra": { @@ -2032,7 +2948,7 @@ }, "autoload": { "psr-4": { - "Symfony\\Component\\Finder\\": "" + "Symfony\\Component\\HttpKernel\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -2052,26 +2968,26 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Finder Component", + "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2017-06-01T21:01:25+00:00" + "time": "2017-11-13T19:37:21+00:00" }, { "name": "symfony/options-resolver", - "version": "v3.3.5", + "version": "v3.3.12", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "ff48982d295bcac1fd861f934f041ebc73ae40f0" + "reference": "623d9c210a137205f7e6e98166105625402cbb2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/ff48982d295bcac1fd861f934f041ebc73ae40f0", - "reference": "ff48982d295bcac1fd861f934f041ebc73ae40f0", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/623d9c210a137205f7e6e98166105625402cbb2f", + "reference": "623d9c210a137205f7e6e98166105625402cbb2f", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^5.5.9|>=7.0.8" }, "type": "library", "extra": { @@ -2108,20 +3024,20 @@ "configuration", "options" ], - "time": "2017-04-12T14:14:56+00:00" + "time": "2017-11-05T15:47:03+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.4.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "f29dca382a6485c3cbe6379f0c61230167681937" + "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f29dca382a6485c3cbe6379f0c61230167681937", - "reference": "f29dca382a6485c3cbe6379f0c61230167681937", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", + "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", "shasum": "" }, "require": { @@ -2133,7 +3049,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -2167,20 +3083,20 @@ "portable", "shim" ], - "time": "2017-06-09T14:24:12+00:00" + "time": "2017-10-11T12:05:26+00:00" }, { "name": "symfony/polyfill-php70", - "version": "v1.4.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "032fd647d5c11a9ceab8ee8747e13b5448e93874" + "reference": "0442b9c0596610bd24ae7b5f0a6cdbbc16d9fcff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/032fd647d5c11a9ceab8ee8747e13b5448e93874", - "reference": "032fd647d5c11a9ceab8ee8747e13b5448e93874", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/0442b9c0596610bd24ae7b5f0a6cdbbc16d9fcff", + "reference": "0442b9c0596610bd24ae7b5f0a6cdbbc16d9fcff", "shasum": "" }, "require": { @@ -2190,7 +3106,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -2226,20 +3142,20 @@ "portable", "shim" ], - "time": "2017-06-09T14:24:12+00:00" + "time": "2017-10-11T12:05:26+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.4.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "d3a71580c1e2cab33b6d705f0ec40e9015e14d5c" + "reference": "6de4f4884b97abbbed9f0a84a95ff2ff77254254" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/d3a71580c1e2cab33b6d705f0ec40e9015e14d5c", - "reference": "d3a71580c1e2cab33b6d705f0ec40e9015e14d5c", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/6de4f4884b97abbbed9f0a84a95ff2ff77254254", + "reference": "6de4f4884b97abbbed9f0a84a95ff2ff77254254", "shasum": "" }, "require": { @@ -2248,7 +3164,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -2281,24 +3197,24 @@ "portable", "shim" ], - "time": "2017-06-09T08:25:21+00:00" + "time": "2017-10-11T12:05:26+00:00" }, { "name": "symfony/process", - "version": "v3.3.5", + "version": "v3.3.12", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "07432804942b9f6dd7b7377faf9920af5f95d70a" + "reference": "a56a3989fb762d7b19a0cf8e7693ee99a6ffb78d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/07432804942b9f6dd7b7377faf9920af5f95d70a", - "reference": "07432804942b9f6dd7b7377faf9920af5f95d70a", + "url": "https://api.github.com/repos/symfony/process/zipball/a56a3989fb762d7b19a0cf8e7693ee99a6ffb78d", + "reference": "a56a3989fb762d7b19a0cf8e7693ee99a6ffb78d", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^5.5.9|>=7.0.8" }, "type": "library", "extra": { @@ -2330,24 +3246,24 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2017-07-13T13:05:09+00:00" + "time": "2017-11-13T15:31:11+00:00" }, { "name": "symfony/stopwatch", - "version": "v3.3.5", + "version": "v3.3.12", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "602a15299dc01556013b07167d4f5d3a60e90d15" + "reference": "1e93c3139ef6c799831fe03efd0fb1c7aecb3365" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/602a15299dc01556013b07167d4f5d3a60e90d15", - "reference": "602a15299dc01556013b07167d4f5d3a60e90d15", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/1e93c3139ef6c799831fe03efd0fb1c7aecb3365", + "reference": "1e93c3139ef6c799831fe03efd0fb1c7aecb3365", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^5.5.9|>=7.0.8" }, "type": "library", "extra": { @@ -2379,7 +3295,7 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2017-04-12T14:14:56+00:00" + "time": "2017-11-10T19:02:53+00:00" }, { "name": "symfony/yaml", @@ -2436,6 +3352,259 @@ "homepage": "https://symfony.com", "time": "2017-11-10T18:26:04+00:00" }, + { + "name": "symplify/coding-standard", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/Symplify/CodingStandard.git", + "reference": "309fd562066cdc86b81375ff080b1ee2f900778e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Symplify/CodingStandard/zipball/309fd562066cdc86b81375ff080b1ee2f900778e", + "reference": "309fd562066cdc86b81375ff080b1ee2f900778e", + "shasum": "" + }, + "require": { + "friendsofphp/php-cs-fixer": "^2.8", + "nette/finder": "^2.4|^3.0", + "nette/utils": "^2.4|^3.0", + "php": "^7.1", + "slevomat/coding-standard": "^4.0", + "squizlabs/php_codesniffer": "^3.1" + }, + "require-dev": { + "gecko-packages/gecko-php-unit": "3.0 as 2.2", + "nette/application": "^2.4", + "phpunit/phpunit": "^6.4", + "symplify/easy-coding-standard": "^2.5|^3.0", + "symplify/package-builder": "^2.5|^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symplify\\CodingStandard\\": "src", + "Symplify\\CodingStandard\\SniffTokenWrapper\\": "packages/SniffTokenWrapper/src", + "Symplify\\CodingStandard\\FixerTokenWrapper\\": "packages/FixerTokenWrapper/src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Set of Symplify rules for PHP_CodeSniffer.", + "time": "2017-11-16 00:38:24" + }, + { + "name": "symplify/easy-coding-standard", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/Symplify/EasyCodingStandard.git", + "reference": "4bac5271050f063b4455bd870cc215e0db57ddf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Symplify/EasyCodingStandard/zipball/4bac5271050f063b4455bd870cc215e0db57ddf8", + "reference": "4bac5271050f063b4455bd870cc215e0db57ddf8", + "shasum": "" + }, + "require": { + "friendsofphp/php-cs-fixer": "^2.8", + "nette/caching": "^2.4|^3.0", + "nette/di": "^2.4|^3.0", + "nette/neon": "^2.4|^3.0", + "nette/robot-loader": "^2.4|^3.0.1", + "nette/utils": "^2.4|^3.0", + "php": "^7.1", + "sebastian/diff": "^1.4|^2.0", + "slevomat/coding-standard": "^4.0", + "squizlabs/php_codesniffer": "^3.1", + "symfony/config": "^3.3|^4.0", + "symfony/console": "^3.3|^4.0", + "symfony/dependency-injection": "^3.3|^4.0", + "symfony/finder": "^3.3|^4.0", + "symfony/http-kernel": "^3.3|^4.0", + "symfony/yaml": "^3.3|^4.0", + "symplify/coding-standard": "^2.5|^3.0", + "symplify/package-builder": "^2.5|^3.0", + "tracy/tracy": "^2.4|^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "bin": [ + "bin/easy-coding-standard", + "bin/ecs", + "bin/easy-coding-standard.php" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symplify\\EasyCodingStandard\\": "src", + "Symplify\\EasyCodingStandard\\ChangedFilesDetector\\": "packages/ChangedFilesDetector/src", + "Symplify\\EasyCodingStandard\\Configuration\\": "packages/Configuration/src", + "Symplify\\EasyCodingStandard\\FixerRunner\\": "packages/FixerRunner/src", + "Symplify\\EasyCodingStandard\\SniffRunner\\": "packages/SniffRunner/src", + "Symplify\\EasyCodingStandard\\Performance\\": "packages/Performance/src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Use Coding Standard with 0-knowledge of PHP-CS-Fixer and PHP_CodeSniffer.", + "time": "2017-11-16 15:36:21" + }, + { + "name": "symplify/package-builder", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/Symplify/PackageBuilder.git", + "reference": "3604beadddfdee295b978d87475a834810a0ffd4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Symplify/PackageBuilder/zipball/3604beadddfdee295b978d87475a834810a0ffd4", + "reference": "3604beadddfdee295b978d87475a834810a0ffd4", + "shasum": "" + }, + "require": { + "nette/di": "^2.4|^3.0", + "nette/neon": "^2.4|^3.0", + "php": "^7.1", + "symfony/config": "^3.3|^4.0", + "symfony/console": "^3.3|^4.0", + "symfony/dependency-injection": "^3.3|^4.0", + "symfony/http-kernel": "^3.3|^4.0", + "symfony/yaml": "^3.3|^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.4", + "tracy/tracy": "^2.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symplify\\PackageBuilder\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Dependency Injection, Console and Kernel toolkit for Symplify packages.", + "time": "2017-11-16T01:05:48+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2017-04-07T12:08:54+00:00" + }, + { + "name": "tracy/tracy", + "version": "v2.4.10", + "source": { + "type": "git", + "url": "https://github.com/nette/tracy.git", + "reference": "5b302790edd71924dfe4ec44f499ef61df3f53a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/tracy/zipball/5b302790edd71924dfe4ec44f499ef61df3f53a2", + "reference": "5b302790edd71924dfe4ec44f499ef61df3f53a2", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-session": "*", + "php": ">=5.4.4" + }, + "require-dev": { + "nette/di": "~2.3", + "nette/tester": "~1.7" + }, + "suggest": { + "https://nette.org/donate": "Please support Tracy via a donation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "classmap": [ + "src" + ], + "files": [ + "src/shortcuts.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "😎 Tracy: the addictive tool to ease debugging PHP code for cool developers. Friendly design, logging, profiler, advanced features like debugging AJAX calls or CLI support. You will love it.", + "homepage": "https://tracy.nette.org", + "keywords": [ + "Xdebug", + "debug", + "debugger", + "nette", + "profiler" + ], + "time": "2017-10-04T18:43:42+00:00" + }, { "name": "webmozart/assert", "version": "1.2.0", @@ -2487,9 +3656,32 @@ "time": "2016-11-23T20:04:58+00:00" } ], - "aliases": [], + "aliases": [ + { + "alias": "2.5", + "alias_normalized": "2.5.0.0", + "version": "9999999-dev", + "package": "symplify/easy-coding-standard" + }, + { + "alias": "2.5", + "alias_normalized": "2.5.0.0", + "version": "9999999-dev", + "package": "symplify/coding-standard" + }, + { + "alias": "2.5", + "alias_normalized": "2.5.0.0", + "version": "9999999-dev", + "package": "symplify/package-builder" + } + ], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "symplify/easy-coding-standard": 20, + "symplify/coding-standard": 20, + "symplify/package-builder": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/easy-coding-standard.neon b/easy-coding-standard.neon new file mode 100644 index 00000000..14d87709 --- /dev/null +++ b/easy-coding-standard.neon @@ -0,0 +1,39 @@ +includes: + - vendor/symplify/easy-coding-standard/config/psr2.neon + - vendor/symplify/easy-coding-standard/config/php70.neon + - vendor/symplify/easy-coding-standard/config/clean-code.neon + - vendor/symplify/easy-coding-standard/config/common/array.neon + - vendor/symplify/easy-coding-standard/config/common/docblock.neon + - vendor/symplify/easy-coding-standard/config/common/namespaces.neon + - vendor/symplify/easy-coding-standard/config/common/control-structures.neon + + # many errors, need help + #- vendor/symplify/easy-coding-standard/config/common/strict.neon + +checkers: + - Symplify\CodingStandard\Fixer\Import\ImportNamespacedNameFixer + - Symplify\CodingStandard\Fixer\Php\ClassStringToClassConstantFixer + - Symplify\CodingStandard\Fixer\Property\ArrayPropertyDefaultValueFixer + - Symplify\CodingStandard\Fixer\ClassNotation\PropertyAndConstantSeparationFixer + - Symplify\CodingStandard\Fixer\ArrayNotation\StandaloneLineInMultilineArrayFixer + +parameters: + exclude_checkers: + # from strict.neon + - PhpCsFixer\Fixer\PhpUnit\PhpUnitStrictFixer + skip: + PhpCsFixer\Fixer\Alias\RandomApiMigrationFixer: + # random_int() breaks code + - src/Phpml/CrossValidation/RandomSplit.php + SlevomatCodingStandard\Sniffs\Classes\UnusedPrivateElementsSniff: + # magic calls + - src/Phpml/Preprocessing/Normalizer.php + + skip_codes: + # missing typehints + - SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingParameterTypeHint + - SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingTraversableParameterTypeHintSpecification + - SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingReturnTypeHint + - SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingTraversableReturnTypeHintSpecification + - SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingPropertyTypeHint + - SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingTraversablePropertyTypeHintSpecification diff --git a/phpunit.xml b/phpunit.xml index cbf6c185..455f8bbc 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,11 +6,9 @@ beStrictAboutTestSize="true" beStrictAboutChangesToGlobalState="true" > - - - tests/* - - ​ + + tests/* + diff --git a/src/Phpml/Association/Apriori.php b/src/Phpml/Association/Apriori.php index f1398d2e..e13f556f 100644 --- a/src/Phpml/Association/Apriori.php +++ b/src/Phpml/Association/Apriori.php @@ -31,7 +31,7 @@ class Apriori implements Associator * * @var mixed[][][] */ - private $large; + private $large = []; /** * Minimum relative frequency of transactions. @@ -45,7 +45,7 @@ class Apriori implements Associator * * @var mixed[][] */ - private $rules; + private $rules = []; /** * Apriori constructor. @@ -61,7 +61,7 @@ public function __construct(float $support = 0.0, float $confidence = 0.0) * * @return mixed[][] */ - public function getRules() : array + public function getRules(): array { if (!$this->large) { $this->large = $this->apriori(); @@ -83,7 +83,7 @@ public function getRules() : array * * @return mixed[][][] */ - public function apriori() : array + public function apriori(): array { $L = []; $L[1] = $this->items(); @@ -102,7 +102,7 @@ public function apriori() : array * * @return mixed[][] */ - protected function predictSample(array $sample) : array + protected function predictSample(array $sample): array { $predicts = array_values(array_filter($this->getRules(), function ($rule) use ($sample) { return $this->equals($rule[self::ARRAY_KEY_ANTECEDENT], $sample); @@ -133,7 +133,8 @@ private function generateAllRules(): void private function generateRules(array $frequent): void { foreach ($this->antecedents($frequent) as $antecedent) { - if ($this->confidence <= ($confidence = $this->confidence($frequent, $antecedent))) { + $confidence = $this->confidence($frequent, $antecedent); + if ($this->confidence <= $confidence) { $consequent = array_values(array_diff($frequent, $antecedent)); $this->rules[] = [ self::ARRAY_KEY_ANTECEDENT => $antecedent, @@ -152,7 +153,7 @@ private function generateRules(array $frequent): void * * @return mixed[][] */ - private function powerSet(array $sample) : array + private function powerSet(array $sample): array { $results = [[]]; foreach ($sample as $item) { @@ -171,7 +172,7 @@ private function powerSet(array $sample) : array * * @return mixed[][] */ - private function antecedents(array $sample) : array + private function antecedents(array $sample): array { $cardinality = count($sample); $antecedents = $this->powerSet($sample); @@ -186,7 +187,7 @@ private function antecedents(array $sample) : array * * @return mixed[][] */ - private function items() : array + private function items(): array { $items = []; @@ -210,7 +211,7 @@ private function items() : array * * @return mixed[][] */ - private function frequent(array $samples) : array + private function frequent(array $samples): array { return array_filter($samples, function ($entry) { return $this->support($entry) >= $this->support; @@ -224,7 +225,7 @@ private function frequent(array $samples) : array * * @return mixed[][] */ - private function candidates(array $samples) : array + private function candidates(array $samples): array { $candidates = []; @@ -259,7 +260,7 @@ private function candidates(array $samples) : array * @param mixed[] $set * @param mixed[] $subset */ - private function confidence(array $set, array $subset) : float + private function confidence(array $set, array $subset): float { return $this->support($set) / $this->support($subset); } @@ -272,7 +273,7 @@ private function confidence(array $set, array $subset) : float * * @param mixed[] $sample */ - private function support(array $sample) : float + private function support(array $sample): float { return $this->frequency($sample) / count($this->samples); } @@ -284,7 +285,7 @@ private function support(array $sample) : float * * @param mixed[] $sample */ - private function frequency(array $sample) : int + private function frequency(array $sample): int { return count(array_filter($this->samples, function ($entry) use ($sample) { return $this->subset($entry, $sample); @@ -299,7 +300,7 @@ private function frequency(array $sample) : int * @param mixed[][] $system * @param mixed[] $set */ - private function contains(array $system, array $set) : bool + private function contains(array $system, array $set): bool { return (bool) array_filter($system, function ($entry) use ($set) { return $this->equals($entry, $set); @@ -312,7 +313,7 @@ private function contains(array $system, array $set) : bool * @param mixed[] $set * @param mixed[] $subset */ - private function subset(array $set, array $subset) : bool + private function subset(array $set, array $subset): bool { return !array_diff($subset, array_intersect($subset, $set)); } @@ -323,7 +324,7 @@ private function subset(array $set, array $subset) : bool * @param mixed[] $set1 * @param mixed[] $set2 */ - private function equals(array $set1, array $set2) : bool + private function equals(array $set1, array $set2): bool { return array_diff($set1, $set2) == array_diff($set2, $set1); } diff --git a/src/Phpml/Classification/DecisionTree.php b/src/Phpml/Classification/DecisionTree.php index 653b1bf5..5bb730b6 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -15,22 +15,18 @@ class DecisionTree implements Classifier use Trainable, Predictable; public const CONTINUOUS = 1; + public const NOMINAL = 2; /** - * @var array + * @var int */ - protected $columnTypes; + public $actualDepth = 0; /** * @var array */ - private $labels = []; - - /** - * @var int - */ - private $featureCount = 0; + protected $columnTypes = []; /** * @var DecisionTreeLeaf @@ -42,10 +38,15 @@ class DecisionTree implements Classifier */ protected $maxDepth; + /** + * @var array + */ + private $labels = []; + /** * @var int */ - public $actualDepth = 0; + private $featureCount = 0; /** * @var int @@ -55,7 +56,7 @@ class DecisionTree implements Classifier /** * @var array */ - private $selectedFeatures; + private $selectedFeatures = []; /** * @var array @@ -100,7 +101,7 @@ public function train(array $samples, array $targets): void } } - public static function getColumnTypes(array $samples) : array + public static function getColumnTypes(array $samples): array { $types = []; $featureCount = count($samples[0]); @@ -113,7 +114,122 @@ public static function getColumnTypes(array $samples) : array return $types; } - protected function getSplitLeaf(array $records, int $depth = 0) : DecisionTreeLeaf + /** + * @param mixed $baseValue + */ + public function getGiniIndex($baseValue, array $colValues, array $targets): float + { + $countMatrix = []; + foreach ($this->labels as $label) { + $countMatrix[$label] = [0, 0]; + } + + foreach ($colValues as $index => $value) { + $label = $targets[$index]; + $rowIndex = $value === $baseValue ? 0 : 1; + ++$countMatrix[$label][$rowIndex]; + } + + $giniParts = [0, 0]; + for ($i = 0; $i <= 1; ++$i) { + $part = 0; + $sum = array_sum(array_column($countMatrix, $i)); + if ($sum > 0) { + foreach ($this->labels as $label) { + $part += pow($countMatrix[$label][$i] / (float) $sum, 2); + } + } + + $giniParts[$i] = (1 - $part) * $sum; + } + + return array_sum($giniParts) / count($colValues); + } + + /** + * This method is used to set number of columns to be used + * when deciding a split at an internal node of the tree.
+ * If the value is given 0, then all features are used (default behaviour), + * otherwise the given value will be used as a maximum for number of columns + * randomly selected for each split operation. + * + * @return $this + * + * @throws InvalidArgumentException + */ + public function setNumFeatures(int $numFeatures) + { + if ($numFeatures < 0) { + throw new InvalidArgumentException('Selected column count should be greater or equal to zero'); + } + + $this->numUsableFeatures = $numFeatures; + + return $this; + } + + /** + * A string array to represent columns. Useful when HTML output or + * column importances are desired to be inspected. + * + * @return $this + * + * @throws InvalidArgumentException + */ + public function setColumnNames(array $names) + { + if ($this->featureCount !== 0 && count($names) !== $this->featureCount) { + throw new InvalidArgumentException(sprintf('Length of the given array should be equal to feature count %s', $this->featureCount)); + } + + $this->columnNames = $names; + + return $this; + } + + public function getHtml(): string + { + return $this->tree->getHTML($this->columnNames); + } + + /** + * This will return an array including an importance value for + * each column in the given dataset. The importance values are + * normalized and their total makes 1.
+ */ + public function getFeatureImportances(): array + { + if ($this->featureImportances !== null) { + return $this->featureImportances; + } + + $sampleCount = count($this->samples); + $this->featureImportances = []; + foreach ($this->columnNames as $column => $columnName) { + $nodes = $this->getSplitNodesByColumn($column, $this->tree); + + $importance = 0; + foreach ($nodes as $node) { + $importance += $node->getNodeImpurityDecrease($sampleCount); + } + + $this->featureImportances[$columnName] = $importance; + } + + // Normalize & sort the importances + $total = array_sum($this->featureImportances); + if ($total > 0) { + foreach ($this->featureImportances as &$importance) { + $importance /= $total; + } + + arsort($this->featureImportances); + } + + return $this->featureImportances; + } + + protected function getSplitLeaf(array $records, int $depth = 0): DecisionTreeLeaf { $split = $this->getBestSplit($records); $split->level = $depth; @@ -136,6 +252,7 @@ protected function getSplitLeaf(array $records, int $depth = 0) : DecisionTreeLe if ($prevRecord && $prevRecord != $record) { $allSame = false; } + $prevRecord = $record; // According to the split criteron, this record will @@ -163,6 +280,7 @@ protected function getSplitLeaf(array $records, int $depth = 0) : DecisionTreeLe if ($leftRecords) { $split->leftLeaf = $this->getSplitLeaf($leftRecords, $depth + 1); } + if ($rightRecords) { $split->rightLeaf = $this->getSplitLeaf($rightRecords, $depth + 1); } @@ -171,7 +289,7 @@ protected function getSplitLeaf(array $records, int $depth = 0) : DecisionTreeLe return $split; } - protected function getBestSplit(array $records) : DecisionTreeLeaf + protected function getBestSplit(array $records): DecisionTreeLeaf { $targets = array_intersect_key($this->targets, array_flip($records)); $samples = array_intersect_key($this->samples, array_flip($records)); @@ -184,6 +302,7 @@ protected function getBestSplit(array $records) : DecisionTreeLeaf foreach ($samples as $index => $row) { $colValues[$index] = $row[$i]; } + $counts = array_count_values($colValues); arsort($counts); $baseValue = key($counts); @@ -227,7 +346,7 @@ protected function getBestSplit(array $records) : DecisionTreeLeaf * If any of above methods were not called beforehand, then all features * are returned by default. */ - protected function getSelectedFeatures() : array + protected function getSelectedFeatures(): array { $allFeatures = range(0, $this->featureCount - 1); if ($this->numUsableFeatures === 0 && !$this->selectedFeatures) { @@ -242,6 +361,7 @@ protected function getSelectedFeatures() : array if ($numFeatures > $this->featureCount) { $numFeatures = $this->featureCount; } + shuffle($allFeatures); $selectedFeatures = array_slice($allFeatures, 0, $numFeatures, false); sort($selectedFeatures); @@ -249,39 +369,7 @@ protected function getSelectedFeatures() : array return $selectedFeatures; } - /** - * @param mixed $baseValue - */ - public function getGiniIndex($baseValue, array $colValues, array $targets) : float - { - $countMatrix = []; - foreach ($this->labels as $label) { - $countMatrix[$label] = [0, 0]; - } - - foreach ($colValues as $index => $value) { - $label = $targets[$index]; - $rowIndex = $value === $baseValue ? 0 : 1; - ++$countMatrix[$label][$rowIndex]; - } - - $giniParts = [0, 0]; - for ($i = 0; $i <= 1; ++$i) { - $part = 0; - $sum = array_sum(array_column($countMatrix, $i)); - if ($sum > 0) { - foreach ($this->labels as $label) { - $part += pow($countMatrix[$label][$i] / (float) $sum, 2); - } - } - - $giniParts[$i] = (1 - $part) * $sum; - } - - return array_sum($giniParts) / count($colValues); - } - - protected function preprocess(array $samples) : array + protected function preprocess(array $samples): array { // Detect and convert continuous data column values into // discrete values by using the median as a threshold value @@ -298,14 +386,16 @@ protected function preprocess(array $samples) : array } } } + $columns[] = $values; } + // Below method is a strange yet very simple & efficient method // to get the transpose of a 2D array return array_map(null, ...$columns); } - protected static function isCategoricalColumn(array $columnValues) : bool + protected static function isCategoricalColumn(array $columnValues): bool { $count = count($columnValues); @@ -329,28 +419,6 @@ protected static function isCategoricalColumn(array $columnValues) : bool return count($distinctValues) <= $count / 5; } - /** - * This method is used to set number of columns to be used - * when deciding a split at an internal node of the tree.
- * If the value is given 0, then all features are used (default behaviour), - * otherwise the given value will be used as a maximum for number of columns - * randomly selected for each split operation. - * - * @return $this - * - * @throws InvalidArgumentException - */ - public function setNumFeatures(int $numFeatures) - { - if ($numFeatures < 0) { - throw new InvalidArgumentException('Selected column count should be greater or equal to zero'); - } - - $this->numUsableFeatures = $numFeatures; - - return $this; - } - /** * Used to set predefined features to consider while deciding which column to use for a split */ @@ -359,71 +427,11 @@ protected function setSelectedFeatures(array $selectedFeatures): void $this->selectedFeatures = $selectedFeatures; } - /** - * A string array to represent columns. Useful when HTML output or - * column importances are desired to be inspected. - * - * @return $this - * - * @throws InvalidArgumentException - */ - public function setColumnNames(array $names) - { - if ($this->featureCount !== 0 && count($names) !== $this->featureCount) { - throw new InvalidArgumentException(sprintf('Length of the given array should be equal to feature count %s', $this->featureCount)); - } - - $this->columnNames = $names; - - return $this; - } - - public function getHtml() : string - { - return $this->tree->getHTML($this->columnNames); - } - - /** - * This will return an array including an importance value for - * each column in the given dataset. The importance values are - * normalized and their total makes 1.
- */ - public function getFeatureImportances() : array - { - if ($this->featureImportances !== null) { - return $this->featureImportances; - } - - $sampleCount = count($this->samples); - $this->featureImportances = []; - foreach ($this->columnNames as $column => $columnName) { - $nodes = $this->getSplitNodesByColumn($column, $this->tree); - - $importance = 0; - foreach ($nodes as $node) { - $importance += $node->getNodeImpurityDecrease($sampleCount); - } - - $this->featureImportances[$columnName] = $importance; - } - - // Normalize & sort the importances - $total = array_sum($this->featureImportances); - if ($total > 0) { - foreach ($this->featureImportances as &$importance) { - $importance /= $total; - } - arsort($this->featureImportances); - } - - return $this->featureImportances; - } - /** * Collects and returns an array of internal nodes that use the given * column as a split criterion */ - protected function getSplitNodesByColumn(int $column, DecisionTreeLeaf $node) : array + protected function getSplitNodesByColumn(int $column, DecisionTreeLeaf $node): array { if (!$node || $node->isTerminal) { return []; diff --git a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php index 2bcc3ac1..f3f9449c 100644 --- a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -71,7 +71,15 @@ class DecisionTreeLeaf */ public $level = 0; - public function evaluate(array $record) : bool + /** + * HTML representation of the tree without column names + */ + public function __toString(): string + { + return $this->getHTML(); + } + + public function evaluate(array $record): bool { $recordField = $record[$this->columnIndex]; @@ -86,7 +94,7 @@ public function evaluate(array $record) : bool * Returns Mean Decrease Impurity (MDI) in the node. * For terminal nodes, this value is equal to 0 */ - public function getNodeImpurityDecrease(int $parentRecordCount) : float + public function getNodeImpurityDecrease(int $parentRecordCount): float { if ($this->isTerminal) { return 0.0; @@ -111,7 +119,7 @@ public function getNodeImpurityDecrease(int $parentRecordCount) : float /** * Returns HTML representation of the node including children nodes */ - public function getHTML($columnNames = null) : string + public function getHTML($columnNames = null): string { if ($this->isTerminal) { $value = "$this->classValue"; @@ -154,12 +162,4 @@ public function getHTML($columnNames = null) : string return $str; } - - /** - * HTML representation of the tree without column names - */ - public function __toString() : string - { - return $this->getHTML(); - } } diff --git a/src/Phpml/Classification/Ensemble/AdaBoost.php b/src/Phpml/Classification/Ensemble/AdaBoost.php index 5bdca1b1..67f71983 100644 --- a/src/Phpml/Classification/Ensemble/AdaBoost.php +++ b/src/Phpml/Classification/Ensemble/AdaBoost.php @@ -4,6 +4,7 @@ namespace Phpml\Classification\Ensemble; +use Exception; use Phpml\Classification\Classifier; use Phpml\Classification\Linear\DecisionStump; use Phpml\Classification\WeightedClassifier; @@ -11,6 +12,7 @@ use Phpml\Helper\Trainable; use Phpml\Math\Statistic\Mean; use Phpml\Math\Statistic\StandardDeviation; +use ReflectionClass; class AdaBoost implements Classifier { @@ -98,11 +100,14 @@ public function train(array $samples, array $targets): void // Initialize usual variables $this->labels = array_keys(array_count_values($targets)); if (count($this->labels) != 2) { - throw new \Exception('AdaBoost is a binary classifier and can classify between two classes only'); + throw new Exception('AdaBoost is a binary classifier and can classify between two classes only'); } // Set all target values to either -1 or 1 - $this->labels = [1 => $this->labels[0], -1 => $this->labels[1]]; + $this->labels = [ + 1 => $this->labels[0], + -1 => $this->labels[1], + ]; foreach ($targets as $target) { $this->targets[] = $target == $this->labels[1] ? 1 : -1; } @@ -132,13 +137,27 @@ public function train(array $samples, array $targets): void } } + /** + * @return mixed + */ + public function predictSample(array $sample) + { + $sum = 0; + foreach ($this->alpha as $index => $alpha) { + $h = $this->classifiers[$index]->predict($sample); + $sum += $h * $alpha; + } + + return $this->labels[$sum > 0 ? 1 : -1]; + } + /** * Returns the classifier with the lowest error rate with the * consideration of current sample weights */ - protected function getBestClassifier() : Classifier + protected function getBestClassifier(): Classifier { - $ref = new \ReflectionClass($this->baseClassifier); + $ref = new ReflectionClass($this->baseClassifier); if ($this->classifierOptions) { $classifier = $ref->newInstanceArgs($this->classifierOptions); } else { @@ -160,7 +179,7 @@ protected function getBestClassifier() : Classifier * Resamples the dataset in accordance with the weights and * returns the new dataset */ - protected function resample() : array + protected function resample(): array { $weights = $this->weights; $std = StandardDeviation::population($weights); @@ -173,9 +192,10 @@ protected function resample() : array foreach ($weights as $index => $weight) { $z = (int) round(($weight - $mean) / $std) - $minZ + 1; for ($i = 0; $i < $z; ++$i) { - if (rand(0, 1) == 0) { + if (random_int(0, 1) == 0) { continue; } + $samples[] = $this->samples[$index]; $targets[] = $this->targets[$index]; } @@ -187,7 +207,7 @@ protected function resample() : array /** * Evaluates the classifier and returns the classification error rate */ - protected function evaluateClassifier(Classifier $classifier) : float + protected function evaluateClassifier(Classifier $classifier): float { $total = (float) array_sum($this->weights); $wrong = 0; @@ -204,7 +224,7 @@ protected function evaluateClassifier(Classifier $classifier) : float /** * Calculates alpha of a classifier */ - protected function calculateAlpha(float $errorRate) : float + protected function calculateAlpha(float $errorRate): float { if ($errorRate == 0) { $errorRate = 1e-10; @@ -231,18 +251,4 @@ protected function updateWeights(Classifier $classifier, float $alpha): void $this->weights = $weightsT1; } - - /** - * @return mixed - */ - public function predictSample(array $sample) - { - $sum = 0; - foreach ($this->alpha as $index => $alpha) { - $h = $this->classifiers[$index]->predict($sample); - $sum += $h * $alpha; - } - - return $this->labels[$sum > 0 ? 1 : -1]; - } } diff --git a/src/Phpml/Classification/Ensemble/Bagging.php b/src/Phpml/Classification/Ensemble/Bagging.php index 8d2bbf9b..6fa1ec80 100644 --- a/src/Phpml/Classification/Ensemble/Bagging.php +++ b/src/Phpml/Classification/Ensemble/Bagging.php @@ -4,10 +4,12 @@ namespace Phpml\Classification\Ensemble; +use Exception; use Phpml\Classification\Classifier; use Phpml\Classification\DecisionTree; use Phpml\Helper\Predictable; use Phpml\Helper\Trainable; +use ReflectionClass; class Bagging implements Classifier { @@ -18,11 +20,6 @@ class Bagging implements Classifier */ protected $numSamples; - /** - * @var array - */ - private $targets = []; - /** * @var int */ @@ -46,13 +43,18 @@ class Bagging implements Classifier /** * @var array */ - protected $classifiers; + protected $classifiers = []; /** * @var float */ protected $subsetRatio = 0.7; + /** + * @var array + */ + private $targets = []; + /** * @var array */ @@ -80,7 +82,7 @@ public function __construct(int $numClassifier = 50) public function setSubsetRatio(float $ratio) { if ($ratio < 0.1 || $ratio > 1.0) { - throw new \Exception('Subset ratio should be between 0.1 and 1.0'); + throw new Exception('Subset ratio should be between 0.1 and 1.0'); } $this->subsetRatio = $ratio; @@ -123,14 +125,14 @@ public function train(array $samples, array $targets): void } } - protected function getRandomSubset(int $index) : array + protected function getRandomSubset(int $index): array { $samples = []; $targets = []; srand($index); $bootstrapSize = $this->subsetRatio * $this->numSamples; for ($i = 0; $i < $bootstrapSize; ++$i) { - $rand = rand(0, $this->numSamples - 1); + $rand = random_int(0, $this->numSamples - 1); $samples[] = $this->samples[$rand]; $targets[] = $this->targets[$rand]; } @@ -138,11 +140,11 @@ protected function getRandomSubset(int $index) : array return [$samples, $targets]; } - protected function initClassifiers() : array + protected function initClassifiers(): array { $classifiers = []; for ($i = 0; $i < $this->numClassifier; ++$i) { - $ref = new \ReflectionClass($this->classifier); + $ref = new ReflectionClass($this->classifier); if ($this->classifierOptions) { $obj = $ref->newInstanceArgs($this->classifierOptions); } else { @@ -155,12 +157,7 @@ protected function initClassifiers() : array return $classifiers; } - /** - * @param Classifier $classifier - * - * @return Classifier - */ - protected function initSingleClassifier($classifier) + protected function initSingleClassifier(Classifier $classifier): Classifier { return $classifier; } diff --git a/src/Phpml/Classification/Ensemble/RandomForest.php b/src/Phpml/Classification/Ensemble/RandomForest.php index 4928ea54..59f19c13 100644 --- a/src/Phpml/Classification/Ensemble/RandomForest.php +++ b/src/Phpml/Classification/Ensemble/RandomForest.php @@ -4,6 +4,8 @@ namespace Phpml\Classification\Ensemble; +use Exception; +use Phpml\Classification\Classifier; use Phpml\Classification\DecisionTree; class RandomForest extends Bagging @@ -48,11 +50,11 @@ public function __construct(int $numClassifier = 50) public function setFeatureSubsetRatio($ratio) { if (is_float($ratio) && ($ratio < 0.1 || $ratio > 1.0)) { - throw new \Exception('When a float given, feature subset ratio should be between 0.1 and 1.0'); + throw new Exception('When a float given, feature subset ratio should be between 0.1 and 1.0'); } if (is_string($ratio) && $ratio != 'sqrt' && $ratio != 'log') { - throw new \Exception("When a string given, feature subset ratio can only be 'sqrt' or 'log' "); + throw new Exception("When a string given, feature subset ratio can only be 'sqrt' or 'log' "); } $this->featureSubsetRatio = $ratio; @@ -70,7 +72,7 @@ public function setFeatureSubsetRatio($ratio) public function setClassifer(string $classifier, array $classifierOptions = []) { if ($classifier != DecisionTree::class) { - throw new \Exception('RandomForest can only use DecisionTree as base classifier'); + throw new Exception('RandomForest can only use DecisionTree as base classifier'); } return parent::setClassifer($classifier, $classifierOptions); @@ -81,7 +83,7 @@ public function setClassifer(string $classifier, array $classifierOptions = []) * each column in the given dataset. Importance values for a column * is the average importance of that column in all trees in the forest */ - public function getFeatureImportances() : array + public function getFeatureImportances(): array { // Traverse each tree and sum importance of the columns $sum = []; @@ -127,7 +129,7 @@ public function setColumnNames(array $names) * * @return DecisionTree */ - protected function initSingleClassifier($classifier) + protected function initSingleClassifier(Classifier $classifier): Classifier { if (is_float($this->featureSubsetRatio)) { $featureCount = (int) ($this->featureSubsetRatio * $this->featureCount); diff --git a/src/Phpml/Classification/KNearestNeighbors.php b/src/Phpml/Classification/KNearestNeighbors.php index a261631c..238fc1d9 100644 --- a/src/Phpml/Classification/KNearestNeighbors.php +++ b/src/Phpml/Classification/KNearestNeighbors.php @@ -28,7 +28,7 @@ class KNearestNeighbors implements Classifier */ public function __construct(int $k = 3, ?Distance $distanceMetric = null) { - if (null === $distanceMetric) { + if ($distanceMetric === null) { $distanceMetric = new Euclidean(); } @@ -60,7 +60,7 @@ protected function predictSample(array $sample) /** * @throws \Phpml\Exception\InvalidArgumentException */ - private function kNeighborsDistances(array $sample) : array + private function kNeighborsDistances(array $sample): array { $distances = []; diff --git a/src/Phpml/Classification/Linear/Adaline.php b/src/Phpml/Classification/Linear/Adaline.php index 64d25a48..cda746f3 100644 --- a/src/Phpml/Classification/Linear/Adaline.php +++ b/src/Phpml/Classification/Linear/Adaline.php @@ -4,6 +4,8 @@ namespace Phpml\Classification\Linear; +use Exception; + class Adaline extends Perceptron { /** @@ -41,7 +43,7 @@ public function __construct( int $trainingType = self::BATCH_TRAINING ) { if (!in_array($trainingType, [self::BATCH_TRAINING, self::ONLINE_TRAINING])) { - throw new \Exception('Adaline can only be trained with batch and online/stochastic gradient descent algorithm'); + throw new Exception('Adaline can only be trained with batch and online/stochastic gradient descent algorithm'); } $this->trainingType = $trainingType; diff --git a/src/Phpml/Classification/Linear/DecisionStump.php b/src/Phpml/Classification/Linear/DecisionStump.php index e1486a65..3f6eb586 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -4,6 +4,7 @@ namespace Phpml\Classification\Linear; +use Exception; use Phpml\Classification\DecisionTree; use Phpml\Classification\WeightedClassifier; use Phpml\Helper\OneVsRest; @@ -24,7 +25,7 @@ class DecisionStump extends WeightedClassifier /** * @var array */ - protected $binaryLabels; + protected $binaryLabels = []; /** * Lowest error rate obtained while training/optimizing the model @@ -51,7 +52,7 @@ class DecisionStump extends WeightedClassifier /** * @var array */ - protected $columnTypes; + protected $columnTypes = []; /** * @var int @@ -68,7 +69,7 @@ class DecisionStump extends WeightedClassifier * * @var array */ - protected $prob; + protected $prob = []; /** * A DecisionStump classifier is a one-level deep DecisionTree. It is generally @@ -83,6 +84,25 @@ public function __construct(int $columnIndex = self::AUTO_SELECT) $this->givenColumnIndex = $columnIndex; } + public function __toString(): string + { + return "IF $this->column $this->operator $this->value ". + 'THEN '.$this->binaryLabels[0].' '. + 'ELSE '.$this->binaryLabels[1]; + } + + /** + * While finding best split point for a numerical valued column, + * DecisionStump looks for equally distanced values between minimum and maximum + * values in the column. Given $count value determines how many split + * points to be probed. The more split counts, the better performance but + * worse processing time (Default value is 10.0) + */ + public function setNumericalSplitCount(float $count): void + { + $this->numSplitCount = $count; + } + /** * @throws \Exception */ @@ -101,7 +121,7 @@ protected function trainBinary(array $samples, array $targets, array $labels): v if ($this->weights) { $numWeights = count($this->weights); if ($numWeights != count($samples)) { - throw new \Exception('Number of sample weights does not match with number of samples'); + throw new Exception('Number of sample weights does not match with number of samples'); } } else { $this->weights = array_fill(0, count($samples), 1); @@ -118,9 +138,12 @@ protected function trainBinary(array $samples, array $targets, array $labels): v } $bestSplit = [ - 'value' => 0, 'operator' => '', - 'prob' => [], 'column' => 0, - 'trainingErrorRate' => 1.0]; + 'value' => 0, + 'operator' => '', + 'prob' => [], + 'column' => 0, + 'trainingErrorRate' => 1.0, + ]; foreach ($columns as $col) { if ($this->columnTypes[$col] == DecisionTree::CONTINUOUS) { $split = $this->getBestNumericalSplit($samples, $targets, $col); @@ -139,22 +162,10 @@ protected function trainBinary(array $samples, array $targets, array $labels): v } } - /** - * While finding best split point for a numerical valued column, - * DecisionStump looks for equally distanced values between minimum and maximum - * values in the column. Given $count value determines how many split - * points to be probed. The more split counts, the better performance but - * worse processing time (Default value is 10.0) - */ - public function setNumericalSplitCount(float $count): void - { - $this->numSplitCount = $count; - } - /** * Determines best split point for the given column */ - protected function getBestNumericalSplit(array $samples, array $targets, int $col) : array + protected function getBestNumericalSplit(array $samples, array $targets, int $col): array { $values = array_column($samples, $col); // Trying all possible points may be accomplished in two general ways: @@ -173,9 +184,13 @@ protected function getBestNumericalSplit(array $samples, array $targets, int $co $threshold = array_sum($values) / (float) count($values); [$errorRate, $prob] = $this->calculateErrorRate($targets, $threshold, $operator, $values); if ($split == null || $errorRate < $split['trainingErrorRate']) { - $split = ['value' => $threshold, 'operator' => $operator, - 'prob' => $prob, 'column' => $col, - 'trainingErrorRate' => $errorRate]; + $split = [ + 'value' => $threshold, + 'operator' => $operator, + 'prob' => $prob, + 'column' => $col, + 'trainingErrorRate' => $errorRate, + ]; } // Try other possible points one by one @@ -183,9 +198,13 @@ protected function getBestNumericalSplit(array $samples, array $targets, int $co $threshold = (float) $step; [$errorRate, $prob] = $this->calculateErrorRate($targets, $threshold, $operator, $values); if ($errorRate < $split['trainingErrorRate']) { - $split = ['value' => $threshold, 'operator' => $operator, - 'prob' => $prob, 'column' => $col, - 'trainingErrorRate' => $errorRate]; + $split = [ + 'value' => $threshold, + 'operator' => $operator, + 'prob' => $prob, + 'column' => $col, + 'trainingErrorRate' => $errorRate, + ]; } }// for } @@ -193,7 +212,7 @@ protected function getBestNumericalSplit(array $samples, array $targets, int $co return $split; } - protected function getBestNominalSplit(array $samples, array $targets, int $col) : array + protected function getBestNominalSplit(array $samples, array $targets, int $col): array { $values = array_column($samples, $col); $valueCounts = array_count_values($values); @@ -206,9 +225,13 @@ protected function getBestNominalSplit(array $samples, array $targets, int $col) [$errorRate, $prob] = $this->calculateErrorRate($targets, $val, $operator, $values); if ($split == null || $split['trainingErrorRate'] < $errorRate) { - $split = ['value' => $val, 'operator' => $operator, - 'prob' => $prob, 'column' => $col, - 'trainingErrorRate' => $errorRate]; + $split = [ + 'value' => $val, + 'operator' => $operator, + 'prob' => $prob, + 'column' => $col, + 'trainingErrorRate' => $errorRate, + ]; } } } @@ -220,7 +243,7 @@ protected function getBestNominalSplit(array $samples, array $targets, int $col) * Calculates the ratio of wrong predictions based on the new threshold * value given as the parameter */ - protected function calculateErrorRate(array $targets, float $threshold, string $operator, array $values) : array + protected function calculateErrorRate(array $targets, float $threshold, string $operator, array $values): array { $wrong = 0.0; $prob = []; @@ -242,6 +265,7 @@ protected function calculateErrorRate(array $targets, float $threshold, string $ if (!isset($prob[$predicted][$target])) { $prob[$predicted][$target] = 0; } + ++$prob[$predicted][$target]; } @@ -267,7 +291,7 @@ protected function calculateErrorRate(array $targets, float $threshold, string $ * * @param mixed $label */ - protected function predictProbability(array $sample, $label) : float + protected function predictProbability(array $sample, $label): float { $predicted = $this->predictSampleBinary($sample); if ((string) $predicted == (string) $label) { @@ -292,11 +316,4 @@ protected function predictSampleBinary(array $sample) protected function resetBinary(): void { } - - public function __toString() : string - { - return "IF $this->column $this->operator $this->value ". - 'THEN '.$this->binaryLabels[0].' '. - 'ELSE '.$this->binaryLabels[1]; - } } diff --git a/src/Phpml/Classification/Linear/LogisticRegression.php b/src/Phpml/Classification/Linear/LogisticRegression.php index e8881be6..6b8cdd55 100644 --- a/src/Phpml/Classification/Linear/LogisticRegression.php +++ b/src/Phpml/Classification/Linear/LogisticRegression.php @@ -4,6 +4,8 @@ namespace Phpml\Classification\Linear; +use Closure; +use Exception; use Phpml\Helper\Optimizer\ConjugateGradient; class LogisticRegression extends Adaline @@ -70,18 +72,18 @@ public function __construct( ) { $trainingTypes = range(self::BATCH_TRAINING, self::CONJUGATE_GRAD_TRAINING); if (!in_array($trainingType, $trainingTypes)) { - throw new \Exception('Logistic regression can only be trained with '. + throw new Exception('Logistic regression can only be trained with '. 'batch (gradient descent), online (stochastic gradient descent) '. 'or conjugate batch (conjugate gradients) algorithms'); } if (!in_array($cost, ['log', 'sse'])) { - throw new \Exception("Logistic regression cost function can be one of the following: \n". + throw new Exception("Logistic regression cost function can be one of the following: \n". "'log' for log-likelihood and 'sse' for sum of squared errors"); } if ($penalty != '' && strtoupper($penalty) !== 'L2') { - throw new \Exception("Logistic regression supports only 'L2' regularization"); + throw new Exception("Logistic regression supports only 'L2' regularization"); } $this->learningRate = 0.001; @@ -132,14 +134,14 @@ protected function runTraining(array $samples, array $targets) return $this->runConjugateGradient($samples, $targets, $callback); default: - throw new \Exception('Logistic regression has invalid training type: %s.', $this->trainingType); + throw new Exception('Logistic regression has invalid training type: %s.', $this->trainingType); } } /** * Executes Conjugate Gradient method to optimize the weights of the LogReg model */ - protected function runConjugateGradient(array $samples, array $targets, \Closure $gradientFunc): void + protected function runConjugateGradient(array $samples, array $targets, Closure $gradientFunc): void { if (empty($this->optimizer)) { $this->optimizer = (new ConjugateGradient($this->featureCount)) @@ -155,7 +157,7 @@ protected function runConjugateGradient(array $samples, array $targets, \Closure * * @throws \Exception */ - protected function getCostFunction() : \Closure + protected function getCostFunction(): Closure { $penalty = 0; if ($this->penalty == 'L2') { @@ -183,9 +185,11 @@ protected function getCostFunction() : \Closure if ($hX == 1) { $hX = 1 - 1e-10; } + if ($hX == 0) { $hX = 1e-10; } + $error = -$y * log($hX) - (1 - $y) * log(1 - $hX); $gradient = $hX - $y; @@ -218,16 +222,14 @@ protected function getCostFunction() : \Closure return $callback; default: - throw new \Exception(sprintf('Logistic regression has invalid cost function: %s.', $this->costFunction)); + throw new Exception(sprintf('Logistic regression has invalid cost function: %s.', $this->costFunction)); } } /** * Returns the output of the network, a float value between 0.0 and 1.0 - * - * @return float */ - protected function output(array $sample) + protected function output(array $sample): float { $sum = parent::output($sample); @@ -237,7 +239,7 @@ protected function output(array $sample) /** * Returns the class value (either -1 or 1) for the given input */ - protected function outputClass(array $sample) : int + protected function outputClass(array $sample): int { $output = $this->output($sample); @@ -253,10 +255,10 @@ protected function outputClass(array $sample) : int * * The probability is simply taken as the distance of the sample * to the decision plane. - + * * @param mixed $label */ - protected function predictProbability(array $sample, $label) : float + protected function predictProbability(array $sample, $label): float { $predicted = $this->predictSampleBinary($sample); diff --git a/src/Phpml/Classification/Linear/Perceptron.php b/src/Phpml/Classification/Linear/Perceptron.php index 77eb7173..0db7496d 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -4,6 +4,8 @@ namespace Phpml\Classification\Linear; +use Closure; +use Exception; use Phpml\Classification\Classifier; use Phpml\Helper\OneVsRest; use Phpml\Helper\Optimizer\GD; @@ -34,7 +36,7 @@ class Perceptron implements Classifier, IncrementalEstimator /** * @var array */ - protected $weights; + protected $weights = []; /** * @var float @@ -73,11 +75,11 @@ class Perceptron implements Classifier, IncrementalEstimator public function __construct(float $learningRate = 0.001, int $maxIterations = 1000, bool $normalizeInputs = true) { if ($learningRate <= 0.0 || $learningRate > 1.0) { - throw new \Exception('Learning rate should be a float value between 0.0(exclusive) and 1.0(inclusive)'); + throw new Exception('Learning rate should be a float value between 0.0(exclusive) and 1.0(inclusive)'); } if ($maxIterations <= 0) { - throw new \Exception('Maximum number of iterations must be an integer greater than 0'); + throw new Exception('Maximum number of iterations must be an integer greater than 0'); } if ($normalizeInputs) { @@ -100,7 +102,10 @@ public function trainBinary(array $samples, array $targets, array $labels): void } // Set all target values to either -1 or 1 - $this->labels = [1 => $labels[0], -1 => $labels[1]]; + $this->labels = [ + 1 => $labels[0], + -1 => $labels[1], + ]; foreach ($targets as $key => $target) { $targets[$key] = (string) $target == (string) $this->labels[1] ? 1 : -1; } @@ -111,15 +116,6 @@ public function trainBinary(array $samples, array $targets, array $labels): void $this->runTraining($samples, $targets); } - protected function resetBinary(): void - { - $this->labels = []; - $this->optimizer = null; - $this->featureCount = 0; - $this->weights = null; - $this->costValues = []; - } - /** * Normally enabling early stopping for the optimization procedure may * help saving processing time while in some cases it may result in @@ -140,16 +136,23 @@ public function setEarlyStop(bool $enable = true) /** * Returns the cost values obtained during the training. */ - public function getCostValues() : array + public function getCostValues(): array { return $this->costValues; } + protected function resetBinary(): void + { + $this->labels = []; + $this->optimizer = null; + $this->featureCount = 0; + $this->weights = null; + $this->costValues = []; + } + /** * Trains the perceptron model with Stochastic Gradient Descent optimization * to get the correct set of weights - * - * @return void|mixed */ protected function runTraining(array $samples, array $targets) { @@ -171,7 +174,7 @@ protected function runTraining(array $samples, array $targets) * Executes a Gradient Descent algorithm for * the given cost function */ - protected function runGradientDescent(array $samples, array $targets, \Closure $gradientFunc, bool $isBatch = false): void + protected function runGradientDescent(array $samples, array $targets, Closure $gradientFunc, bool $isBatch = false): void { $class = $isBatch ? GD::class : StochasticGD::class; @@ -191,7 +194,7 @@ protected function runGradientDescent(array $samples, array $targets, \Closure $ * Checks if the sample should be normalized and if so, returns the * normalized sample */ - protected function checkNormalizedSample(array $sample) : array + protected function checkNormalizedSample(array $sample): array { if ($this->normalizer) { $samples = [$sample]; @@ -205,7 +208,7 @@ protected function checkNormalizedSample(array $sample) : array /** * Calculates net output of the network as a float value for the given input * - * @return int + * @return int|float */ protected function output(array $sample) { @@ -224,7 +227,7 @@ protected function output(array $sample) /** * Returns the class value (either -1 or 1) for the given input */ - protected function outputClass(array $sample) : int + protected function outputClass(array $sample): int { return $this->output($sample) > 0 ? 1 : -1; } @@ -237,7 +240,7 @@ protected function outputClass(array $sample) : int * * @param mixed $label */ - protected function predictProbability(array $sample, $label) : float + protected function predictProbability(array $sample, $label): float { $predicted = $this->predictSampleBinary($sample); diff --git a/src/Phpml/Classification/MLPClassifier.php b/src/Phpml/Classification/MLPClassifier.php index b76091dc..b225a64c 100644 --- a/src/Phpml/Classification/MLPClassifier.php +++ b/src/Phpml/Classification/MLPClassifier.php @@ -14,7 +14,7 @@ class MLPClassifier extends MultilayerPerceptron implements Classifier * * @throws InvalidArgumentException */ - public function getTargetClass($target) : int + public function getTargetClass($target): int { if (!in_array($target, $this->classes)) { throw InvalidArgumentException::invalidTarget($target); diff --git a/src/Phpml/Classification/NaiveBayes.php b/src/Phpml/Classification/NaiveBayes.php index 97f734ae..a470fd42 100644 --- a/src/Phpml/Classification/NaiveBayes.php +++ b/src/Phpml/Classification/NaiveBayes.php @@ -14,7 +14,9 @@ class NaiveBayes implements Classifier use Trainable, Predictable; public const CONTINUOS = 1; + public const NOMINAL = 2; + public const EPSILON = 1e-10; /** @@ -73,6 +75,31 @@ public function train(array $samples, array $targets): void } } + /** + * @return mixed + */ + protected function predictSample(array $sample) + { + // Use NaiveBayes assumption for each label using: + // P(label|features) = P(label) * P(feature0|label) * P(feature1|label) .... P(featureN|label) + // Then compare probability for each class to determine which label is most likely + $predictions = []; + foreach ($this->labels as $label) { + $p = $this->p[$label]; + for ($i = 0; $i < $this->featureCount; ++$i) { + $Plf = $this->sampleProbability($sample, $i, $label); + $p += $Plf; + } + + $predictions[$label] = $p; + } + + arsort($predictions, SORT_NUMERIC); + reset($predictions); + + return key($predictions); + } + /** * Calculates vital statistics for each label & feature. Stores these * values in private array in order to avoid repeated calculation @@ -108,7 +135,7 @@ private function calculateStatistics(string $label, array $samples): void /** * Calculates the probability P(label|sample_n) */ - private function sampleProbability(array $sample, int $feature, string $label) : float + private function sampleProbability(array $sample, int $feature, string $label): float { $value = $sample[$feature]; if ($this->dataType[$label][$feature] == self::NOMINAL) { @@ -119,6 +146,7 @@ private function sampleProbability(array $sample, int $feature, string $label) : return $this->discreteProb[$label][$feature][$value]; } + $std = $this->std[$label][$feature] ; $mean = $this->mean[$label][$feature]; // Calculate the probability density by use of normal/Gaussian distribution @@ -137,7 +165,7 @@ private function sampleProbability(array $sample, int $feature, string $label) : /** * Return samples belonging to specific label */ - private function getSamplesByLabel(string $label) : array + private function getSamplesByLabel(string $label): array { $samples = []; for ($i = 0; $i < $this->sampleCount; ++$i) { @@ -148,28 +176,4 @@ private function getSamplesByLabel(string $label) : array return $samples; } - - /** - * @return mixed - */ - protected function predictSample(array $sample) - { - // Use NaiveBayes assumption for each label using: - // P(label|features) = P(label) * P(feature0|label) * P(feature1|label) .... P(featureN|label) - // Then compare probability for each class to determine which label is most likely - $predictions = []; - foreach ($this->labels as $label) { - $p = $this->p[$label]; - for ($i = 0; $i < $this->featureCount; ++$i) { - $Plf = $this->sampleProbability($sample, $i, $label); - $p += $Plf; - } - $predictions[$label] = $p; - } - - arsort($predictions, SORT_NUMERIC); - reset($predictions); - - return key($predictions); - } } diff --git a/src/Phpml/Classification/WeightedClassifier.php b/src/Phpml/Classification/WeightedClassifier.php index c9b1f975..98347647 100644 --- a/src/Phpml/Classification/WeightedClassifier.php +++ b/src/Phpml/Classification/WeightedClassifier.php @@ -9,7 +9,7 @@ abstract class WeightedClassifier implements Classifier /** * @var array */ - protected $weights; + protected $weights = []; /** * Sets the array including a weight for each sample diff --git a/src/Phpml/Clustering/Clusterer.php b/src/Phpml/Clustering/Clusterer.php index ad24af2a..22e65e6c 100644 --- a/src/Phpml/Clustering/Clusterer.php +++ b/src/Phpml/Clustering/Clusterer.php @@ -6,5 +6,5 @@ interface Clusterer { - public function cluster(array $samples) : array; + public function cluster(array $samples): array; } diff --git a/src/Phpml/Clustering/DBSCAN.php b/src/Phpml/Clustering/DBSCAN.php index 1968b836..3546ebf0 100644 --- a/src/Phpml/Clustering/DBSCAN.php +++ b/src/Phpml/Clustering/DBSCAN.php @@ -4,6 +4,7 @@ namespace Phpml\Clustering; +use array_merge; use Phpml\Math\Distance; use Phpml\Math\Distance\Euclidean; @@ -26,7 +27,7 @@ class DBSCAN implements Clusterer public function __construct(float $epsilon = 0.5, int $minSamples = 3, ?Distance $distanceMetric = null) { - if (null === $distanceMetric) { + if ($distanceMetric === null) { $distanceMetric = new Euclidean(); } @@ -35,7 +36,7 @@ public function __construct(float $epsilon = 0.5, int $minSamples = 3, ?Distance $this->distanceMetric = $distanceMetric; } - public function cluster(array $samples) : array + public function cluster(array $samples): array { $clusters = []; $visited = []; @@ -44,6 +45,7 @@ public function cluster(array $samples) : array if (isset($visited[$index])) { continue; } + $visited[$index] = true; $regionSamples = $this->getSamplesInRegion($sample, $samples); @@ -55,7 +57,7 @@ public function cluster(array $samples) : array return $clusters; } - private function getSamplesInRegion(array $localSample, array $samples) : array + private function getSamplesInRegion(array $localSample, array $samples): array { $region = []; @@ -68,7 +70,7 @@ private function getSamplesInRegion(array $localSample, array $samples) : array return $region; } - private function expandCluster(array $samples, array &$visited) : array + private function expandCluster(array $samples, array &$visited): array { $cluster = []; @@ -84,7 +86,8 @@ private function expandCluster(array $samples, array &$visited) : array $cluster[$index] = $sample; } - $cluster = \array_merge($cluster, ...$clusterMerge); + + $cluster = array_merge($cluster, ...$clusterMerge); return $cluster; } diff --git a/src/Phpml/Clustering/FuzzyCMeans.php b/src/Phpml/Clustering/FuzzyCMeans.php index 6eccea0b..d3be1011 100644 --- a/src/Phpml/Clustering/FuzzyCMeans.php +++ b/src/Phpml/Clustering/FuzzyCMeans.php @@ -30,7 +30,7 @@ class FuzzyCMeans implements Clusterer /** * @var array|float[][] */ - private $membership; + private $membership = []; /** * @var float @@ -55,7 +55,7 @@ class FuzzyCMeans implements Clusterer /** * @var array */ - private $samples; + private $samples = []; /** * @throws InvalidArgumentException @@ -65,12 +65,63 @@ public function __construct(int $clustersNumber, float $fuzziness = 2.0, float $ if ($clustersNumber <= 0) { throw InvalidArgumentException::invalidClustersNumber(); } + $this->clustersNumber = $clustersNumber; $this->fuzziness = $fuzziness; $this->epsilon = $epsilon; $this->maxIterations = $maxIterations; } + public function getMembershipMatrix(): array + { + return $this->membership; + } + + /** + * @param array|Point[] $samples + */ + public function cluster(array $samples): array + { + // Initialize variables, clusters and membership matrix + $this->sampleCount = count($samples); + $this->samples = &$samples; + $this->space = new Space(count($samples[0])); + $this->initClusters(); + + // Our goal is minimizing the objective value while + // executing the clustering steps at a maximum number of iterations + $lastObjective = 0.0; + $iterations = 0; + do { + // Update the membership matrix and cluster centers, respectively + $this->updateMembershipMatrix(); + $this->updateClusters(); + + // Calculate the new value of the objective function + $objectiveVal = $this->getObjective(); + $difference = abs($lastObjective - $objectiveVal); + $lastObjective = $objectiveVal; + } while ($difference > $this->epsilon && $iterations++ <= $this->maxIterations); + + // Attach (hard cluster) each data point to the nearest cluster + for ($k = 0; $k < $this->sampleCount; ++$k) { + $column = array_column($this->membership, $k); + arsort($column); + reset($column); + $i = key($column); + $cluster = $this->clusters[$i]; + $cluster->attach(new Point($this->samples[$k])); + } + + // Return grouped samples + $grouped = []; + foreach ($this->clusters as $cluster) { + $grouped[] = $cluster->getPoints(); + } + + return $grouped; + } + protected function initClusters(): void { // Membership array is a matrix of cluster number by sample counts @@ -87,7 +138,7 @@ protected function generateRandomMembership(int $rows, int $cols): void $row = []; $total = 0.0; for ($k = 0; $k < $cols; ++$k) { - $val = rand(1, 5) / 10.0; + $val = random_int(1, 5) / 10.0; $row[] = $val; $total += $val; } @@ -146,13 +197,13 @@ protected function updateMembershipMatrix(): void } } - protected function getDistanceCalc(int $row, int $col) : float + protected function getDistanceCalc(int $row, int $col): float { $sum = 0.0; $distance = new Euclidean(); $dist1 = $distance->distance( - $this->clusters[$row]->getCoordinates(), - $this->samples[$col] + $this->clusters[$row]->getCoordinates(), + $this->samples[$col] ); for ($j = 0; $j < $this->clustersNumber; ++$j) { @@ -187,54 +238,4 @@ protected function getObjective() return $sum; } - - public function getMembershipMatrix() : array - { - return $this->membership; - } - - /** - * @param array|Point[] $samples - */ - public function cluster(array $samples) : array - { - // Initialize variables, clusters and membership matrix - $this->sampleCount = count($samples); - $this->samples = &$samples; - $this->space = new Space(count($samples[0])); - $this->initClusters(); - - // Our goal is minimizing the objective value while - // executing the clustering steps at a maximum number of iterations - $lastObjective = 0.0; - $iterations = 0; - do { - // Update the membership matrix and cluster centers, respectively - $this->updateMembershipMatrix(); - $this->updateClusters(); - - // Calculate the new value of the objective function - $objectiveVal = $this->getObjective(); - $difference = abs($lastObjective - $objectiveVal); - $lastObjective = $objectiveVal; - } while ($difference > $this->epsilon && $iterations++ <= $this->maxIterations); - - // Attach (hard cluster) each data point to the nearest cluster - for ($k = 0; $k < $this->sampleCount; ++$k) { - $column = array_column($this->membership, $k); - arsort($column); - reset($column); - $i = key($column); - $cluster = $this->clusters[$i]; - $cluster->attach(new Point($this->samples[$k])); - } - - // Return grouped samples - $grouped = []; - foreach ($this->clusters as $cluster) { - $grouped[] = $cluster->getPoints(); - } - - return $grouped; - } } diff --git a/src/Phpml/Clustering/KMeans.php b/src/Phpml/Clustering/KMeans.php index a4e85bcf..78a2e4ab 100644 --- a/src/Phpml/Clustering/KMeans.php +++ b/src/Phpml/Clustering/KMeans.php @@ -10,6 +10,7 @@ class KMeans implements Clusterer { public const INIT_RANDOM = 1; + public const INIT_KMEANS_PLUS_PLUS = 2; /** @@ -32,7 +33,7 @@ public function __construct(int $clustersNumber, int $initialization = self::INI $this->initialization = $initialization; } - public function cluster(array $samples) : array + public function cluster(array $samples): array { $space = new Space(count($samples[0])); foreach ($samples as $sample) { diff --git a/src/Phpml/Clustering/KMeans/Cluster.php b/src/Phpml/Clustering/KMeans/Cluster.php index 22545b6c..fea1ff82 100644 --- a/src/Phpml/Clustering/KMeans/Cluster.php +++ b/src/Phpml/Clustering/KMeans/Cluster.php @@ -28,7 +28,7 @@ public function __construct(Space $space, array $coordinates) $this->points = new SplObjectStorage(); } - public function getPoints() : array + public function getPoints(): array { $points = []; foreach ($this->points as $point) { @@ -38,7 +38,7 @@ public function getPoints() : array return $points; } - public function toArray() : array + public function toArray(): array { return [ 'centroid' => parent::toArray(), @@ -46,7 +46,7 @@ public function toArray() : array ]; } - public function attach(Point $point) : Point + public function attach(Point $point): Point { if ($point instanceof self) { throw new LogicException('cannot attach a cluster to another'); @@ -57,7 +57,7 @@ public function attach(Point $point) : Point return $point; } - public function detach(Point $point) : Point + public function detach(Point $point): Point { $this->points->detach($point); @@ -76,7 +76,8 @@ public function detachAll(SplObjectStorage $points): void public function updateCentroid(): void { - if (!$count = count($this->points)) { + $count = count($this->points); + if (!$count) { return; } diff --git a/src/Phpml/Clustering/KMeans/Point.php b/src/Phpml/Clustering/KMeans/Point.php index f90de8a0..6aa40a92 100644 --- a/src/Phpml/Clustering/KMeans/Point.php +++ b/src/Phpml/Clustering/KMeans/Point.php @@ -16,7 +16,7 @@ class Point implements ArrayAccess /** * @var array */ - protected $coordinates; + protected $coordinates = []; public function __construct(array $coordinates) { @@ -24,7 +24,7 @@ public function __construct(array $coordinates) $this->coordinates = $coordinates; } - public function toArray() : array + public function toArray(): array { return $this->coordinates; } @@ -66,7 +66,7 @@ public function getClosest(array $points) return $minPoint; } - public function getCoordinates() : array + public function getCoordinates(): array { return $this->coordinates; } diff --git a/src/Phpml/Clustering/KMeans/Space.php b/src/Phpml/Clustering/KMeans/Space.php index 4412d536..0d4adf5a 100644 --- a/src/Phpml/Clustering/KMeans/Space.php +++ b/src/Phpml/Clustering/KMeans/Space.php @@ -25,7 +25,7 @@ public function __construct($dimension) $this->dimension = $dimension; } - public function toArray() : array + public function toArray(): array { $points = []; foreach ($this as $point) { @@ -35,7 +35,7 @@ public function toArray() : array return ['points' => $points]; } - public function newPoint(array $coordinates) : Point + public function newPoint(array $coordinates): Point { if (count($coordinates) != $this->dimension) { throw new LogicException('('.implode(',', $coordinates).') is not a point of this space'); @@ -65,7 +65,7 @@ public function attach($point, $data = null): void parent::attach($point, $data); } - public function getDimension() : int + public function getDimension(): int { return $this->dimension; } @@ -92,7 +92,7 @@ public function getBoundaries() return [$min, $max]; } - public function getRandomPoint(Point $min, Point $max) : Point + public function getRandomPoint(Point $min, Point $max): Point { $point = $this->newPoint(array_fill(0, $this->dimension, null)); @@ -106,7 +106,7 @@ public function getRandomPoint(Point $min, Point $max) : Point /** * @return array|Cluster[] */ - public function cluster(int $clustersNumber, int $initMethod = KMeans::INIT_RANDOM) : array + public function cluster(int $clustersNumber, int $initMethod = KMeans::INIT_RANDOM): array { $clusters = $this->initializeClusters($clustersNumber, $initMethod); @@ -119,7 +119,7 @@ public function cluster(int $clustersNumber, int $initMethod = KMeans::INIT_RAND /** * @return array|Cluster[] */ - protected function initializeClusters(int $clustersNumber, int $initMethod) : array + protected function initializeClusters(int $clustersNumber, int $initMethod): array { switch ($initMethod) { case KMeans::INIT_RANDOM: @@ -139,7 +139,7 @@ protected function initializeClusters(int $clustersNumber, int $initMethod) : ar return $clusters; } - protected function iterate($clusters) : bool + protected function iterate($clusters): bool { $convergence = true; @@ -177,19 +177,7 @@ protected function iterate($clusters) : bool return $convergence; } - private function initializeRandomClusters(int $clustersNumber) : array - { - $clusters = []; - [$min, $max] = $this->getBoundaries(); - - for ($n = 0; $n < $clustersNumber; ++$n) { - $clusters[] = new Cluster($this, $this->getRandomPoint($min, $max)->getCoordinates()); - } - - return $clusters; - } - - protected function initializeKMPPClusters(int $clustersNumber) : array + protected function initializeKMPPClusters(int $clustersNumber): array { $clusters = []; $this->rewind(); @@ -218,4 +206,16 @@ protected function initializeKMPPClusters(int $clustersNumber) : array return $clusters; } + + private function initializeRandomClusters(int $clustersNumber): array + { + $clusters = []; + [$min, $max] = $this->getBoundaries(); + + for ($n = 0; $n < $clustersNumber; ++$n) { + $clusters[] = new Cluster($this, $this->getRandomPoint($min, $max)->getCoordinates()); + } + + return $clusters; + } } diff --git a/src/Phpml/CrossValidation/Split.php b/src/Phpml/CrossValidation/Split.php index e485ffb5..96c9019d 100644 --- a/src/Phpml/CrossValidation/Split.php +++ b/src/Phpml/CrossValidation/Split.php @@ -31,39 +31,40 @@ abstract class Split public function __construct(Dataset $dataset, float $testSize = 0.3, ?int $seed = null) { - if (0 >= $testSize || 1 <= $testSize) { + if ($testSize <= 0 || $testSize >= 1) { throw InvalidArgumentException::percentNotInRange('testSize'); } + $this->seedGenerator($seed); $this->splitDataset($dataset, $testSize); } - abstract protected function splitDataset(Dataset $dataset, float $testSize); - - public function getTrainSamples() : array + public function getTrainSamples(): array { return $this->trainSamples; } - public function getTestSamples() : array + public function getTestSamples(): array { return $this->testSamples; } - public function getTrainLabels() : array + public function getTrainLabels(): array { return $this->trainLabels; } - public function getTestLabels() : array + public function getTestLabels(): array { return $this->testLabels; } + abstract protected function splitDataset(Dataset $dataset, float $testSize); + protected function seedGenerator(?int $seed = null): void { - if (null === $seed) { + if ($seed === null) { mt_srand(); } else { mt_srand($seed); diff --git a/src/Phpml/CrossValidation/StratifiedRandomSplit.php b/src/Phpml/CrossValidation/StratifiedRandomSplit.php index 153cb8f1..d4508422 100644 --- a/src/Phpml/CrossValidation/StratifiedRandomSplit.php +++ b/src/Phpml/CrossValidation/StratifiedRandomSplit.php @@ -21,7 +21,7 @@ protected function splitDataset(Dataset $dataset, float $testSize): void /** * @return Dataset[]|array */ - private function splitByTarget(Dataset $dataset) : array + private function splitByTarget(Dataset $dataset): array { $targets = $dataset->getTargets(); $samples = $dataset->getSamples(); @@ -38,7 +38,7 @@ private function splitByTarget(Dataset $dataset) : array return $datasets; } - private function createDatasets(array $uniqueTargets, array $split) : array + private function createDatasets(array $uniqueTargets, array $split): array { $datasets = []; foreach ($uniqueTargets as $target) { diff --git a/src/Phpml/Dataset/ArrayDataset.php b/src/Phpml/Dataset/ArrayDataset.php index e27b2e39..7d30b0b0 100644 --- a/src/Phpml/Dataset/ArrayDataset.php +++ b/src/Phpml/Dataset/ArrayDataset.php @@ -31,12 +31,12 @@ public function __construct(array $samples, array $targets) $this->targets = $targets; } - public function getSamples() : array + public function getSamples(): array { return $this->samples; } - public function getTargets() : array + public function getTargets(): array { return $this->targets; } diff --git a/src/Phpml/Dataset/CsvDataset.php b/src/Phpml/Dataset/CsvDataset.php index ef33e2cb..f88fe314 100644 --- a/src/Phpml/Dataset/CsvDataset.php +++ b/src/Phpml/Dataset/CsvDataset.php @@ -11,7 +11,7 @@ class CsvDataset extends ArrayDataset /** * @var array */ - protected $columnNames; + protected $columnNames = []; /** * @throws FileException @@ -22,7 +22,8 @@ public function __construct(string $filepath, int $features, bool $headingRow = throw FileException::missingFile(basename($filepath)); } - if (false === $handle = fopen($filepath, 'rb')) { + $handle = fopen($filepath, 'rb'); + if ($handle === false) { throw FileException::cantOpenFile(basename($filepath)); } @@ -44,7 +45,7 @@ public function __construct(string $filepath, int $features, bool $headingRow = parent::__construct($samples, $targets); } - public function getColumnNames() : array + public function getColumnNames(): array { return $this->columnNames; } diff --git a/src/Phpml/Dataset/Dataset.php b/src/Phpml/Dataset/Dataset.php index ce75a8ae..f851d852 100644 --- a/src/Phpml/Dataset/Dataset.php +++ b/src/Phpml/Dataset/Dataset.php @@ -9,10 +9,10 @@ interface Dataset /** * @return array */ - public function getSamples() : array; + public function getSamples(): array; /** * @return array */ - public function getTargets() : array; + public function getTargets(): array; } diff --git a/src/Phpml/DimensionReduction/EigenTransformerBase.php b/src/Phpml/DimensionReduction/EigenTransformerBase.php index a6352ba6..ec64163f 100644 --- a/src/Phpml/DimensionReduction/EigenTransformerBase.php +++ b/src/Phpml/DimensionReduction/EigenTransformerBase.php @@ -84,7 +84,7 @@ protected function eigenDecomposition(array $matrix): void /** * Returns the reduced data */ - protected function reduce(array $data) : array + protected function reduce(array $data): array { $m1 = new Matrix($data); $m2 = new Matrix($this->eigVectors); diff --git a/src/Phpml/DimensionReduction/KernelPCA.php b/src/Phpml/DimensionReduction/KernelPCA.php index d11e1a6a..1981cb58 100644 --- a/src/Phpml/DimensionReduction/KernelPCA.php +++ b/src/Phpml/DimensionReduction/KernelPCA.php @@ -4,6 +4,8 @@ namespace Phpml\DimensionReduction; +use Closure; +use Exception; use Phpml\Math\Distance\Euclidean; use Phpml\Math\Distance\Manhattan; use Phpml\Math\Matrix; @@ -11,8 +13,11 @@ class KernelPCA extends PCA { public const KERNEL_RBF = 1; + public const KERNEL_SIGMOID = 2; + public const KERNEL_LAPLACIAN = 3; + public const KERNEL_LINEAR = 4; /** @@ -34,7 +39,7 @@ class KernelPCA extends PCA * * @var array */ - protected $data; + protected $data = []; /** * Kernel principal component analysis (KernelPCA) is an extension of PCA using @@ -54,7 +59,7 @@ public function __construct(int $kernel = self::KERNEL_RBF, ?float $totalVarianc { $availableKernels = [self::KERNEL_RBF, self::KERNEL_SIGMOID, self::KERNEL_LAPLACIAN, self::KERNEL_LINEAR]; if (!in_array($kernel, $availableKernels)) { - throw new \Exception('KernelPCA can be initialized with the following kernels only: Linear, RBF, Sigmoid and Laplacian'); + throw new Exception('KernelPCA can be initialized with the following kernels only: Linear, RBF, Sigmoid and Laplacian'); } parent::__construct($totalVariance, $numFeatures); @@ -69,7 +74,7 @@ public function __construct(int $kernel = self::KERNEL_RBF, ?float $totalVarianc * $data is an n-by-m matrix and returned array is * n-by-k matrix where k <= m */ - public function fit(array $data) : array + public function fit(array $data): array { $numRows = count($data); $this->data = $data; @@ -88,11 +93,32 @@ public function fit(array $data) : array return Matrix::transposeArray($this->eigVectors); } + /** + * Transforms the given sample to a lower dimensional vector by using + * the variables obtained during the last run of fit. + * + * @throws \Exception + */ + public function transform(array $sample): array + { + if (!$this->fit) { + throw new Exception('KernelPCA has not been fitted with respect to original dataset, please run KernelPCA::fit() first'); + } + + if (is_array($sample[0])) { + throw new Exception('KernelPCA::transform() accepts only one-dimensional arrays'); + } + + $pairs = $this->getDistancePairs($sample); + + return $this->projectSample($pairs); + } + /** * Calculates similarity matrix by use of selected kernel function
* An n-by-m matrix is given and an n-by-n matrix is returned */ - protected function calculateKernelMatrix(array $data, int $numRows) : array + protected function calculateKernelMatrix(array $data, int $numRows): array { $kernelFunc = $this->getKernel(); @@ -116,7 +142,7 @@ protected function calculateKernelMatrix(array $data, int $numRows) : array * * K′ = K − N.K − K.N + N.K.N where N is n-by-n matrix filled with 1/n */ - protected function centerMatrix(array $matrix, int $n) : array + protected function centerMatrix(array $matrix, int $n): array { $N = array_fill(0, $n, array_fill(0, $n, 1.0 / $n)); $N = new Matrix($N, false); @@ -140,7 +166,7 @@ protected function centerMatrix(array $matrix, int $n) : array * * @throws \Exception */ - protected function getKernel(): \Closure + protected function getKernel(): Closure { switch ($this->kernel) { case self::KERNEL_LINEAR: @@ -173,11 +199,11 @@ protected function getKernel(): \Closure }; default: - throw new \Exception(sprintf('KernelPCA initialized with invalid kernel: %d', $this->kernel)); + throw new Exception(sprintf('KernelPCA initialized with invalid kernel: %d', $this->kernel)); } } - protected function getDistancePairs(array $sample) : array + protected function getDistancePairs(array $sample): array { $kernel = $this->getKernel(); @@ -189,7 +215,7 @@ protected function getDistancePairs(array $sample) : array return $pairs; } - protected function projectSample(array $pairs) : array + protected function projectSample(array $pairs): array { // Normalize eigenvectors by eig = eigVectors / eigValues $func = function ($eigVal, $eigVect) { @@ -203,25 +229,4 @@ protected function projectSample(array $pairs) : array // return k.dot(eig) return Matrix::dot($pairs, $eig); } - - /** - * Transforms the given sample to a lower dimensional vector by using - * the variables obtained during the last run of fit. - * - * @throws \Exception - */ - public function transform(array $sample) : array - { - if (!$this->fit) { - throw new \Exception('KernelPCA has not been fitted with respect to original dataset, please run KernelPCA::fit() first'); - } - - if (is_array($sample[0])) { - throw new \Exception('KernelPCA::transform() accepts only one-dimensional arrays'); - } - - $pairs = $this->getDistancePairs($sample); - - return $this->projectSample($pairs); - } } diff --git a/src/Phpml/DimensionReduction/LDA.php b/src/Phpml/DimensionReduction/LDA.php index 26b2324e..6400d141 100644 --- a/src/Phpml/DimensionReduction/LDA.php +++ b/src/Phpml/DimensionReduction/LDA.php @@ -4,6 +4,7 @@ namespace Phpml\DimensionReduction; +use Exception; use Phpml\Math\Matrix; class LDA extends EigenTransformerBase @@ -16,22 +17,22 @@ class LDA extends EigenTransformerBase /** * @var array */ - public $labels; + public $labels = []; /** * @var array */ - public $means; + public $means = []; /** * @var array */ - public $counts; + public $counts = []; /** * @var float[] */ - public $overallMean; + public $overallMean = []; /** * Linear Discriminant Analysis (LDA) is used to reduce the dimensionality @@ -50,18 +51,21 @@ class LDA extends EigenTransformerBase public function __construct(?float $totalVariance = null, ?int $numFeatures = null) { if ($totalVariance !== null && ($totalVariance < 0.1 || $totalVariance > 0.99)) { - throw new \Exception('Total variance can be a value between 0.1 and 0.99'); + throw new Exception('Total variance can be a value between 0.1 and 0.99'); } + if ($numFeatures !== null && $numFeatures <= 0) { - throw new \Exception('Number of features to be preserved should be greater than 0'); + throw new Exception('Number of features to be preserved should be greater than 0'); } + if ($totalVariance !== null && $numFeatures !== null) { - throw new \Exception('Either totalVariance or numFeatures should be specified in order to run the algorithm'); + throw new Exception('Either totalVariance or numFeatures should be specified in order to run the algorithm'); } if ($numFeatures !== null) { $this->numFeatures = $numFeatures; } + if ($totalVariance !== null) { $this->totalVariance = $totalVariance; } @@ -70,7 +74,7 @@ public function __construct(?float $totalVariance = null, ?int $numFeatures = nu /** * Trains the algorithm to transform the given data to a lower dimensional space. */ - public function fit(array $data, array $classes) : array + public function fit(array $data, array $classes): array { $this->labels = $this->getLabels($classes); $this->means = $this->calculateMeans($data, $classes); @@ -86,10 +90,29 @@ public function fit(array $data, array $classes) : array return $this->reduce($data); } + /** + * Transforms the given sample to a lower dimensional vector by using + * the eigenVectors obtained in the last run of fit. + * + * @throws \Exception + */ + public function transform(array $sample): array + { + if (!$this->fit) { + throw new Exception('LDA has not been fitted with respect to original dataset, please run LDA::fit() first'); + } + + if (!is_array($sample[0])) { + $sample = [$sample]; + } + + return $this->reduce($sample); + } + /** * Returns unique labels in the dataset */ - protected function getLabels(array $classes) : array + protected function getLabels(array $classes): array { $counts = array_count_values($classes); @@ -100,7 +123,7 @@ protected function getLabels(array $classes) : array * Calculates mean of each column for each class and returns * n by m matrix where n is number of labels and m is number of columns */ - protected function calculateMeans(array $data, array $classes) : array + protected function calculateMeans(array $data, array $classes): array { $means = []; $counts = []; @@ -113,6 +136,7 @@ protected function calculateMeans(array $data, array $classes) : array if (!isset($means[$label][$col])) { $means[$label][$col] = 0.0; } + $means[$label][$col] += $val; $overallMean[$col] += $val; } @@ -146,7 +170,7 @@ protected function calculateMeans(array $data, array $classes) : array * is a n by m matrix where n is number of classes and * m is number of columns */ - protected function calculateClassVar(array $data, array $classes) : Matrix + protected function calculateClassVar(array $data, array $classes): Matrix { // s is an n (number of classes) by m (number of column) matrix $s = array_fill(0, count($data[0]), array_fill(0, count($data[0]), 0)); @@ -169,7 +193,7 @@ protected function calculateClassVar(array $data, array $classes) : Matrix * is an n by m matrix where n is number of classes and * m is number of columns */ - protected function calculateClassCov() : Matrix + protected function calculateClassCov(): Matrix { // s is an n (number of classes) by m (number of column) matrix $s = array_fill(0, count($this->overallMean), array_fill(0, count($this->overallMean), 0)); @@ -187,7 +211,7 @@ protected function calculateClassCov() : Matrix /** * Returns the result of the calculation (x - m)T.(x - m) */ - protected function calculateVar(array $row, array $means) : Matrix + protected function calculateVar(array $row, array $means): Matrix { $x = new Matrix($row, false); $m = new Matrix($means, false); @@ -195,23 +219,4 @@ protected function calculateVar(array $row, array $means) : Matrix return $diff->transpose()->multiply($diff); } - - /** - * Transforms the given sample to a lower dimensional vector by using - * the eigenVectors obtained in the last run of fit. - * - * @throws \Exception - */ - public function transform(array $sample) : array - { - if (!$this->fit) { - throw new \Exception('LDA has not been fitted with respect to original dataset, please run LDA::fit() first'); - } - - if (!is_array($sample[0])) { - $sample = [$sample]; - } - - return $this->reduce($sample); - } } diff --git a/src/Phpml/DimensionReduction/PCA.php b/src/Phpml/DimensionReduction/PCA.php index 25b71863..18879bbd 100644 --- a/src/Phpml/DimensionReduction/PCA.php +++ b/src/Phpml/DimensionReduction/PCA.php @@ -4,6 +4,7 @@ namespace Phpml\DimensionReduction; +use Exception; use Phpml\Math\Statistic\Covariance; use Phpml\Math\Statistic\Mean; @@ -35,18 +36,21 @@ class PCA extends EigenTransformerBase public function __construct(?float $totalVariance = null, ?int $numFeatures = null) { if ($totalVariance !== null && ($totalVariance < 0.1 || $totalVariance > 0.99)) { - throw new \Exception('Total variance can be a value between 0.1 and 0.99'); + throw new Exception('Total variance can be a value between 0.1 and 0.99'); } + if ($numFeatures !== null && $numFeatures <= 0) { - throw new \Exception('Number of features to be preserved should be greater than 0'); + throw new Exception('Number of features to be preserved should be greater than 0'); } + if ($totalVariance !== null && $numFeatures !== null) { - throw new \Exception('Either totalVariance or numFeatures should be specified in order to run the algorithm'); + throw new Exception('Either totalVariance or numFeatures should be specified in order to run the algorithm'); } if ($numFeatures !== null) { $this->numFeatures = $numFeatures; } + if ($totalVariance !== null) { $this->totalVariance = $totalVariance; } @@ -58,7 +62,7 @@ public function __construct(?float $totalVariance = null, ?int $numFeatures = nu * $data is an n-by-m matrix and returned array is * n-by-k matrix where k <= m */ - public function fit(array $data) : array + public function fit(array $data): array { $n = count($data[0]); @@ -73,6 +77,27 @@ public function fit(array $data) : array return $this->reduce($data); } + /** + * Transforms the given sample to a lower dimensional vector by using + * the eigenVectors obtained in the last run of fit. + * + * @throws \Exception + */ + public function transform(array $sample): array + { + if (!$this->fit) { + throw new Exception('PCA has not been fitted with respect to original dataset, please run PCA::fit() first'); + } + + if (!is_array($sample[0])) { + $sample = [$sample]; + } + + $sample = $this->normalize($sample, count($sample[0])); + + return $this->reduce($sample); + } + protected function calculateMeans(array $data, int $n): void { // Calculate means for each dimension @@ -87,7 +112,7 @@ protected function calculateMeans(array $data, int $n): void * Normalization of the data includes subtracting mean from * each dimension therefore dimensions will be centered to zero */ - protected function normalize(array $data, int $n) : array + protected function normalize(array $data, int $n): array { if (empty($this->means)) { $this->calculateMeans($data, $n); @@ -102,25 +127,4 @@ protected function normalize(array $data, int $n) : array return $data; } - - /** - * Transforms the given sample to a lower dimensional vector by using - * the eigenVectors obtained in the last run of fit. - * - * @throws \Exception - */ - public function transform(array $sample) : array - { - if (!$this->fit) { - throw new \Exception('PCA has not been fitted with respect to original dataset, please run PCA::fit() first'); - } - - if (!is_array($sample[0])) { - $sample = [$sample]; - } - - $sample = $this->normalize($sample, count($sample[0])); - - return $this->reduce($sample); - } } diff --git a/src/Phpml/Exception/DatasetException.php b/src/Phpml/Exception/DatasetException.php index 5d3e0db7..8d6d5da6 100644 --- a/src/Phpml/Exception/DatasetException.php +++ b/src/Phpml/Exception/DatasetException.php @@ -4,9 +4,11 @@ namespace Phpml\Exception; -class DatasetException extends \Exception +use Exception; + +class DatasetException extends Exception { - public static function missingFolder(string $path) : DatasetException + public static function missingFolder(string $path): self { return new self(sprintf('Dataset root folder "%s" missing.', $path)); } diff --git a/src/Phpml/Exception/FileException.php b/src/Phpml/Exception/FileException.php index 39b9b03d..719c2c2b 100644 --- a/src/Phpml/Exception/FileException.php +++ b/src/Phpml/Exception/FileException.php @@ -4,19 +4,21 @@ namespace Phpml\Exception; -class FileException extends \Exception +use Exception; + +class FileException extends Exception { - public static function missingFile(string $filepath) : FileException + public static function missingFile(string $filepath): self { return new self(sprintf('File "%s" missing.', $filepath)); } - public static function cantOpenFile(string $filepath) : FileException + public static function cantOpenFile(string $filepath): self { return new self(sprintf('File "%s" can\'t be open.', $filepath)); } - public static function cantSaveFile(string $filepath) : FileException + public static function cantSaveFile(string $filepath): self { return new self(sprintf('File "%s" can\'t be saved.', $filepath)); } diff --git a/src/Phpml/Exception/InvalidArgumentException.php b/src/Phpml/Exception/InvalidArgumentException.php index d96bb33a..e02d14d8 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -4,39 +4,41 @@ namespace Phpml\Exception; -class InvalidArgumentException extends \Exception +use Exception; + +class InvalidArgumentException extends Exception { - public static function arraySizeNotMatch() : InvalidArgumentException + public static function arraySizeNotMatch(): self { return new self('Size of given arrays does not match'); } - public static function percentNotInRange($name) : InvalidArgumentException + public static function percentNotInRange($name): self { return new self(sprintf('%s must be between 0.0 and 1.0', $name)); } - public static function arrayCantBeEmpty() : InvalidArgumentException + public static function arrayCantBeEmpty(): self { return new self('The array has zero elements'); } - public static function arraySizeToSmall(int $minimumSize = 2) : InvalidArgumentException + public static function arraySizeToSmall(int $minimumSize = 2): self { return new self(sprintf('The array must have at least %d elements', $minimumSize)); } - public static function matrixDimensionsDidNotMatch() : InvalidArgumentException + public static function matrixDimensionsDidNotMatch(): self { return new self('Matrix dimensions did not match'); } - public static function inconsistentMatrixSupplied() : InvalidArgumentException + public static function inconsistentMatrixSupplied(): self { return new self('Inconsistent matrix supplied'); } - public static function invalidClustersNumber() : InvalidArgumentException + public static function invalidClustersNumber(): self { return new self('Invalid clusters number'); } @@ -44,57 +46,57 @@ public static function invalidClustersNumber() : InvalidArgumentException /** * @param mixed $target */ - public static function invalidTarget($target) : InvalidArgumentException + public static function invalidTarget($target): self { return new self(sprintf('Target with value "%s" is not part of the accepted classes', $target)); } - public static function invalidStopWordsLanguage(string $language) : InvalidArgumentException + public static function invalidStopWordsLanguage(string $language): self { return new self(sprintf('Can\'t find "%s" language for StopWords', $language)); } - public static function invalidLayerNodeClass() : InvalidArgumentException + public static function invalidLayerNodeClass(): self { return new self('Layer node class must implement Node interface'); } - public static function invalidLayersNumber() : InvalidArgumentException + public static function invalidLayersNumber(): self { return new self('Provide at least 1 hidden layer'); } - public static function invalidClassesNumber() : InvalidArgumentException + public static function invalidClassesNumber(): self { return new self('Provide at least 2 different classes'); } - public static function inconsistentClasses() : InvalidArgumentException + public static function inconsistentClasses(): self { return new self('The provided classes don\'t match the classes provided in the constructor'); } - public static function fileNotFound(string $file) : InvalidArgumentException + public static function fileNotFound(string $file): self { return new self(sprintf('File "%s" not found', $file)); } - public static function fileNotExecutable(string $file) : InvalidArgumentException + public static function fileNotExecutable(string $file): self { return new self(sprintf('File "%s" is not executable', $file)); } - public static function pathNotFound(string $path) : InvalidArgumentException + public static function pathNotFound(string $path): self { return new self(sprintf('The specified path "%s" does not exist', $path)); } - public static function pathNotWritable(string $path) : InvalidArgumentException + public static function pathNotWritable(string $path): self { return new self(sprintf('The specified path "%s" is not writable', $path)); } - public static function invalidOperator(string $operator) : InvalidArgumentException + public static function invalidOperator(string $operator): self { return new self(sprintf('Invalid operator "%s" provided', $operator)); } diff --git a/src/Phpml/Exception/MatrixException.php b/src/Phpml/Exception/MatrixException.php index a52feaad..b309bfff 100644 --- a/src/Phpml/Exception/MatrixException.php +++ b/src/Phpml/Exception/MatrixException.php @@ -4,19 +4,21 @@ namespace Phpml\Exception; -class MatrixException extends \Exception +use Exception; + +class MatrixException extends Exception { - public static function notSquareMatrix() : MatrixException + public static function notSquareMatrix(): self { return new self('Matrix is not square matrix'); } - public static function columnOutOfRange() : MatrixException + public static function columnOutOfRange(): self { return new self('Column out of range'); } - public static function singularMatrix() : MatrixException + public static function singularMatrix(): self { return new self('Matrix is singular'); } diff --git a/src/Phpml/Exception/NormalizerException.php b/src/Phpml/Exception/NormalizerException.php index a7604e86..282fa1b3 100644 --- a/src/Phpml/Exception/NormalizerException.php +++ b/src/Phpml/Exception/NormalizerException.php @@ -4,9 +4,11 @@ namespace Phpml\Exception; -class NormalizerException extends \Exception +use Exception; + +class NormalizerException extends Exception { - public static function unknownNorm() : NormalizerException + public static function unknownNorm(): self { return new self('Unknown norm supplied.'); } diff --git a/src/Phpml/Exception/SerializeException.php b/src/Phpml/Exception/SerializeException.php index 913667a0..6d1abaae 100644 --- a/src/Phpml/Exception/SerializeException.php +++ b/src/Phpml/Exception/SerializeException.php @@ -4,14 +4,16 @@ namespace Phpml\Exception; -class SerializeException extends \Exception +use Exception; + +class SerializeException extends Exception { - public static function cantUnserialize(string $filepath) : SerializeException + public static function cantUnserialize(string $filepath): self { return new self(sprintf('"%s" can not be unserialized.', $filepath)); } - public static function cantSerialize(string $classname) : SerializeException + public static function cantSerialize(string $classname): self { return new self(sprintf('Class "%s" can not be serialized.', $classname)); } diff --git a/src/Phpml/FeatureExtraction/StopWords.php b/src/Phpml/FeatureExtraction/StopWords.php index b6717b94..fdb985f7 100644 --- a/src/Phpml/FeatureExtraction/StopWords.php +++ b/src/Phpml/FeatureExtraction/StopWords.php @@ -11,19 +11,19 @@ class StopWords /** * @var array */ - protected $stopWords; + protected $stopWords = []; public function __construct(array $stopWords) { $this->stopWords = array_fill_keys($stopWords, true); } - public function isStopWord(string $token) : bool + public function isStopWord(string $token): bool { return isset($this->stopWords[$token]); } - public static function factory(string $language = 'English') : StopWords + public static function factory(string $language = 'English'): self { $className = __NAMESPACE__."\\StopWords\\$language"; diff --git a/src/Phpml/FeatureExtraction/TfIdfTransformer.php b/src/Phpml/FeatureExtraction/TfIdfTransformer.php index 6efd90f4..4b678a44 100644 --- a/src/Phpml/FeatureExtraction/TfIdfTransformer.php +++ b/src/Phpml/FeatureExtraction/TfIdfTransformer.php @@ -11,7 +11,7 @@ class TfIdfTransformer implements Transformer /** * @var array */ - private $idf; + private $idf = []; public function __construct(?array $samples = null) { diff --git a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php index e00fc698..e0bd4024 100644 --- a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php +++ b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php @@ -27,21 +27,18 @@ class TokenCountVectorizer implements Transformer /** * @var array */ - private $vocabulary; + private $vocabulary = []; /** * @var array */ - private $frequencies; + private $frequencies = []; public function __construct(Tokenizer $tokenizer, ?StopWords $stopWords = null, float $minDF = 0.0) { $this->tokenizer = $tokenizer; $this->stopWords = $stopWords; $this->minDF = $minDF; - - $this->vocabulary = []; - $this->frequencies = []; } public function fit(array $samples): void @@ -58,7 +55,7 @@ public function transform(array &$samples): void $this->checkDocumentFrequency($samples); } - public function getVocabulary() : array + public function getVocabulary(): array { return array_flip($this->vocabulary); } @@ -80,7 +77,7 @@ private function transformSample(string &$sample): void foreach ($tokens as $token) { $index = $this->getTokenIndex($token); - if (false !== $index) { + if ($index !== false) { $this->updateFrequency($token); if (!isset($counts[$index])) { $counts[$index] = 0; @@ -155,7 +152,7 @@ private function resetBeyondMinimum(array &$sample, array $beyondMinimum): void } } - private function getBeyondMinimumIndexes(int $samplesCount) : array + private function getBeyondMinimumIndexes(int $samplesCount): array { $indexes = []; foreach ($this->frequencies as $token => $frequency) { diff --git a/src/Phpml/Helper/OneVsRest.php b/src/Phpml/Helper/OneVsRest.php index 15d62d86..4f661ba5 100644 --- a/src/Phpml/Helper/OneVsRest.php +++ b/src/Phpml/Helper/OneVsRest.php @@ -36,6 +36,18 @@ public function train(array $samples, array $targets): void $this->trainBylabel($samples, $targets); } + /** + * Resets the classifier and the vars internally used by OneVsRest to create multiple classifiers. + */ + public function reset(): void + { + $this->classifiers = []; + $this->allLabels = []; + $this->costValues = []; + + $this->resetBinary(); + } + protected function trainByLabel(array $samples, array $targets, array $allLabels = []): void { // Overwrites the current value if it exist. $allLabels must be provided for each partialTrain run. @@ -44,6 +56,7 @@ protected function trainByLabel(array $samples, array $targets, array $allLabels } else { $this->allLabels = array_keys(array_count_values($targets)); } + sort($this->allLabels, SORT_STRING); // If there are only two targets, then there is no need to perform OvR @@ -77,18 +90,6 @@ protected function trainByLabel(array $samples, array $targets, array $allLabels } } - /** - * Resets the classifier and the vars internally used by OneVsRest to create multiple classifiers. - */ - public function reset(): void - { - $this->classifiers = []; - $this->allLabels = []; - $this->costValues = []; - - $this->resetBinary(); - } - /** * Returns an instance of the current class after cleaning up OneVsRest stuff. * @@ -105,29 +106,6 @@ protected function getClassifierCopy() return $classifier; } - /** - * Groups all targets into two groups: Targets equal to - * the given label and the others - * - * $targets is not passed by reference nor contains objects so this method - * changes will not affect the caller $targets array. - * - * @param mixed $label - * - * @return array Binarized targets and target's labels - */ - private function binarizeTargets(array $targets, $label) : array - { - $notLabel = "not_$label"; - foreach ($targets as $key => $target) { - $targets[$key] = $target == $label ? $label : $notLabel; - } - - $labels = [$label, $notLabel]; - - return [$targets, $labels]; - } - /** * @return mixed */ @@ -155,8 +133,6 @@ abstract protected function trainBinary(array $samples, array $targets, array $l /** * To be overwritten by OneVsRest classifiers. - * - * @return void */ abstract protected function resetBinary(): void; @@ -174,4 +150,27 @@ abstract protected function predictProbability(array $sample, string $label); * @return mixed */ abstract protected function predictSampleBinary(array $sample); + + /** + * Groups all targets into two groups: Targets equal to + * the given label and the others + * + * $targets is not passed by reference nor contains objects so this method + * changes will not affect the caller $targets array. + * + * @param mixed $label + * + * @return array Binarized targets and target's labels + */ + private function binarizeTargets(array $targets, $label): array + { + $notLabel = "not_$label"; + foreach ($targets as $key => $target) { + $targets[$key] = $target == $label ? $label : $notLabel; + } + + $labels = [$label, $notLabel]; + + return [$targets, $labels]; + } } diff --git a/src/Phpml/Helper/Optimizer/ConjugateGradient.php b/src/Phpml/Helper/Optimizer/ConjugateGradient.php index c119eae2..153ffcb2 100644 --- a/src/Phpml/Helper/Optimizer/ConjugateGradient.php +++ b/src/Phpml/Helper/Optimizer/ConjugateGradient.php @@ -4,6 +4,8 @@ namespace Phpml\Helper\Optimizer; +use Closure; + /** * Conjugate Gradient method to solve a non-linear f(x) with respect to unknown x * See https://en.wikipedia.org/wiki/Nonlinear_conjugate_gradient_method) @@ -17,7 +19,7 @@ */ class ConjugateGradient extends GD { - public function runOptimization(array $samples, array $targets, \Closure $gradientCb) : array + public function runOptimization(array $samples, array $targets, Closure $gradientCb): array { $this->samples = $samples; $this->targets = $targets; @@ -25,7 +27,7 @@ public function runOptimization(array $samples, array $targets, \Closure $gradie $this->sampleCount = count($samples); $this->costValues = []; - $d = mp::muls($this->gradient($this->theta), -1); + $d = MP::muls($this->gradient($this->theta), -1); for ($i = 0; $i < $this->maxIterations; ++$i) { // Obtain α that minimizes f(θ + α.d) @@ -59,7 +61,7 @@ public function runOptimization(array $samples, array $targets, \Closure $gradie * Executes the callback function for the problem and returns * sum of the gradient for all samples & targets. */ - protected function gradient(array $theta) : array + protected function gradient(array $theta): array { [, $gradient] = parent::gradient($theta); @@ -69,7 +71,7 @@ protected function gradient(array $theta) : array /** * Returns the value of f(x) for given solution */ - protected function cost(array $theta) : float + protected function cost(array $theta): float { [$cost] = parent::gradient($theta); @@ -90,14 +92,14 @@ protected function cost(array $theta) : float * b-1) If cost function decreases, continue enlarging alpha * b-2) If cost function increases, take the midpoint and try again */ - protected function getAlpha(float $d) : float + protected function getAlpha(float $d): float { $small = 0.0001 * $d; $large = 0.01 * $d; // Obtain θ + α.d for two initial values, x0 and x1 - $x0 = mp::adds($this->theta, $small); - $x1 = mp::adds($this->theta, $large); + $x0 = MP::adds($this->theta, $small); + $x1 = MP::adds($this->theta, $large); $epsilon = 0.0001; $iteration = 0; @@ -113,9 +115,9 @@ protected function getAlpha(float $d) : float if ($fx1 < $fx0) { $x0 = $x1; - $x1 = mp::adds($x1, 0.01); // Enlarge second + $x1 = MP::adds($x1, 0.01); // Enlarge second } else { - $x1 = mp::divs(mp::add($x1, $x0), 2.0); + $x1 = MP::divs(MP::add($x1, $x0), 2.0); } // Get to the midpoint $error = $fx1 / $this->dimensions; @@ -135,7 +137,7 @@ protected function getAlpha(float $d) : float * * θ(k+1) = θ(k) + α.d */ - protected function getNewTheta(float $alpha, array $d) : array + protected function getNewTheta(float $alpha, array $d): array { $theta = $this->theta; @@ -164,7 +166,7 @@ protected function getNewTheta(float $alpha, array $d) : array * See: * R. Fletcher and C. M. Reeves, "Function minimization by conjugate gradients", Comput. J. 7 (1964), 149–154. */ - protected function getBeta(array $newTheta) : float + protected function getBeta(array $newTheta): float { $dNew = array_sum($this->gradient($newTheta)); $dOld = array_sum($this->gradient($this->theta)) + 1e-100; @@ -177,11 +179,11 @@ protected function getBeta(array $newTheta) : float * * d(k+1) =–∇f(x(k+1)) + β(k).d(k) */ - protected function getNewDirection(array $theta, float $beta, array $d) : array + protected function getNewDirection(array $theta, float $beta, array $d): array { $grad = $this->gradient($theta); - return mp::add(mp::muls($grad, -1), mp::muls($d, $beta)); + return MP::add(MP::muls($grad, -1), MP::muls($d, $beta)); } } @@ -189,12 +191,12 @@ protected function getNewDirection(array $theta, float $beta, array $d) : array * Handles element-wise vector operations between vector-vector * and vector-scalar variables */ -class mp +class MP { /** * Element-wise multiplication of two vectors of the same size */ - public static function mul(array $m1, array $m2) : array + public static function mul(array $m1, array $m2): array { $res = []; foreach ($m1 as $i => $val) { @@ -207,7 +209,7 @@ public static function mul(array $m1, array $m2) : array /** * Element-wise division of two vectors of the same size */ - public static function div(array $m1, array $m2) : array + public static function div(array $m1, array $m2): array { $res = []; foreach ($m1 as $i => $val) { @@ -220,7 +222,7 @@ public static function div(array $m1, array $m2) : array /** * Element-wise addition of two vectors of the same size */ - public static function add(array $m1, array $m2, int $mag = 1) : array + public static function add(array $m1, array $m2, int $mag = 1): array { $res = []; foreach ($m1 as $i => $val) { @@ -233,7 +235,7 @@ public static function add(array $m1, array $m2, int $mag = 1) : array /** * Element-wise subtraction of two vectors of the same size */ - public static function sub(array $m1, array $m2) : array + public static function sub(array $m1, array $m2): array { return self::add($m1, $m2, -1); } @@ -241,7 +243,7 @@ public static function sub(array $m1, array $m2) : array /** * Element-wise multiplication of a vector with a scalar */ - public static function muls(array $m1, float $m2) : array + public static function muls(array $m1, float $m2): array { $res = []; foreach ($m1 as $val) { @@ -254,7 +256,7 @@ public static function muls(array $m1, float $m2) : array /** * Element-wise division of a vector with a scalar */ - public static function divs(array $m1, float $m2) : array + public static function divs(array $m1, float $m2): array { $res = []; foreach ($m1 as $val) { @@ -267,7 +269,7 @@ public static function divs(array $m1, float $m2) : array /** * Element-wise addition of a vector with a scalar */ - public static function adds(array $m1, float $m2, int $mag = 1) : array + public static function adds(array $m1, float $m2, int $mag = 1): array { $res = []; foreach ($m1 as $val) { @@ -280,7 +282,7 @@ public static function adds(array $m1, float $m2, int $mag = 1) : array /** * Element-wise subtraction of a vector with a scalar */ - public static function subs(array $m1, float $m2) : array + public static function subs(array $m1, float $m2): array { return self::adds($m1, $m2, -1); } diff --git a/src/Phpml/Helper/Optimizer/GD.php b/src/Phpml/Helper/Optimizer/GD.php index 38b4253c..4eadf281 100644 --- a/src/Phpml/Helper/Optimizer/GD.php +++ b/src/Phpml/Helper/Optimizer/GD.php @@ -4,6 +4,8 @@ namespace Phpml\Helper\Optimizer; +use Closure; + /** * Batch version of Gradient Descent to optimize the weights * of a classifier given samples, targets and the objective function to minimize @@ -17,7 +19,7 @@ class GD extends StochasticGD */ protected $sampleCount = null; - public function runOptimization(array $samples, array $targets, \Closure $gradientCb) : array + public function runOptimization(array $samples, array $targets, Closure $gradientCb): array { $this->samples = $samples; $this->targets = $targets; @@ -51,7 +53,7 @@ public function runOptimization(array $samples, array $targets, \Closure $gradie * Calculates gradient, cost function and penalty term for each sample * then returns them as an array of values */ - protected function gradient(array $theta) : array + protected function gradient(array $theta): array { $costs = []; $gradient = []; diff --git a/src/Phpml/Helper/Optimizer/Optimizer.php b/src/Phpml/Helper/Optimizer/Optimizer.php index ee613215..2b25acde 100644 --- a/src/Phpml/Helper/Optimizer/Optimizer.php +++ b/src/Phpml/Helper/Optimizer/Optimizer.php @@ -4,6 +4,9 @@ namespace Phpml\Helper\Optimizer; +use Closure; +use Exception; + abstract class Optimizer { /** @@ -11,7 +14,7 @@ abstract class Optimizer * * @var array */ - protected $theta; + protected $theta = []; /** * Number of dimensions @@ -30,7 +33,7 @@ public function __construct(int $dimensions) // Inits the weights randomly $this->theta = []; for ($i = 0; $i < $this->dimensions; ++$i) { - $this->theta[] = rand() / (float) getrandmax(); + $this->theta[] = random_int(0, getrandmax()) / (float) getrandmax(); } } @@ -44,7 +47,7 @@ public function __construct(int $dimensions) public function setInitialTheta(array $theta) { if (count($theta) != $this->dimensions) { - throw new \Exception("Number of values in the weights array should be $this->dimensions"); + throw new Exception("Number of values in the weights array should be $this->dimensions"); } $this->theta = $theta; @@ -56,5 +59,5 @@ public function setInitialTheta(array $theta) * Executes the optimization with the given samples & targets * and returns the weights */ - abstract public function runOptimization(array $samples, array $targets, \Closure $gradientCb); + abstract public function runOptimization(array $samples, array $targets, Closure $gradientCb); } diff --git a/src/Phpml/Helper/Optimizer/StochasticGD.php b/src/Phpml/Helper/Optimizer/StochasticGD.php index f1b0979c..07ad216e 100644 --- a/src/Phpml/Helper/Optimizer/StochasticGD.php +++ b/src/Phpml/Helper/Optimizer/StochasticGD.php @@ -4,6 +4,8 @@ namespace Phpml\Helper\Optimizer; +use Closure; + /** * Stochastic Gradient Descent optimization method * to find a solution for the equation A.ϴ = y where @@ -66,6 +68,7 @@ class StochasticGD extends Optimizer * @var bool */ protected $enableEarlyStop = true; + /** * List of values obtained by evaluating the cost function at each iteration * of the algorithm @@ -141,7 +144,7 @@ public function setMaxIterations(int $maxIterations) * The cost function to minimize and the gradient of the function are to be * handled by the callback function provided as the third parameter of the method. */ - public function runOptimization(array $samples, array $targets, \Closure $gradientCb) : array + public function runOptimization(array $samples, array $targets, Closure $gradientCb): array { $this->samples = $samples; $this->targets = $targets; @@ -181,7 +184,16 @@ public function runOptimization(array $samples, array $targets, \Closure $gradie return $this->theta = $bestTheta; } - protected function updateTheta() : float + /** + * Returns the list of cost values for each iteration executed in + * last run of the optimization + */ + public function getCostValues(): array + { + return $this->costValues; + } + + protected function updateTheta(): float { $jValue = 0.0; $theta = $this->theta; @@ -237,15 +249,6 @@ function ($w1, $w2) { return false; } - /** - * Returns the list of cost values for each iteration executed in - * last run of the optimization - */ - public function getCostValues() : array - { - return $this->costValues; - } - /** * Clears the optimizer internal vars after the optimization process. */ diff --git a/src/Phpml/Math/Distance.php b/src/Phpml/Math/Distance.php index 696ee4b1..9faa8e09 100644 --- a/src/Phpml/Math/Distance.php +++ b/src/Phpml/Math/Distance.php @@ -10,5 +10,5 @@ interface Distance * @param array $a * @param array $b */ - public function distance(array $a, array $b) : float; + public function distance(array $a, array $b): float; } diff --git a/src/Phpml/Math/Distance/Chebyshev.php b/src/Phpml/Math/Distance/Chebyshev.php index 40cdfbc4..52e969cb 100644 --- a/src/Phpml/Math/Distance/Chebyshev.php +++ b/src/Phpml/Math/Distance/Chebyshev.php @@ -12,7 +12,7 @@ class Chebyshev implements Distance /** * @throws InvalidArgumentException */ - public function distance(array $a, array $b) : float + public function distance(array $a, array $b): float { if (count($a) !== count($b)) { throw InvalidArgumentException::arraySizeNotMatch(); diff --git a/src/Phpml/Math/Distance/Euclidean.php b/src/Phpml/Math/Distance/Euclidean.php index f6a87cf1..4ecc576e 100644 --- a/src/Phpml/Math/Distance/Euclidean.php +++ b/src/Phpml/Math/Distance/Euclidean.php @@ -12,7 +12,7 @@ class Euclidean implements Distance /** * @throws InvalidArgumentException */ - public function distance(array $a, array $b) : float + public function distance(array $a, array $b): float { if (count($a) !== count($b)) { throw InvalidArgumentException::arraySizeNotMatch(); @@ -30,7 +30,7 @@ public function distance(array $a, array $b) : float /** * Square of Euclidean distance */ - public function sqDistance(array $a, array $b) : float + public function sqDistance(array $a, array $b): float { return $this->distance($a, $b) ** 2; } diff --git a/src/Phpml/Math/Distance/Manhattan.php b/src/Phpml/Math/Distance/Manhattan.php index 6d10b71c..457333c4 100644 --- a/src/Phpml/Math/Distance/Manhattan.php +++ b/src/Phpml/Math/Distance/Manhattan.php @@ -12,7 +12,7 @@ class Manhattan implements Distance /** * @throws InvalidArgumentException */ - public function distance(array $a, array $b) : float + public function distance(array $a, array $b): float { if (count($a) !== count($b)) { throw InvalidArgumentException::arraySizeNotMatch(); diff --git a/src/Phpml/Math/Distance/Minkowski.php b/src/Phpml/Math/Distance/Minkowski.php index 17df39d2..5ff7364f 100644 --- a/src/Phpml/Math/Distance/Minkowski.php +++ b/src/Phpml/Math/Distance/Minkowski.php @@ -22,7 +22,7 @@ public function __construct(float $lambda = 3.0) /** * @throws InvalidArgumentException */ - public function distance(array $a, array $b) : float + public function distance(array $a, array $b): float { if (count($a) !== count($b)) { throw InvalidArgumentException::arraySizeNotMatch(); diff --git a/src/Phpml/Math/Kernel.php b/src/Phpml/Math/Kernel.php index 6d1461fd..9a2cb97b 100644 --- a/src/Phpml/Math/Kernel.php +++ b/src/Phpml/Math/Kernel.php @@ -7,10 +7,10 @@ interface Kernel { /** - * @param float $a - * @param float $b + * @param float|array $a + * @param float|array $b * - * @return float + * @return float|array */ public function compute($a, $b); } diff --git a/src/Phpml/Math/Kernel/RBF.php b/src/Phpml/Math/Kernel/RBF.php index e47dbb52..4f9cfaf6 100644 --- a/src/Phpml/Math/Kernel/RBF.php +++ b/src/Phpml/Math/Kernel/RBF.php @@ -23,12 +23,11 @@ public function __construct(float $gamma) * @param array $a * @param array $b */ - public function compute($a, $b) + public function compute($a, $b): float { $score = 2 * Product::scalar($a, $b); $squares = Product::scalar($a, $a) + Product::scalar($b, $b); - $result = exp(-$this->gamma * ($squares - $score)); - return $result; + return exp(-$this->gamma * ($squares - $score)); } } diff --git a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php index 6261d26b..4d7f662a 100644 --- a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -1,6 +1,7 @@ V; + + // Always return the eigenvectors of length 1.0 + $vectors = new Matrix($vectors); + $vectors = array_map(function ($vect) { + $sum = 0; + for ($i = 0; $i < count($vect); ++$i) { + $sum += $vect[$i] ** 2; + } + + $sum = sqrt($sum); + for ($i = 0; $i < count($vect); ++$i) { + $vect[$i] /= $sum; + } + + return $vect; + }, $vectors->transpose()->toArray()); + + return $vectors; + } + + /** + * Return the real parts of the eigenvalues
+ * d = real(diag(D)); + */ + public function getRealEigenvalues(): array + { + return $this->d; + } + + /** + * Return the imaginary parts of the eigenvalues
+ * d = imag(diag(D)) + */ + public function getImagEigenvalues(): array + { + return $this->e; + } + + /** + * Return the block diagonal eigenvalue matrix + */ + public function getDiagonalEigenvalues(): array + { + $D = []; + + for ($i = 0; $i < $this->n; ++$i) { + $D[$i] = array_fill(0, $this->n, 0.0); + $D[$i][$i] = $this->d[$i]; + if ($this->e[$i] == 0) { + continue; + } + + $o = ($this->e[$i] > 0) ? $i + 1 : $i - 1; + $D[$i][$o] = $this->e[$i]; + } + + return $D; + } + /** * Symmetric Householder reduction to tridiagonal form. */ @@ -158,6 +226,7 @@ private function tred2(): void for ($j = 0; $j < $i; ++$j) { $this->e[$j] = 0.0; } + // Apply similarity transformation to remaining columns. for ($j = 0; $j < $i; ++$j) { $f = $this->d[$j]; @@ -168,6 +237,7 @@ private function tred2(): void $g += $this->V[$k][$j] * $this->d[$k]; $this->e[$k] += $this->V[$k][$j] * $f; } + $this->e[$j] = $g; } @@ -185,16 +255,19 @@ private function tred2(): void for ($j = 0; $j < $i; ++$j) { $this->e[$j] -= $hh * $this->d[$j]; } + for ($j = 0; $j < $i; ++$j) { $f = $this->d[$j]; $g = $this->e[$j]; for ($k = $j; $k <= $i_; ++$k) { $this->V[$k][$j] -= ($f * $this->e[$k] + $g * $this->d[$k]); } + $this->d[$j] = $this->V[$i - 1][$j]; $this->V[$i][$j] = 0.0; } } + $this->d[$i] = $h; } @@ -207,16 +280,19 @@ private function tred2(): void for ($k = 0; $k <= $i; ++$k) { $this->d[$k] = $this->V[$k][$i + 1] / $h; } + for ($j = 0; $j <= $i; ++$j) { $g = 0.0; for ($k = 0; $k <= $i; ++$k) { $g += $this->V[$k][$i + 1] * $this->V[$k][$j]; } + for ($k = 0; $k <= $i; ++$k) { $this->V[$k][$j] -= $g * $this->d[$k]; } } } + for ($k = 0; $k <= $i; ++$k) { $this->V[$k][$i + 1] = 0.0; } @@ -241,6 +317,7 @@ private function tql2(): void for ($i = 1; $i < $this->n; ++$i) { $this->e[$i - 1] = $this->e[$i]; } + $this->e[$this->n - 1] = 0.0; $f = 0.0; $tst1 = 0.0; @@ -254,8 +331,10 @@ private function tql2(): void if (abs($this->e[$m]) <= $eps * $tst1) { break; } + ++$m; } + // If m == l, $this->d[l] is an eigenvalue, // otherwise, iterate. if ($m > $l) { @@ -270,6 +349,7 @@ private function tql2(): void if ($p < 0) { $r *= -1; } + $this->d[$l] = $this->e[$l] / ($p + $r); $this->d[$l + 1] = $this->e[$l] * ($p + $r); $dl1 = $this->d[$l + 1]; @@ -277,6 +357,7 @@ private function tql2(): void for ($i = $l + 2; $i < $this->n; ++$i) { $this->d[$i] -= $h; } + $f += $h; // Implicit QL transformation. $p = $this->d[$m]; @@ -303,12 +384,14 @@ private function tql2(): void $this->V[$k][$i] = $c * $this->V[$k][$i] - $s * $h; } } + $p = -$s * $s2 * $c3 * $el1 * $this->e[$l] / $dl1; $this->e[$l] = $s * $p; $this->d[$l] = $c * $p; // Check for convergence. } while (abs($this->e[$l]) > $eps * $tst1); } + $this->d[$l] = $this->d[$l] + $f; $this->e[$l] = 0.0; } @@ -323,6 +406,7 @@ private function tql2(): void $p = $this->d[$j]; } } + if ($k != $i) { $this->d[$k] = $this->d[$i]; $this->d[$i] = $p; @@ -354,6 +438,7 @@ private function orthes(): void for ($i = $m; $i <= $high; ++$i) { $scale = $scale + abs($this->H[$i][$m - 1]); } + if ($scale != 0.0) { // Compute Householder transformation. $h = 0.0; @@ -361,10 +446,12 @@ private function orthes(): void $this->ort[$i] = $this->H[$i][$m - 1] / $scale; $h += $this->ort[$i] * $this->ort[$i]; } + $g = sqrt($h); if ($this->ort[$m] > 0) { $g *= -1; } + $h -= $this->ort[$m] * $g; $this->ort[$m] -= $g; // Apply Householder similarity transformation @@ -374,21 +461,25 @@ private function orthes(): void for ($i = $high; $i >= $m; --$i) { $f += $this->ort[$i] * $this->H[$i][$j]; } + $f /= $h; for ($i = $m; $i <= $high; ++$i) { $this->H[$i][$j] -= $f * $this->ort[$i]; } } + for ($i = 0; $i <= $high; ++$i) { $f = 0.0; for ($j = $high; $j >= $m; --$j) { $f += $this->ort[$j] * $this->H[$i][$j]; } + $f = $f / $h; for ($j = $m; $j <= $high; ++$j) { $this->H[$i][$j] -= $f * $this->ort[$j]; } } + $this->ort[$m] = $scale * $this->ort[$m]; $this->H[$m][$m - 1] = $scale * $g; } @@ -400,16 +491,19 @@ private function orthes(): void $this->V[$i][$j] = ($i == $j ? 1.0 : 0.0); } } + for ($m = $high - 1; $m >= $low + 1; --$m) { if ($this->H[$m][$m - 1] != 0.0) { for ($i = $m + 1; $i <= $high; ++$i) { $this->ort[$i] = $this->H[$i][$m - 1]; } + for ($j = $m; $j <= $high; ++$j) { $g = 0.0; for ($i = $m; $i <= $high; ++$i) { $g += $this->ort[$i] * $this->V[$i][$j]; } + // Double division avoids possible underflow $g = ($g / $this->ort[$m]) / $this->H[$m][$m - 1]; for ($i = $m; $i <= $high; ++$i) { @@ -469,6 +563,7 @@ private function hqr2(): void $this->d[$i] = $this->H[$i][$i]; $this->e[$i] = 0.0; } + for ($j = max($i - 1, 0); $j < $nn; ++$j) { $norm = $norm + abs($this->H[$i][$j]); } @@ -484,11 +579,14 @@ private function hqr2(): void if ($s == 0.0) { $s = $norm; } + if (abs($this->H[$l][$l - 1]) < $eps * $s) { break; } + --$l; } + // Check for convergence // One root found if ($l == $n) { @@ -513,11 +611,13 @@ private function hqr2(): void } else { $z = $p - $z; } + $this->d[$n - 1] = $x + $z; $this->d[$n] = $this->d[$n - 1]; if ($z != 0.0) { $this->d[$n] = $x - $w / $z; } + $this->e[$n - 1] = 0.0; $this->e[$n] = 0.0; $x = $this->H[$n][$n - 1]; @@ -533,18 +633,21 @@ private function hqr2(): void $this->H[$n - 1][$j] = $q * $z + $p * $this->H[$n][$j]; $this->H[$n][$j] = $q * $this->H[$n][$j] - $p * $z; } + // Column modification for ($i = 0; $i <= $n; ++$i) { $z = $this->H[$i][$n - 1]; $this->H[$i][$n - 1] = $q * $z + $p * $this->H[$i][$n]; $this->H[$i][$n] = $q * $this->H[$i][$n] - $p * $z; } + // Accumulate transformations for ($i = $low; $i <= $high; ++$i) { $z = $this->V[$i][$n - 1]; $this->V[$i][$n - 1] = $q * $z + $p * $this->V[$i][$n]; $this->V[$i][$n] = $q * $this->V[$i][$n] - $p * $z; } + // Complex pair } else { $this->d[$n - 1] = $x + $p; @@ -552,6 +655,7 @@ private function hqr2(): void $this->e[$n - 1] = $z; $this->e[$n] = -$z; } + $n = $n - 2; $iter = 0; // No convergence yet @@ -564,16 +668,19 @@ private function hqr2(): void $y = $this->H[$n - 1][$n - 1]; $w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n]; } + // Wilkinson's original ad hoc shift if ($iter == 10) { $exshift += $x; for ($i = $low; $i <= $n; ++$i) { $this->H[$i][$i] -= $x; } + $s = abs($this->H[$n][$n - 1]) + abs($this->H[$n - 1][$n - 2]); $x = $y = 0.75 * $s; $w = -0.4375 * $s * $s; } + // MATLAB's new ad hoc shift if ($iter == 30) { $s = ($y - $x) / 2.0; @@ -583,14 +690,17 @@ private function hqr2(): void if ($y < $x) { $s = -$s; } + $s = $x - $w / (($y - $x) / 2.0 + $s); for ($i = $low; $i <= $n; ++$i) { $this->H[$i][$i] -= $s; } + $exshift += $s; $x = $y = $w = 0.964; } } + // Could check iteration count here. $iter = $iter + 1; // Look for two consecutive small sub-diagonal elements @@ -609,18 +719,22 @@ private function hqr2(): void if ($m == $l) { break; } + if (abs($this->H[$m][$m - 1]) * (abs($q) + abs($r)) < $eps * (abs($p) * (abs($this->H[$m - 1][$m - 1]) + abs($z) + abs($this->H[$m + 1][$m + 1])))) { break; } + --$m; } + for ($i = $m + 2; $i <= $n; ++$i) { $this->H[$i][$i - 2] = 0.0; if ($i > $m + 2) { $this->H[$i][$i - 3] = 0.0; } } + // Double QR step involving rows l:n and columns m:n for ($k = $m; $k <= $n - 1; ++$k) { $notlast = ($k != $n - 1); @@ -635,19 +749,23 @@ private function hqr2(): void $r = $r / $x; } } + if ($x == 0.0) { break; } + $s = sqrt($p * $p + $q * $q + $r * $r); if ($p < 0) { $s = -$s; } + if ($s != 0) { if ($k != $m) { $this->H[$k][$k - 1] = -$s * $x; } elseif ($l != $m) { $this->H[$k][$k - 1] = -$this->H[$k][$k - 1]; } + $p = $p + $s; $x = $p / $s; $y = $q / $s; @@ -661,9 +779,11 @@ private function hqr2(): void $p = $p + $r * $this->H[$k + 2][$j]; $this->H[$k + 2][$j] = $this->H[$k + 2][$j] - $p * $z; } + $this->H[$k][$j] = $this->H[$k][$j] - $p * $x; $this->H[$k + 1][$j] = $this->H[$k + 1][$j] - $p * $y; } + // Column modification for ($i = 0; $i <= min($n, $k + 3); ++$i) { $p = $x * $this->H[$i][$k] + $y * $this->H[$i][$k + 1]; @@ -671,9 +791,11 @@ private function hqr2(): void $p = $p + $z * $this->H[$i][$k + 2]; $this->H[$i][$k + 2] = $this->H[$i][$k + 2] - $p * $r; } + $this->H[$i][$k] = $this->H[$i][$k] - $p; $this->H[$i][$k + 1] = $this->H[$i][$k + 1] - $p * $q; } + // Accumulate transformations for ($i = $low; $i <= $high; ++$i) { $p = $x * $this->V[$i][$k] + $y * $this->V[$i][$k + 1]; @@ -681,6 +803,7 @@ private function hqr2(): void $p = $p + $z * $this->V[$i][$k + 2]; $this->V[$i][$k + 2] = $this->V[$i][$k + 2] - $p * $r; } + $this->V[$i][$k] = $this->V[$i][$k] - $p; $this->V[$i][$k + 1] = $this->V[$i][$k + 1] - $p * $q; } @@ -719,6 +842,7 @@ private function hqr2(): void } else { $this->H[$i][$n] = -$r / ($eps * $norm); } + // Solve real equations } else { $x = $this->H[$i][$i + 1]; @@ -732,6 +856,7 @@ private function hqr2(): void $this->H[$i + 1][$n] = (-$s - $y * $t) / $z; } } + // Overflow control $t = abs($this->H[$i][$n]); if (($eps * $t) * $t > 1) { @@ -741,6 +866,7 @@ private function hqr2(): void } } } + // Complex vector } elseif ($q < 0) { $l = $n - 1; @@ -753,6 +879,7 @@ private function hqr2(): void $this->H[$n - 1][$n - 1] = $this->cdivr; $this->H[$n - 1][$n] = $this->cdivi; } + $this->H[$n][$n - 1] = 0.0; $this->H[$n][$n] = 1.0; for ($i = $n - 2; $i >= 0; --$i) { @@ -763,6 +890,7 @@ private function hqr2(): void $ra = $ra + $this->H[$i][$j] * $this->H[$j][$n - 1]; $sa = $sa + $this->H[$i][$j] * $this->H[$j][$n]; } + $w = $this->H[$i][$i] - $p; if ($this->e[$i] < 0.0) { $z = $w; @@ -783,6 +911,7 @@ private function hqr2(): void if ($vr == 0.0 & $vi == 0.0) { $vr = $eps * $norm * (abs($w) + abs($q) + abs($x) + abs($y) + abs($z)); } + $this->cdiv($x * $r - $z * $ra + $q * $sa, $x * $s - $z * $sa - $q * $ra, $vr, $vi); $this->H[$i][$n - 1] = $this->cdivr; $this->H[$i][$n] = $this->cdivi; @@ -795,6 +924,7 @@ private function hqr2(): void $this->H[$i + 1][$n] = $this->cdivi; } } + // Overflow control $t = max(abs($this->H[$i][$n - 1]), abs($this->H[$i][$n])); if (($eps * $t) * $t > 1) { @@ -824,81 +954,9 @@ private function hqr2(): void for ($k = $low; $k <= min($j, $high); ++$k) { $z = $z + $this->V[$i][$k] * $this->H[$k][$j]; } - $this->V[$i][$j] = $z; - } - } - } - - /** - * Return the eigenvector matrix - * - * @return array - */ - public function getEigenvectors() - { - $vectors = $this->V; - - // Always return the eigenvectors of length 1.0 - $vectors = new Matrix($vectors); - $vectors = array_map(function ($vect) { - $sum = 0; - for ($i = 0; $i < count($vect); ++$i) { - $sum += $vect[$i] ** 2; - } - - $sum = sqrt($sum); - for ($i = 0; $i < count($vect); ++$i) { - $vect[$i] /= $sum; - } - - return $vect; - }, $vectors->transpose()->toArray()); - - return $vectors; - } - - /** - * Return the real parts of the eigenvalues
- * d = real(diag(D)); - * - * @return array - */ - public function getRealEigenvalues() - { - return $this->d; - } - - /** - * Return the imaginary parts of the eigenvalues
- * d = imag(diag(D)) - * - * @return array - */ - public function getImagEigenvalues() - { - return $this->e; - } - /** - * Return the block diagonal eigenvalue matrix - * - * @return array - */ - public function getDiagonalEigenvalues() - { - $D = []; - - for ($i = 0; $i < $this->n; ++$i) { - $D[$i] = array_fill(0, $this->n, 0.0); - $D[$i][$i] = $this->d[$i]; - if ($this->e[$i] == 0) { - continue; + $this->V[$i][$j] = $z; } - - $o = ($this->e[$i] > 0) ? $i + 1 : $i - 1; - $D[$i][$o] = $this->e[$i]; } - - return $D; } } diff --git a/src/Phpml/Math/LinearAlgebra/LUDecomposition.php b/src/Phpml/Math/LinearAlgebra/LUDecomposition.php index 164a72f6..6ebd8cbe 100644 --- a/src/Phpml/Math/LinearAlgebra/LUDecomposition.php +++ b/src/Phpml/Math/LinearAlgebra/LUDecomposition.php @@ -1,6 +1,7 @@ m; ++$i) { $this->piv[$i] = $i; } + $this->pivsign = 1; $LUcolj = []; @@ -99,6 +101,7 @@ public function __construct(Matrix $A) for ($i = 0; $i < $this->m; ++$i) { $LUcolj[$i] = &$this->LU[$i][$j]; } + // Apply previous transformations. for ($i = 0; $i < $this->m; ++$i) { $LUrowi = $this->LU[$i]; @@ -108,8 +111,10 @@ public function __construct(Matrix $A) for ($k = 0; $k < $kmax; ++$k) { $s += $LUrowi[$k] * $LUcolj[$k]; } + $LUrowi[$j] = $LUcolj[$i] -= $s; } + // Find pivot and exchange if necessary. $p = $j; for ($i = $j + 1; $i < $this->m; ++$i) { @@ -117,17 +122,20 @@ public function __construct(Matrix $A) $p = $i; } } + if ($p != $j) { for ($k = 0; $k < $this->n; ++$k) { $t = $this->LU[$p][$k]; $this->LU[$p][$k] = $this->LU[$j][$k]; $this->LU[$j][$k] = $t; } + $k = $this->piv[$p]; $this->piv[$p] = $this->piv[$j]; $this->piv[$j] = $k; $this->pivsign = $this->pivsign * -1; } + // Compute multipliers. if (($j < $this->m) && ($this->LU[$j][$j] != 0.0)) { for ($i = $j + 1; $i < $this->m; ++$i) { @@ -142,7 +150,7 @@ public function __construct(Matrix $A) * * @return Matrix Lower triangular factor */ - public function getL() : Matrix + public function getL(): Matrix { $L = []; for ($i = 0; $i < $this->m; ++$i) { @@ -165,7 +173,7 @@ public function getL() : Matrix * * @return Matrix Upper triangular factor */ - public function getU() : Matrix + public function getU(): Matrix { $U = []; for ($i = 0; $i < $this->n; ++$i) { @@ -186,7 +194,7 @@ public function getU() : Matrix * * @return array Pivot vector */ - public function getPivot() : array + public function getPivot(): array { return $this->piv; } @@ -247,7 +255,7 @@ public function det() * * @throws MatrixException */ - public function solve(Matrix $B) : array + public function solve(Matrix $B): array { if ($B->getRows() != $this->m) { throw MatrixException::notSquareMatrix(); @@ -268,11 +276,13 @@ public function solve(Matrix $B) : array } } } + // Solve U*X = Y; for ($k = $this->n - 1; $k >= 0; --$k) { for ($j = 0; $j < $nx; ++$j) { $X[$k][$j] /= $this->LU[$k][$k]; } + for ($i = 0; $i < $k; ++$i) { for ($j = 0; $j < $nx; ++$j) { $X[$i][$j] -= $X[$k][$j] * $this->LU[$i][$k]; @@ -283,7 +293,7 @@ public function solve(Matrix $B) : array return $X; } - protected function getSubMatrix(array $matrix, array $RL, int $j0, int $jF) : array + protected function getSubMatrix(array $matrix, array $RL, int $j0, int $jF): array { $m = count($RL); $n = $jF - $j0; diff --git a/src/Phpml/Math/Matrix.php b/src/Phpml/Math/Matrix.php index 6145521a..7c1ff3e2 100644 --- a/src/Phpml/Math/Matrix.php +++ b/src/Phpml/Math/Matrix.php @@ -13,7 +13,7 @@ class Matrix /** * @var array */ - private $matrix; + private $matrix = []; /** * @var int @@ -56,7 +56,7 @@ public function __construct(array $matrix, bool $validate = true) $this->matrix = $matrix; } - public static function fromFlatArray(array $array) : Matrix + public static function fromFlatArray(array $array): self { $matrix = []; foreach ($array as $value) { @@ -66,12 +66,12 @@ public static function fromFlatArray(array $array) : Matrix return new self($matrix); } - public function toArray() : array + public function toArray(): array { return $this->matrix; } - public function toScalar() : float + public function toScalar(): float { return $this->matrix[0][0]; } @@ -89,7 +89,7 @@ public function getColumns(): int /** * @throws MatrixException */ - public function getColumnValues($column) : array + public function getColumnValues($column): array { if ($column >= $this->columns) { throw MatrixException::columnOutOfRange(); @@ -123,7 +123,7 @@ public function isSquare(): bool return $this->columns === $this->rows; } - public function transpose() : Matrix + public function transpose(): self { if ($this->rows == 1) { $matrix = array_map(function ($el) { @@ -136,7 +136,7 @@ public function transpose() : Matrix return new self($matrix, false); } - public function multiply(Matrix $matrix) : Matrix + public function multiply(self $matrix): self { if ($this->columns != $matrix->getRows()) { throw InvalidArgumentException::inconsistentMatrixSupplied(); @@ -157,7 +157,7 @@ public function multiply(Matrix $matrix) : Matrix return new self($product, false); } - public function divideByScalar($value) : Matrix + public function divideByScalar($value): self { $newMatrix = []; for ($i = 0; $i < $this->rows; ++$i) { @@ -169,7 +169,7 @@ public function divideByScalar($value) : Matrix return new self($newMatrix, false); } - public function multiplyByScalar($value) : Matrix + public function multiplyByScalar($value): self { $newMatrix = []; for ($i = 0; $i < $this->rows; ++$i) { @@ -184,7 +184,7 @@ public function multiplyByScalar($value) : Matrix /** * Element-wise addition of the matrix with another one */ - public function add(Matrix $other) : Matrix + public function add(self $other): self { return $this->_add($other); } @@ -192,30 +192,12 @@ public function add(Matrix $other) : Matrix /** * Element-wise subtracting of another matrix from this one */ - public function subtract(Matrix $other) : Matrix + public function subtract(self $other): self { return $this->_add($other, -1); } - /** - * Element-wise addition or substraction depending on the given sign parameter - */ - protected function _add(Matrix $other, int $sign = 1) : Matrix - { - $a1 = $this->toArray(); - $a2 = $other->toArray(); - - $newMatrix = []; - for ($i = 0; $i < $this->rows; ++$i) { - for ($k = 0; $k < $this->columns; ++$k) { - $newMatrix[$i][$k] = $a1[$i][$k] + $sign * $a2[$i][$k]; - } - } - - return new self($newMatrix, false); - } - - public function inverse() : Matrix + public function inverse(): self { if (!$this->isSquare()) { throw MatrixException::notSquareMatrix(); @@ -228,20 +210,7 @@ public function inverse() : Matrix return new self($inverse, false); } - /** - * Returns diagonal identity matrix of the same size of this matrix - */ - protected function getIdentity() : Matrix - { - $array = array_fill(0, $this->rows, array_fill(0, $this->columns, 0)); - for ($i = 0; $i < $this->rows; ++$i) { - $array[$i][$i] = 1; - } - - return new self($array, false); - } - - public function crossOut(int $row, int $column) : Matrix + public function crossOut(int $row, int $column): self { $newMatrix = []; $r = 0; @@ -254,6 +223,7 @@ public function crossOut(int $row, int $column) : Matrix ++$c; } } + ++$r; } } @@ -261,15 +231,15 @@ public function crossOut(int $row, int $column) : Matrix return new self($newMatrix, false); } - public function isSingular() : bool + public function isSingular(): bool { - return 0 == $this->getDeterminant(); + return $this->getDeterminant() == 0; } /** * Returns the transpose of given array */ - public static function transposeArray(array $array) : array + public static function transposeArray(array $array): array { return (new self($array, false))->transpose()->toArray(); } @@ -278,11 +248,42 @@ public static function transposeArray(array $array) : array * Returns the dot product of two arrays
* Matrix::dot(x, y) ==> x.y' */ - public static function dot(array $array1, array $array2) : array + public static function dot(array $array1, array $array2): array { $m1 = new self($array1, false); $m2 = new self($array2, false); return $m1->multiply($m2->transpose())->toArray()[0]; } + + /** + * Element-wise addition or substraction depending on the given sign parameter + */ + protected function _add(self $other, int $sign = 1): self + { + $a1 = $this->toArray(); + $a2 = $other->toArray(); + + $newMatrix = []; + for ($i = 0; $i < $this->rows; ++$i) { + for ($k = 0; $k < $this->columns; ++$k) { + $newMatrix[$i][$k] = $a1[$i][$k] + $sign * $a2[$i][$k]; + } + } + + return new self($newMatrix, false); + } + + /** + * Returns diagonal identity matrix of the same size of this matrix + */ + protected function getIdentity(): self + { + $array = array_fill(0, $this->rows, array_fill(0, $this->columns, 0)); + for ($i = 0; $i < $this->rows; ++$i) { + $array[$i][$i] = 1; + } + + return new self($array, false); + } } diff --git a/src/Phpml/Math/Set.php b/src/Phpml/Math/Set.php index c5c5aa7c..a67d5c22 100644 --- a/src/Phpml/Math/Set.php +++ b/src/Phpml/Math/Set.php @@ -4,12 +4,15 @@ namespace Phpml\Math; -class Set implements \IteratorAggregate +use ArrayIterator; +use IteratorAggregate; + +class Set implements IteratorAggregate { /** * @var string[]|int[]|float[] */ - private $elements; + private $elements = []; /** * @param string[]|int[]|float[] $elements @@ -22,7 +25,7 @@ public function __construct(array $elements = []) /** * Creates the union of A and B. */ - public static function union(Set $a, Set $b) : Set + public static function union(self $a, self $b): self { return new self(array_merge($a->toArray(), $b->toArray())); } @@ -30,7 +33,7 @@ public static function union(Set $a, Set $b) : Set /** * Creates the intersection of A and B. */ - public static function intersection(Set $a, Set $b) : Set + public static function intersection(self $a, self $b): self { return new self(array_intersect($a->toArray(), $b->toArray())); } @@ -38,7 +41,7 @@ public static function intersection(Set $a, Set $b) : Set /** * Creates the difference of A and B. */ - public static function difference(Set $a, Set $b) : Set + public static function difference(self $a, self $b): self { return new self(array_diff($a->toArray(), $b->toArray())); } @@ -48,7 +51,7 @@ public static function difference(Set $a, Set $b) : Set * * @return Set[] */ - public static function cartesian(Set $a, Set $b) : array + public static function cartesian(self $a, self $b): array { $cartesian = []; @@ -66,7 +69,7 @@ public static function cartesian(Set $a, Set $b) : array * * @return Set[] */ - public static function power(Set $a) : array + public static function power(self $a): array { $power = [new self()]; @@ -79,24 +82,10 @@ public static function power(Set $a) : array return $power; } - /** - * Removes duplicates and rewrites index. - * - * @param string[]|int[]|float[] $elements - * - * @return string[]|int[]|float[] - */ - private static function sanitize(array $elements) : array - { - sort($elements, SORT_ASC); - - return array_values(array_unique($elements, SORT_ASC)); - } - /** * @param string|int|float $element */ - public function add($element) : Set + public function add($element): self { return $this->addAll([$element]); } @@ -104,7 +93,7 @@ public function add($element) : Set /** * @param string[]|int[]|float[] $elements */ - public function addAll(array $elements) : Set + public function addAll(array $elements): self { $this->elements = self::sanitize(array_merge($this->elements, $elements)); @@ -114,7 +103,7 @@ public function addAll(array $elements) : Set /** * @param string|int|float $element */ - public function remove($element) : Set + public function remove($element): self { return $this->removeAll([$element]); } @@ -122,7 +111,7 @@ public function remove($element) : Set /** * @param string[]|int[]|float[] $elements */ - public function removeAll(array $elements) : Set + public function removeAll(array $elements): self { $this->elements = self::sanitize(array_diff($this->elements, $elements)); @@ -132,7 +121,7 @@ public function removeAll(array $elements) : Set /** * @param string|int|float $element */ - public function contains($element) : bool + public function contains($element): bool { return $this->containsAll([$element]); } @@ -140,7 +129,7 @@ public function contains($element) : bool /** * @param string[]|int[]|float[] $elements */ - public function containsAll(array $elements) : bool + public function containsAll(array $elements): bool { return !array_diff($elements, $this->elements); } @@ -148,23 +137,37 @@ public function containsAll(array $elements) : bool /** * @return string[]|int[]|float[] */ - public function toArray() : array + public function toArray(): array { return $this->elements; } - public function getIterator() : \ArrayIterator + public function getIterator(): ArrayIterator { - return new \ArrayIterator($this->elements); + return new ArrayIterator($this->elements); } - public function isEmpty() : bool + public function isEmpty(): bool { return $this->cardinality() == 0; } - public function cardinality() : int + public function cardinality(): int { return count($this->elements); } + + /** + * Removes duplicates and rewrites index. + * + * @param string[]|int[]|float[] $elements + * + * @return string[]|int[]|float[] + */ + private static function sanitize(array $elements): array + { + sort($elements, SORT_ASC); + + return array_values(array_unique($elements, SORT_ASC)); + } } diff --git a/src/Phpml/Math/Statistic/Correlation.php b/src/Phpml/Math/Statistic/Correlation.php index 9bcf271a..8803cbfb 100644 --- a/src/Phpml/Math/Statistic/Correlation.php +++ b/src/Phpml/Math/Statistic/Correlation.php @@ -14,7 +14,7 @@ class Correlation * * @throws InvalidArgumentException */ - public static function pearson(array $x, array $y) : float + public static function pearson(array $x, array $y): float { if (count($x) !== count($y)) { throw InvalidArgumentException::arraySizeNotMatch(); diff --git a/src/Phpml/Math/Statistic/Covariance.php b/src/Phpml/Math/Statistic/Covariance.php index 627a8a64..a669a7f3 100644 --- a/src/Phpml/Math/Statistic/Covariance.php +++ b/src/Phpml/Math/Statistic/Covariance.php @@ -4,6 +4,7 @@ namespace Phpml\Math\Statistic; +use Exception; use Phpml\Exception\InvalidArgumentException; class Covariance @@ -13,7 +14,7 @@ class Covariance * * @throws InvalidArgumentException */ - public static function fromXYArrays(array $x, array $y, bool $sample = true, ?float $meanX = null, ?float $meanY = null) : float + public static function fromXYArrays(array $x, array $y, bool $sample = true, ?float $meanX = null, ?float $meanY = null): float { if (empty($x) || empty($y)) { throw InvalidArgumentException::arrayCantBeEmpty(); @@ -51,7 +52,7 @@ public static function fromXYArrays(array $x, array $y, bool $sample = true, ?fl * @throws InvalidArgumentException * @throws \Exception */ - public static function fromDataset(array $data, int $i, int $k, bool $sample = true, ?float $meanX = null, ?float $meanY = null) : float + public static function fromDataset(array $data, int $i, int $k, bool $sample = true, ?float $meanX = null, ?float $meanY = null): float { if (empty($data)) { throw InvalidArgumentException::arrayCantBeEmpty(); @@ -63,7 +64,7 @@ public static function fromDataset(array $data, int $i, int $k, bool $sample = t } if ($i < 0 || $k < 0 || $i >= $n || $k >= $n) { - throw new \Exception('Given indices i and k do not match with the dimensionality of data'); + throw new Exception('Given indices i and k do not match with the dimensionality of data'); } if ($meanX === null || $meanY === null) { @@ -92,10 +93,12 @@ public static function fromDataset(array $data, int $i, int $k, bool $sample = t if ($index == $i) { $val[0] = $col - $meanX; } + if ($index == $k) { $val[1] = $col - $meanY; } } + $sum += $val[0] * $val[1]; } } @@ -112,7 +115,7 @@ public static function fromDataset(array $data, int $i, int $k, bool $sample = t * * @param array|null $means */ - public static function covarianceMatrix(array $data, ?array $means = null) : array + public static function covarianceMatrix(array $data, ?array $means = null): array { $n = count($data[0]); diff --git a/src/Phpml/Math/Statistic/Gaussian.php b/src/Phpml/Math/Statistic/Gaussian.php index bdf83081..24aaeea6 100644 --- a/src/Phpml/Math/Statistic/Gaussian.php +++ b/src/Phpml/Math/Statistic/Gaussian.php @@ -41,7 +41,7 @@ public function pdf(float $value) * Returns probability density value of the given $value based on * given standard deviation and the mean */ - public static function distributionPdf(float $mean, float $std, float $value) : float + public static function distributionPdf(float $mean, float $std, float $value): float { $normal = new self($mean, $std); diff --git a/src/Phpml/Math/Statistic/Mean.php b/src/Phpml/Math/Statistic/Mean.php index eb1baef2..8791a657 100644 --- a/src/Phpml/Math/Statistic/Mean.php +++ b/src/Phpml/Math/Statistic/Mean.php @@ -11,7 +11,7 @@ class Mean /** * @throws InvalidArgumentException */ - public static function arithmetic(array $numbers) : float + public static function arithmetic(array $numbers): float { self::checkArrayLength($numbers); @@ -32,7 +32,7 @@ public static function median(array $numbers) sort($numbers, SORT_NUMERIC); $median = $numbers[$middleIndex]; - if (0 === $count % 2) { + if ($count % 2 === 0) { $median = ($median + $numbers[$middleIndex - 1]) / 2; } diff --git a/src/Phpml/Math/Statistic/StandardDeviation.php b/src/Phpml/Math/Statistic/StandardDeviation.php index 3da8ef58..8a0d2411 100644 --- a/src/Phpml/Math/Statistic/StandardDeviation.php +++ b/src/Phpml/Math/Statistic/StandardDeviation.php @@ -13,7 +13,7 @@ class StandardDeviation * * @throws InvalidArgumentException */ - public static function population(array $a, bool $sample = true) : float + public static function population(array $a, bool $sample = true): float { if (empty($a)) { throw InvalidArgumentException::arrayCantBeEmpty(); diff --git a/src/Phpml/Metric/ClassificationReport.php b/src/Phpml/Metric/ClassificationReport.php index ae4c11aa..0f27b066 100644 --- a/src/Phpml/Metric/ClassificationReport.php +++ b/src/Phpml/Metric/ClassificationReport.php @@ -51,27 +51,27 @@ public function __construct(array $actualLabels, array $predictedLabels) $this->computeAverage(); } - public function getPrecision() : array + public function getPrecision(): array { return $this->precision; } - public function getRecall() : array + public function getRecall(): array { return $this->recall; } - public function getF1score() : array + public function getF1score(): array { return $this->f1score; } - public function getSupport() : array + public function getSupport(): array { return $this->support; } - public function getAverage() : array + public function getAverage(): array { return $this->average; } @@ -93,6 +93,7 @@ private function computeAverage(): void $this->average[$metric] = 0.0; continue; } + $this->average[$metric] = array_sum($values) / count($values); } } @@ -102,7 +103,8 @@ private function computeAverage(): void */ private function computePrecision(int $truePositive, int $falsePositive) { - if (0 == ($divider = $truePositive + $falsePositive)) { + $divider = $truePositive + $falsePositive; + if ($divider == 0) { return 0.0; } @@ -114,23 +116,25 @@ private function computePrecision(int $truePositive, int $falsePositive) */ private function computeRecall(int $truePositive, int $falseNegative) { - if (0 == ($divider = $truePositive + $falseNegative)) { + $divider = $truePositive + $falseNegative; + if ($divider == 0) { return 0.0; } return $truePositive / $divider; } - private function computeF1Score(float $precision, float $recall) : float + private function computeF1Score(float $precision, float $recall): float { - if (0 == ($divider = $precision + $recall)) { + $divider = $precision + $recall; + if ($divider == 0) { return 0.0; } return 2.0 * (($precision * $recall) / $divider); } - private static function getLabelIndexedArray(array $actualLabels, array $predictedLabels) : array + private static function getLabelIndexedArray(array $actualLabels, array $predictedLabels): array { $labels = array_values(array_unique(array_merge($actualLabels, $predictedLabels))); sort($labels); diff --git a/src/Phpml/Metric/ConfusionMatrix.php b/src/Phpml/Metric/ConfusionMatrix.php index 0f0b738f..e86a8ed9 100644 --- a/src/Phpml/Metric/ConfusionMatrix.php +++ b/src/Phpml/Metric/ConfusionMatrix.php @@ -6,7 +6,7 @@ class ConfusionMatrix { - public static function compute(array $actualLabels, array $predictedLabels, ?array $labels = null) : array + public static function compute(array $actualLabels, array $predictedLabels, ?array $labels = null): array { $labels = $labels ? array_flip($labels) : self::getUniqueLabels($actualLabels); $matrix = self::generateMatrixWithZeros($labels); @@ -31,7 +31,7 @@ public static function compute(array $actualLabels, array $predictedLabels, ?arr return $matrix; } - private static function generateMatrixWithZeros(array $labels) : array + private static function generateMatrixWithZeros(array $labels): array { $count = count($labels); $matrix = []; @@ -43,7 +43,7 @@ private static function generateMatrixWithZeros(array $labels) : array return $matrix; } - private static function getUniqueLabels(array $labels) : array + private static function getUniqueLabels(array $labels): array { $labels = array_values(array_unique($labels)); sort($labels); diff --git a/src/Phpml/ModelManager.php b/src/Phpml/ModelManager.php index 2fa14f5c..ebcdbe46 100644 --- a/src/Phpml/ModelManager.php +++ b/src/Phpml/ModelManager.php @@ -26,7 +26,7 @@ public function saveToFile(Estimator $estimator, string $filepath): void } } - public function restoreFromFile(string $filepath) : Estimator + public function restoreFromFile(string $filepath): Estimator { if (!file_exists($filepath) || !is_readable($filepath)) { throw FileException::cantOpenFile(basename($filepath)); diff --git a/src/Phpml/NeuralNetwork/ActivationFunction.php b/src/Phpml/NeuralNetwork/ActivationFunction.php index 65ba7b44..5b914257 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction.php @@ -9,5 +9,5 @@ interface ActivationFunction /** * @param float|int $value */ - public function compute($value) : float; + public function compute($value): float; } diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/BinaryStep.php b/src/Phpml/NeuralNetwork/ActivationFunction/BinaryStep.php index 75b2ff1c..764bc4e4 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/BinaryStep.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/BinaryStep.php @@ -11,7 +11,7 @@ class BinaryStep implements ActivationFunction /** * @param float|int $value */ - public function compute($value) : float + public function compute($value): float { return $value >= 0 ? 1.0 : 0.0; } diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php b/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php index 081b8a56..da428a4e 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php @@ -11,7 +11,7 @@ class Gaussian implements ActivationFunction /** * @param float|int $value */ - public function compute($value) : float + public function compute($value): float { return exp(-pow($value, 2)); } diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/HyperbolicTangent.php b/src/Phpml/NeuralNetwork/ActivationFunction/HyperbolicTangent.php index 5c66fd9c..63786060 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/HyperbolicTangent.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/HyperbolicTangent.php @@ -21,7 +21,7 @@ public function __construct(float $beta = 1.0) /** * @param float|int $value */ - public function compute($value) : float + public function compute($value): float { return tanh($this->beta * $value); } diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php b/src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php index 60ade03c..fc7ff628 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php @@ -21,7 +21,7 @@ public function __construct(float $beta = 0.01) /** * @param float|int $value */ - public function compute($value) : float + public function compute($value): float { return $value >= 0 ? $value : $this->beta * $value; } diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php b/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php index dec45a29..4ae96031 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php @@ -21,7 +21,7 @@ public function __construct(float $beta = 1.0) /** * @param float|int $value */ - public function compute($value) : float + public function compute($value): float { return 1 / (1 + exp(-$this->beta * $value)); } diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php b/src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php index dbe8ee63..2bb1cc7d 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php @@ -21,7 +21,7 @@ public function __construct(float $theta = 1.0) /** * @param float|int $value */ - public function compute($value) : float + public function compute($value): float { return $value > $this->theta ? $value : 0.0; } diff --git a/src/Phpml/NeuralNetwork/Layer.php b/src/Phpml/NeuralNetwork/Layer.php index c70bdb36..7424348f 100644 --- a/src/Phpml/NeuralNetwork/Layer.php +++ b/src/Phpml/NeuralNetwork/Layer.php @@ -28,20 +28,6 @@ public function __construct(int $nodesNumber = 0, string $nodeClass = Neuron::cl } } - /** - * @param ActivationFunction|null $activationFunction - * - * @return Neuron - */ - private function createNode(string $nodeClass, ?ActivationFunction $activationFunction = null) - { - if (Neuron::class == $nodeClass) { - return new Neuron($activationFunction); - } - - return new $nodeClass(); - } - public function addNode(Node $node): void { $this->nodes[] = $node; @@ -50,8 +36,20 @@ public function addNode(Node $node): void /** * @return Node[] */ - public function getNodes() : array + public function getNodes(): array { return $this->nodes; } + + /** + * @return Neuron + */ + private function createNode(string $nodeClass, ?ActivationFunction $activationFunction = null): Node + { + if ($nodeClass == Neuron::class) { + return new Neuron($activationFunction); + } + + return new $nodeClass(); + } } diff --git a/src/Phpml/NeuralNetwork/Network.php b/src/Phpml/NeuralNetwork/Network.php index af04f4a6..c2248a64 100644 --- a/src/Phpml/NeuralNetwork/Network.php +++ b/src/Phpml/NeuralNetwork/Network.php @@ -8,20 +8,15 @@ interface Network { /** * @param mixed $input - * - * @return self */ - public function setInput($input); + public function setInput($input): self; - /** - * @return array - */ - public function getOutput() : array; + public function getOutput(): array; public function addLayer(Layer $layer); /** * @return Layer[] */ - public function getLayers() : array; + public function getLayers(): array; } diff --git a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php index 3baa5ac2..4f053988 100644 --- a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php +++ b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php @@ -14,7 +14,7 @@ abstract class LayeredNetwork implements Network /** * @var Layer[] */ - protected $layers; + protected $layers = []; public function addLayer(Layer $layer): void { @@ -24,7 +24,7 @@ public function addLayer(Layer $layer): void /** * @return Layer[] */ - public function getLayers() : array + public function getLayers(): array { return $this->layers; } @@ -39,7 +39,7 @@ public function getOutputLayer(): Layer return $this->layers[count($this->layers) - 1]; } - public function getOutput() : array + public function getOutput(): array { $result = []; foreach ($this->getOutputLayer()->getNodes() as $neuron) { @@ -54,7 +54,7 @@ public function getOutput() : array * * @return $this */ - public function setInput($input) + public function setInput($input): Network { $firstLayer = $this->layers[0]; diff --git a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php index 94a84231..a38e952a 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -21,40 +21,35 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, use Predictable; /** - * @var int + * @var array */ - private $inputLayerFeatures; + protected $classes = []; /** - * @var array + * @var ActivationFunction */ - private $hiddenLayers; + protected $activationFunction; /** - * @var array + * @var Backpropagation */ - protected $classes = []; + protected $backpropagation = null; /** * @var int */ - private $iterations; + private $inputLayerFeatures; /** - * @var ActivationFunction + * @var array */ - protected $activationFunction; + private $hiddenLayers = []; /** * @var float */ private $learningRate; - /** - * @var Backpropagation - */ - protected $backpropagation = null; - /** * @throws InvalidArgumentException */ @@ -78,18 +73,6 @@ public function __construct(int $inputLayerFeatures, array $hiddenLayers, array $this->initNetwork(); } - private function initNetwork(): void - { - $this->addInputLayer($this->inputLayerFeatures); - $this->addNeuronLayers($this->hiddenLayers, $this->activationFunction); - $this->addNeuronLayers([count($this->classes)], $this->activationFunction); - - $this->addBiasNodes(); - $this->generateSynapses(); - - $this->backpropagation = new Backpropagation($this->learningRate); - } - public function train(array $samples, array $targets): void { $this->reset(); @@ -127,6 +110,18 @@ protected function reset(): void $this->removeLayers(); } + private function initNetwork(): void + { + $this->addInputLayer($this->inputLayerFeatures); + $this->addNeuronLayers($this->hiddenLayers, $this->activationFunction); + $this->addNeuronLayers([count($this->classes)], $this->activationFunction); + + $this->addBiasNodes(); + $this->generateSynapses(); + + $this->backpropagation = new Backpropagation($this->learningRate); + } + private function addInputLayer(int $nodes): void { $this->addLayer(new Layer($nodes, Input::class)); diff --git a/src/Phpml/NeuralNetwork/Node.php b/src/Phpml/NeuralNetwork/Node.php index 6627c024..0b7726ff 100644 --- a/src/Phpml/NeuralNetwork/Node.php +++ b/src/Phpml/NeuralNetwork/Node.php @@ -6,5 +6,5 @@ interface Node { - public function getOutput() : float; + public function getOutput(): float; } diff --git a/src/Phpml/NeuralNetwork/Node/Bias.php b/src/Phpml/NeuralNetwork/Node/Bias.php index 4f328844..ac3fb8bb 100644 --- a/src/Phpml/NeuralNetwork/Node/Bias.php +++ b/src/Phpml/NeuralNetwork/Node/Bias.php @@ -8,7 +8,7 @@ class Bias implements Node { - public function getOutput() : float + public function getOutput(): float { return 1.0; } diff --git a/src/Phpml/NeuralNetwork/Node/Input.php b/src/Phpml/NeuralNetwork/Node/Input.php index 8ff78ea4..ce334395 100644 --- a/src/Phpml/NeuralNetwork/Node/Input.php +++ b/src/Phpml/NeuralNetwork/Node/Input.php @@ -18,7 +18,7 @@ public function __construct(float $input = 0.0) $this->input = $input; } - public function getOutput() : float + public function getOutput(): float { return $this->input; } diff --git a/src/Phpml/NeuralNetwork/Node/Neuron.php b/src/Phpml/NeuralNetwork/Node/Neuron.php index 096d54f0..a6c10e61 100644 --- a/src/Phpml/NeuralNetwork/Node/Neuron.php +++ b/src/Phpml/NeuralNetwork/Node/Neuron.php @@ -5,6 +5,7 @@ namespace Phpml\NeuralNetwork\Node; use Phpml\NeuralNetwork\ActivationFunction; +use Phpml\NeuralNetwork\ActivationFunction\Sigmoid; use Phpml\NeuralNetwork\Node; use Phpml\NeuralNetwork\Node\Neuron\Synapse; @@ -13,7 +14,7 @@ class Neuron implements Node /** * @var Synapse[] */ - protected $synapses; + protected $synapses = []; /** * @var ActivationFunction @@ -27,7 +28,7 @@ class Neuron implements Node public function __construct(?ActivationFunction $activationFunction = null) { - $this->activationFunction = $activationFunction ?: new ActivationFunction\Sigmoid(); + $this->activationFunction = $activationFunction ?: new Sigmoid(); $this->synapses = []; $this->output = 0; } @@ -45,9 +46,9 @@ public function getSynapses() return $this->synapses; } - public function getOutput() : float + public function getOutput(): float { - if (0 === $this->output) { + if ($this->output === 0) { $sum = 0; foreach ($this->synapses as $synapse) { $sum += $synapse->getOutput(); diff --git a/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php b/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php index 0883f4e6..08899bf7 100644 --- a/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php +++ b/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php @@ -27,12 +27,7 @@ public function __construct(Node $node, ?float $weight = null) $this->weight = $weight ?: $this->generateRandomWeight(); } - protected function generateRandomWeight() : float - { - return 1 / random_int(5, 25) * (random_int(0, 1) ? -1 : 1); - } - - public function getOutput() : float + public function getOutput(): float { return $this->weight * $this->node->getOutput(); } @@ -42,7 +37,7 @@ public function changeWeight(float $delta): void $this->weight += $delta; } - public function getWeight() : float + public function getWeight(): float { return $this->weight; } @@ -51,4 +46,9 @@ public function getNode(): Node { return $this->node; } + + protected function generateRandomWeight(): float + { + return 1 / random_int(5, 25) * (random_int(0, 1) ? -1 : 1); + } } diff --git a/src/Phpml/NeuralNetwork/Training/Backpropagation.php b/src/Phpml/NeuralNetwork/Training/Backpropagation.php index 6722bd14..8382a8e4 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -47,6 +47,7 @@ public function backpropagate(array $layers, $targetClass): void } } } + $this->prevSigmas = $this->sigmas; } @@ -55,7 +56,7 @@ public function backpropagate(array $layers, $targetClass): void $this->prevSigmas = null; } - private function getSigma(Neuron $neuron, int $targetClass, int $key, bool $lastLayer) : float + private function getSigma(Neuron $neuron, int $targetClass, int $key, bool $lastLayer): float { $neuronOutput = $neuron->getOutput(); $sigma = $neuronOutput * (1 - $neuronOutput); @@ -65,6 +66,7 @@ private function getSigma(Neuron $neuron, int $targetClass, int $key, bool $last if ($targetClass === $key) { $value = 1; } + $sigma *= ($value - $neuronOutput); } else { $sigma *= $this->getPrevSigma($neuron); @@ -75,7 +77,7 @@ private function getSigma(Neuron $neuron, int $targetClass, int $key, bool $last return $sigma; } - private function getPrevSigma(Neuron $neuron) : float + private function getPrevSigma(Neuron $neuron): float { $sigma = 0.0; diff --git a/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php b/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php index 23560fe4..f21c7b12 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php @@ -29,12 +29,12 @@ public function getNeuron(): Neuron return $this->neuron; } - public function getSigma() : float + public function getSigma(): float { return $this->sigma; } - public function getSigmaForNeuron(Neuron $neuron) : float + public function getSigmaForNeuron(Neuron $neuron): float { $sigma = 0.0; diff --git a/src/Phpml/Pipeline.php b/src/Phpml/Pipeline.php index a72e634e..480a9800 100644 --- a/src/Phpml/Pipeline.php +++ b/src/Phpml/Pipeline.php @@ -9,7 +9,7 @@ class Pipeline implements Estimator /** * @var array|Transformer[] */ - private $transformers; + private $transformers = []; /** * @var Estimator @@ -41,7 +41,7 @@ public function setEstimator(Estimator $estimator): void /** * @return array|Transformer[] */ - public function getTransformers() : array + public function getTransformers(): array { return $this->transformers; } diff --git a/src/Phpml/Preprocessing/Imputer.php b/src/Phpml/Preprocessing/Imputer.php index d2dbfcd8..bd409480 100644 --- a/src/Phpml/Preprocessing/Imputer.php +++ b/src/Phpml/Preprocessing/Imputer.php @@ -9,6 +9,7 @@ class Imputer implements Preprocessor { public const AXIS_COLUMN = 0; + public const AXIS_ROW = 1; /** @@ -64,9 +65,9 @@ private function preprocessSample(array &$sample): void } } - private function getAxis(int $column, array $currentSample) : array + private function getAxis(int $column, array $currentSample): array { - if (self::AXIS_ROW === $this->axis) { + if ($this->axis === self::AXIS_ROW) { return array_diff($currentSample, [$this->missingValue]); } diff --git a/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php b/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php index 91badfbc..3ad03212 100644 --- a/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php +++ b/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php @@ -9,7 +9,7 @@ class MeanStrategy implements Strategy { - public function replaceValue(array $currentAxis) : float + public function replaceValue(array $currentAxis): float { return Mean::arithmetic($currentAxis); } diff --git a/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php b/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php index f010bea7..ffd9983b 100644 --- a/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php +++ b/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php @@ -9,7 +9,7 @@ class MedianStrategy implements Strategy { - public function replaceValue(array $currentAxis) : float + public function replaceValue(array $currentAxis): float { return Mean::median($currentAxis); } diff --git a/src/Phpml/Preprocessing/Normalizer.php b/src/Phpml/Preprocessing/Normalizer.php index f038345f..b2721de9 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -11,7 +11,9 @@ class Normalizer implements Preprocessor { public const NORM_L1 = 1; + public const NORM_L2 = 2; + public const NORM_STD = 3; /** @@ -27,12 +29,12 @@ class Normalizer implements Preprocessor /** * @var array */ - private $std; + private $std = []; /** * @var array */ - private $mean; + private $mean = []; /** * @throws NormalizerException @@ -69,7 +71,7 @@ public function transform(array &$samples): void $methods = [ self::NORM_L1 => 'normalizeL1', self::NORM_L2 => 'normalizeL2', - self::NORM_STD => 'normalizeSTD' + self::NORM_STD => 'normalizeSTD', ]; $method = $methods[$this->norm]; @@ -87,7 +89,7 @@ private function normalizeL1(array &$sample): void $norm1 += abs($feature); } - if (0 == $norm1) { + if ($norm1 == 0) { $count = count($sample); $sample = array_fill(0, $count, 1.0 / $count); } else { @@ -103,9 +105,10 @@ private function normalizeL2(array &$sample): void foreach ($sample as $feature) { $norm2 += $feature * $feature; } + $norm2 = sqrt((float) $norm2); - if (0 == $norm2) { + if ($norm2 == 0) { $sample = array_fill(0, count($sample), 1); } else { foreach ($sample as &$feature) { diff --git a/src/Phpml/Regression/LeastSquares.php b/src/Phpml/Regression/LeastSquares.php index f8adcb28..6ecfafc8 100644 --- a/src/Phpml/Regression/LeastSquares.php +++ b/src/Phpml/Regression/LeastSquares.php @@ -28,7 +28,7 @@ class LeastSquares implements Regression /** * @var array */ - private $coefficients; + private $coefficients = []; public function train(array $samples, array $targets): void { @@ -51,12 +51,12 @@ public function predictSample(array $sample) return $result; } - public function getCoefficients() : array + public function getCoefficients(): array { return $this->coefficients; } - public function getIntercept() : float + public function getIntercept(): float { return $this->intercept; } @@ -79,7 +79,7 @@ private function computeCoefficients(): void /** * Add one dimension for intercept calculation. */ - private function getSamplesMatrix() : Matrix + private function getSamplesMatrix(): Matrix { $samples = []; foreach ($this->samples as $sample) { @@ -90,7 +90,7 @@ private function getSamplesMatrix() : Matrix return new Matrix($samples); } - private function getTargetsMatrix() : Matrix + private function getTargetsMatrix(): Matrix { if (is_array($this->targets[0])) { return new Matrix($this->targets); diff --git a/src/Phpml/SupportVectorMachine/DataTransformer.php b/src/Phpml/SupportVectorMachine/DataTransformer.php index b057d01c..2ce938e6 100644 --- a/src/Phpml/SupportVectorMachine/DataTransformer.php +++ b/src/Phpml/SupportVectorMachine/DataTransformer.php @@ -34,7 +34,7 @@ public static function testSet(array $samples): string return $set; } - public static function predictions(string $rawPredictions, array $labels) : array + public static function predictions(string $rawPredictions, array $labels): array { $numericLabels = self::numericLabels($labels); $results = []; @@ -47,7 +47,7 @@ public static function predictions(string $rawPredictions, array $labels) : arra return $results; } - public static function numericLabels(array $labels) : array + public static function numericLabels(array $labels): array { $numericLabels = []; foreach ($labels as $label) { diff --git a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php index cbee23da..2415edac 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -167,7 +167,7 @@ public function getModel(): string } /** - * @return array + * @return array|string */ public function predict(array $samples) { diff --git a/src/Phpml/Tokenization/Tokenizer.php b/src/Phpml/Tokenization/Tokenizer.php index e1f0f353..f2dffd97 100644 --- a/src/Phpml/Tokenization/Tokenizer.php +++ b/src/Phpml/Tokenization/Tokenizer.php @@ -6,5 +6,5 @@ interface Tokenizer { - public function tokenize(string $text) : array; + public function tokenize(string $text): array; } diff --git a/src/Phpml/Tokenization/WhitespaceTokenizer.php b/src/Phpml/Tokenization/WhitespaceTokenizer.php index 14e7d0a2..5b071b8e 100644 --- a/src/Phpml/Tokenization/WhitespaceTokenizer.php +++ b/src/Phpml/Tokenization/WhitespaceTokenizer.php @@ -6,7 +6,7 @@ class WhitespaceTokenizer implements Tokenizer { - public function tokenize(string $text) : array + public function tokenize(string $text): array { return preg_split('/[\pZ\pC]+/u', $text, -1, PREG_SPLIT_NO_EMPTY); } diff --git a/src/Phpml/Tokenization/WordTokenizer.php b/src/Phpml/Tokenization/WordTokenizer.php index 03d134b3..68a75eaa 100644 --- a/src/Phpml/Tokenization/WordTokenizer.php +++ b/src/Phpml/Tokenization/WordTokenizer.php @@ -6,7 +6,7 @@ class WordTokenizer implements Tokenizer { - public function tokenize(string $text) : array + public function tokenize(string $text): array { $tokens = []; preg_match_all('/\w\w+/u', $text, $tokens); diff --git a/tests/Phpml/Association/AprioriTest.php b/tests/Phpml/Association/AprioriTest.php index 3b47483f..7b637c97 100644 --- a/tests/Phpml/Association/AprioriTest.php +++ b/tests/Phpml/Association/AprioriTest.php @@ -7,6 +7,7 @@ use Phpml\Association\Apriori; use Phpml\ModelManager; use PHPUnit\Framework\TestCase; +use ReflectionClass; class AprioriTest extends TestCase { @@ -172,7 +173,6 @@ public function testEquals(): void /** * Invokes objects method. Private/protected will be set accessible. * - * @param object &$object Instantiated object to be called on * @param string $method Method name to be called * @param array $params Array of params to be passed * @@ -180,7 +180,7 @@ public function testEquals(): void */ public function invoke(&$object, $method, array $params = []) { - $reflection = new \ReflectionClass(get_class($object)); + $reflection = new ReflectionClass(get_class($object)); $method = $reflection->getMethod($method); $method->setAccessible(true); @@ -195,7 +195,7 @@ public function testSaveAndRestore(): void $testSamples = [['alpha', 'epsilon'], ['beta', 'theta']]; $predicted = $classifier->predict($testSamples); - $filename = 'apriori-test-'.rand(100, 999).'-'.uniqid(); + $filename = 'apriori-test-'.random_int(100, 999).'-'.uniqid(); $filepath = tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); diff --git a/tests/Phpml/Classification/DecisionTreeTest.php b/tests/Phpml/Classification/DecisionTreeTest.php index c7d2d2af..8533500b 100644 --- a/tests/Phpml/Classification/DecisionTreeTest.php +++ b/tests/Phpml/Classification/DecisionTreeTest.php @@ -24,7 +24,7 @@ class DecisionTreeTest extends TestCase ['sunny', 75, 70, 'true', 'Play'], ['overcast', 72, 90, 'true', 'Play'], ['overcast', 81, 75, 'false', 'Play'], - ['rain', 71, 80, 'true', 'Dont_play'] + ['rain', 71, 80, 'true', 'Dont_play'], ]; private $extraData = [ @@ -32,16 +32,6 @@ class DecisionTreeTest extends TestCase ['scorching', 100, 93, 'true', 'Dont_play'], ]; - private function getData($input) - { - $targets = array_column($input, 4); - array_walk($input, function (&$v): void { - array_splice($v, 4, 1); - }); - - return [$input, $targets]; - } - public function testPredictSingleSample() { [$data, $targets] = $this->getData($this->data); @@ -68,7 +58,7 @@ public function testSaveAndRestore(): void $testSamples = [['sunny', 78, 72, 'false'], ['overcast', 60, 60, 'false']]; $predicted = $classifier->predict($testSamples); - $filename = 'decision-tree-test-'.rand(100, 999).'-'.uniqid(); + $filename = 'decision-tree-test-'.random_int(100, 999).'-'.uniqid(); $filepath = tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); @@ -83,6 +73,16 @@ public function testTreeDepth(): void [$data, $targets] = $this->getData($this->data); $classifier = new DecisionTree(5); $classifier->train($data, $targets); - $this->assertTrue(5 >= $classifier->actualDepth); + $this->assertTrue($classifier->actualDepth <= 5); + } + + private function getData($input) + { + $targets = array_column($input, 4); + array_walk($input, function (&$v): void { + array_splice($v, 4, 1); + }); + + return [$input, $targets]; } } diff --git a/tests/Phpml/Classification/Ensemble/AdaBoostTest.php b/tests/Phpml/Classification/Ensemble/AdaBoostTest.php index 2cc8090d..e01066c9 100644 --- a/tests/Phpml/Classification/Ensemble/AdaBoostTest.php +++ b/tests/Phpml/Classification/Ensemble/AdaBoostTest.php @@ -52,7 +52,7 @@ public function testSaveAndRestore(): void $testSamples = [[0, 1], [1, 1], [0.2, 0.1]]; $predicted = $classifier->predict($testSamples); - $filename = 'adaboost-test-'.rand(100, 999).'-'.uniqid(); + $filename = 'adaboost-test-'.random_int(100, 999).'-'.uniqid(); $filepath = tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); diff --git a/tests/Phpml/Classification/Ensemble/BaggingTest.php b/tests/Phpml/Classification/Ensemble/BaggingTest.php index 5bca8de8..175b79a7 100644 --- a/tests/Phpml/Classification/Ensemble/BaggingTest.php +++ b/tests/Phpml/Classification/Ensemble/BaggingTest.php @@ -26,7 +26,7 @@ class BaggingTest extends TestCase ['sunny', 75, 70, 'true', 'Play'], ['overcast', 72, 90, 'true', 'Play'], ['overcast', 81, 75, 'false', 'Play'], - ['rain', 71, 80, 'true', 'Dont_play'] + ['rain', 71, 80, 'true', 'Dont_play'], ]; private $extraData = [ @@ -61,7 +61,7 @@ public function testSaveAndRestore(): void $testSamples = [['sunny', 78, 72, 'false'], ['overcast', 60, 60, 'false']]; $predicted = $classifier->predict($testSamples); - $filename = 'bagging-test-'.rand(100, 999).'-'.uniqid(); + $filename = 'bagging-test-'.random_int(100, 999).'-'.uniqid(); $filepath = tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); @@ -105,7 +105,7 @@ protected function getAvailableBaseClassifiers() { return [ DecisionTree::class => ['depth' => 5], - NaiveBayes::class => [] + NaiveBayes::class => [], ]; } @@ -117,6 +117,7 @@ private function getData($input) for ($i = 0; $i < 20; ++$i) { $populated = array_merge($populated, $input); } + shuffle($populated); $targets = array_column($populated, 4); array_walk($populated, function (&$v): void { diff --git a/tests/Phpml/Classification/Ensemble/RandomForestTest.php b/tests/Phpml/Classification/Ensemble/RandomForestTest.php index e4ca9e50..f2871e3f 100644 --- a/tests/Phpml/Classification/Ensemble/RandomForestTest.php +++ b/tests/Phpml/Classification/Ensemble/RandomForestTest.php @@ -7,9 +7,20 @@ use Phpml\Classification\DecisionTree; use Phpml\Classification\Ensemble\RandomForest; use Phpml\Classification\NaiveBayes; +use Throwable; class RandomForestTest extends BaggingTest { + public function testOtherBaseClassifier(): void + { + try { + $classifier = new RandomForest(); + $classifier->setClassifer(NaiveBayes::class); + $this->assertEquals(0, 1); + } catch (Throwable $ex) { + $this->assertEquals(1, 1); + } + } protected function getClassifier($numBaseClassifiers = 50) { $classifier = new RandomForest($numBaseClassifiers); @@ -22,15 +33,4 @@ protected function getAvailableBaseClassifiers() { return [DecisionTree::class => ['depth' => 5]]; } - - public function testOtherBaseClassifier(): void - { - try { - $classifier = new RandomForest(); - $classifier->setClassifer(NaiveBayes::class); - $this->assertEquals(0, 1); - } catch (\Exception $ex) { - $this->assertEquals(1, 1); - } - } } diff --git a/tests/Phpml/Classification/KNearestNeighborsTest.php b/tests/Phpml/Classification/KNearestNeighborsTest.php index 7ef61827..d9114b5f 100644 --- a/tests/Phpml/Classification/KNearestNeighborsTest.php +++ b/tests/Phpml/Classification/KNearestNeighborsTest.php @@ -73,7 +73,7 @@ public function testSaveAndRestore(): void $classifier->train($trainSamples, $trainLabels); $predicted = $classifier->predict($testSamples); - $filename = 'knearest-neighbors-test-'.rand(100, 999).'-'.uniqid(); + $filename = 'knearest-neighbors-test-'.random_int(100, 999).'-'.uniqid(); $filepath = tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); diff --git a/tests/Phpml/Classification/Linear/AdalineTest.php b/tests/Phpml/Classification/Linear/AdalineTest.php index 46f1a346..0a90a189 100644 --- a/tests/Phpml/Classification/Linear/AdalineTest.php +++ b/tests/Phpml/Classification/Linear/AdalineTest.php @@ -35,7 +35,7 @@ public function testPredictSingleSample(): void $samples = [ [0, 0], [0, 1], [1, 0], [1, 1], // First group : a cluster at bottom-left corner in 2D [5, 5], [6, 5], [5, 6], [7, 5], // Second group: another cluster at the middle-right - [3, 10],[3, 10],[3, 8], [3, 9] // Third group : cluster at the top-middle + [3, 10], [3, 10], [3, 8], [3, 9], // Third group : cluster at the top-middle ]; $targets = [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2]; @@ -55,7 +55,7 @@ public function testPredictSingleSample(): void $samples = [ [0, 0], [0, 1], [1, 0], [1, 1], // First group : a cluster at bottom-left corner in 2D [5, 5], [6, 5], [5, 6], [7, 5], // Second group: another cluster at the middle-right - [3, 10],[3, 10],[3, 8], [3, 9] // Third group : cluster at the top-middle + [3, 10], [3, 10], [3, 8], [3, 9], // Third group : cluster at the top-middle ]; $targets = [2, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1]; $classifier->train($samples, $targets); @@ -74,7 +74,7 @@ public function testSaveAndRestore(): void $testSamples = [[0, 1], [1, 1], [0.2, 0.1]]; $predicted = $classifier->predict($testSamples); - $filename = 'adaline-test-'.rand(100, 999).'-'.uniqid(); + $filename = 'adaline-test-'.random_int(100, 999).'-'.uniqid(); $filepath = tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); diff --git a/tests/Phpml/Classification/Linear/DecisionStumpTest.php b/tests/Phpml/Classification/Linear/DecisionStumpTest.php index fa522ada..7fbabec9 100644 --- a/tests/Phpml/Classification/Linear/DecisionStumpTest.php +++ b/tests/Phpml/Classification/Linear/DecisionStumpTest.php @@ -40,7 +40,7 @@ public function testPredictSingleSample() $samples = [ [0, 0], [0, 1], [1, 0], [1, 1], // First group : a cluster at bottom-left corner in 2D [5, 5], [6, 5], [5, 6], [7, 5], // Second group: another cluster at the middle-right - [3, 10],[3, 10],[3, 8], [3, 9] // Third group : cluster at the top-middle + [3, 10], [3, 10], [3, 8], [3, 9], // Third group : cluster at the top-middle ]; $targets = [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2]; @@ -63,7 +63,7 @@ public function testSaveAndRestore(): void $testSamples = [[0, 1], [1, 1], [0.2, 0.1]]; $predicted = $classifier->predict($testSamples); - $filename = 'dstump-test-'.rand(100, 999).'-'.uniqid(); + $filename = 'dstump-test-'.random_int(100, 999).'-'.uniqid(); $filepath = tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); diff --git a/tests/Phpml/Classification/Linear/PerceptronTest.php b/tests/Phpml/Classification/Linear/PerceptronTest.php index 27210672..17b1db8b 100644 --- a/tests/Phpml/Classification/Linear/PerceptronTest.php +++ b/tests/Phpml/Classification/Linear/PerceptronTest.php @@ -37,7 +37,7 @@ public function testPredictSingleSample(): void $samples = [ [0, 0], [0, 1], [1, 0], [1, 1], // First group : a cluster at bottom-left corner in 2D [5, 5], [6, 5], [5, 6], [7, 5], // Second group: another cluster at the middle-right - [3, 10],[3, 10],[3, 8], [3, 9] // Third group : cluster at the top-middle + [3, 10], [3, 10], [3, 8], [3, 9], // Third group : cluster at the top-middle ]; $targets = [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2]; @@ -58,7 +58,7 @@ public function testPredictSingleSample(): void $samples = [ [0, 0], [0, 1], [1, 0], [1, 1], // First group : a cluster at bottom-left corner in 2D [5, 5], [6, 5], [5, 6], [7, 5], // Second group: another cluster at the middle-right - [3, 10],[3, 10],[3, 8], [3, 9] // Third group : cluster at the top-middle + [3, 10], [3, 10], [3, 8], [3, 9], // Third group : cluster at the top-middle ]; $targets = [2, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1]; $classifier->train($samples, $targets); @@ -77,7 +77,7 @@ public function testSaveAndRestore(): void $testSamples = [[0, 1], [1, 1], [0.2, 0.1]]; $predicted = $classifier->predict($testSamples); - $filename = 'perceptron-test-'.rand(100, 999).'-'.uniqid(); + $filename = 'perceptron-test-'.random_int(100, 999).'-'.uniqid(); $filepath = tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); diff --git a/tests/Phpml/Classification/MLPClassifierTest.php b/tests/Phpml/Classification/MLPClassifierTest.php index 519dc905..20bc5e17 100644 --- a/tests/Phpml/Classification/MLPClassifierTest.php +++ b/tests/Phpml/Classification/MLPClassifierTest.php @@ -150,7 +150,7 @@ public function testSaveAndRestore(): void $testSamples = [[0, 0], [1, 0], [0, 1], [1, 1]]; $predicted = $classifier->predict($testSamples); - $filename = 'perceptron-test-'.rand(100, 999).'-'.uniqid(); + $filename = 'perceptron-test-'.random_int(100, 999).'-'.uniqid(); $filepath = tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); @@ -189,7 +189,7 @@ public function testThrowExceptionOnInvalidClassesNumber(): void new MLPClassifier(2, [2], [0]); } - private function getSynapsesNodes(array $synapses) : array + private function getSynapsesNodes(array $synapses): array { $nodes = []; foreach ($synapses as $synapse) { diff --git a/tests/Phpml/Classification/NaiveBayesTest.php b/tests/Phpml/Classification/NaiveBayesTest.php index 5b14f7e1..c423c6db 100644 --- a/tests/Phpml/Classification/NaiveBayesTest.php +++ b/tests/Phpml/Classification/NaiveBayesTest.php @@ -59,7 +59,7 @@ public function testSaveAndRestore(): void $classifier->train($trainSamples, $trainLabels); $predicted = $classifier->predict($testSamples); - $filename = 'naive-bayes-test-'.rand(100, 999).'-'.uniqid(); + $filename = 'naive-bayes-test-'.random_int(100, 999).'-'.uniqid(); $filepath = tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); diff --git a/tests/Phpml/Classification/SVCTest.php b/tests/Phpml/Classification/SVCTest.php index f7342970..0941b346 100644 --- a/tests/Phpml/Classification/SVCTest.php +++ b/tests/Phpml/Classification/SVCTest.php @@ -57,7 +57,7 @@ public function testSaveAndRestore(): void $classifier->train($trainSamples, $trainLabels); $predicted = $classifier->predict($testSamples); - $filename = 'svc-test-'.rand(100, 999).'-'.uniqid(); + $filename = 'svc-test-'.random_int(100, 999).'-'.uniqid(); $filepath = tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); diff --git a/tests/Phpml/Clustering/DBSCANTest.php b/tests/Phpml/Clustering/DBSCANTest.php index d8fb0fea..f12fb036 100644 --- a/tests/Phpml/Clustering/DBSCANTest.php +++ b/tests/Phpml/Clustering/DBSCANTest.php @@ -34,10 +34,25 @@ public function testDBSCANSamplesClustering(): void public function testDBSCANSamplesClusteringAssociative(): void { - $samples = ['a' => [1, 1], 'b' => [9, 9], 'c' => [1, 2], 'd' => [9, 8], 'e' => [7, 7], 'f' => [8, 7]]; + $samples = [ + 'a' => [1, 1], + 'b' => [9, 9], + 'c' => [1, 2], + 'd' => [9, 8], + 'e' => [7, 7], + 'f' => [8, 7], + ]; $clustered = [ - ['a' => [1, 1], 'c' => [1, 2]], - ['b' => [9, 9], 'd' => [9, 8], 'e' => [7, 7], 'f' => [8, 7]], + [ + 'a' => [1, 1], + 'c' => [1, 2], + ], + [ + 'b' => [9, 9], + 'd' => [9, 8], + 'e' => [7, 7], + 'f' => [8, 7], + ], ]; $dbscan = new DBSCAN($epsilon = 3, $minSamples = 2); diff --git a/tests/Phpml/Clustering/FuzzyCMeansTest.php b/tests/Phpml/Clustering/FuzzyCMeansTest.php index 5aed678c..7b194220 100644 --- a/tests/Phpml/Clustering/FuzzyCMeansTest.php +++ b/tests/Phpml/Clustering/FuzzyCMeansTest.php @@ -20,6 +20,7 @@ public function testFCMSamplesClustering() unset($samples[$index]); } } + $this->assertCount(0, $samples); return $fcm; @@ -35,6 +36,7 @@ public function testMembershipMatrix(): void foreach ($matrix as $row) { $this->assertCount($sampleCount, $row); } + // Transpose of the matrix array_unshift($matrix, null); $matrix = call_user_func_array('array_map', $matrix); diff --git a/tests/Phpml/Clustering/KMeansTest.php b/tests/Phpml/Clustering/KMeansTest.php index e665c9fc..f4533408 100644 --- a/tests/Phpml/Clustering/KMeansTest.php +++ b/tests/Phpml/Clustering/KMeansTest.php @@ -23,6 +23,7 @@ public function testKMeansSamplesClustering(): void unset($samples[$index]); } } + $this->assertCount(0, $samples); } diff --git a/tests/Phpml/DimensionReduction/KernelPCATest.php b/tests/Phpml/DimensionReduction/KernelPCATest.php index 66b4622f..3f37e3c0 100644 --- a/tests/Phpml/DimensionReduction/KernelPCATest.php +++ b/tests/Phpml/DimensionReduction/KernelPCATest.php @@ -16,12 +16,12 @@ public function testKernelPCA(): void // A simple example whose result is known beforehand $data = [ - [2,2], [1.5,1], [1.,1.5], [1.,1.], - [2.,1.],[2,2.5], [2.,3.], [1.5,3], - [1.,2.5], [1.,2.7], [1.,3.], [1,3], - [1,2], [1.5,2], [1.5,2.2], [1.3,1.7], - [1.7,1.3], [1.5,1.5], [1.5,1.6], [1.6,2], - [1.7,2.1], [1.3,1.3], [1.3,2.2], [1.4,2.4] + [2, 2], [1.5, 1], [1., 1.5], [1., 1.], + [2., 1.], [2, 2.5], [2., 3.], [1.5, 3], + [1., 2.5], [1., 2.7], [1., 3.], [1, 3], + [1, 2], [1.5, 2], [1.5, 2.2], [1.3, 1.7], + [1.7, 1.3], [1.5, 1.5], [1.5, 1.6], [1.6, 2], + [1.7, 2.1], [1.3, 1.3], [1.3, 2.2], [1.4, 2.4], ]; $transformed = [ [0.016485613899708], [-0.089805657741674], [-0.088695974245924], [-0.069761503810802], @@ -29,7 +29,7 @@ public function testKernelPCA(): void [-0.10098315410297], [-0.15617881000654], [-0.21266832077299], [-0.21266832077299], [-0.039234518840831], [0.40858295942991], [0.40110375047242], [-0.10555116296691], [-0.13128352866095], [-0.20865959471756], [-0.17531601535848], [0.4240660966961], - [0.36351946685163], [-0.14334173054136], [0.22454914091011], [0.15035027480881]]; + [0.36351946685163], [-0.14334173054136], [0.22454914091011], [0.15035027480881], ]; $kpca = new KernelPCA(KernelPCA::KERNEL_RBF, null, 1, 15); $reducedData = $kpca->fit($data); diff --git a/tests/Phpml/DimensionReduction/LDATest.php b/tests/Phpml/DimensionReduction/LDATest.php index 19124a00..42a02836 100644 --- a/tests/Phpml/DimensionReduction/LDATest.php +++ b/tests/Phpml/DimensionReduction/LDATest.php @@ -28,7 +28,7 @@ public function testLDA(): void [4.7, 3.2, 1.3, 0.2], [6.5, 3.0, 5.2, 2.0], [6.2, 3.4, 5.4, 2.3], - [5.9, 3.0, 5.1, 1.8] + [5.9, 3.0, 5.1, 1.8], ]; $transformed2 = [ [-1.4922092756753, 1.9047102045574], @@ -36,7 +36,7 @@ public function testLDA(): void [-1.3487505965419, 1.749846351699], [1.7759343101456, 2.0371552314006], [2.0059819019159, 2.4493123003226], - [1.701474913008, 1.9037880473772] + [1.701474913008, 1.9037880473772], ]; $control = []; diff --git a/tests/Phpml/DimensionReduction/PCATest.php b/tests/Phpml/DimensionReduction/PCATest.php index 0a60004e..38b47444 100644 --- a/tests/Phpml/DimensionReduction/PCATest.php +++ b/tests/Phpml/DimensionReduction/PCATest.php @@ -26,12 +26,12 @@ public function testPCA(): void [2.0, 1.6], [1.0, 1.1], [1.5, 1.6], - [1.1, 0.9] + [1.1, 0.9], ]; $transformed = [ [-0.827970186], [1.77758033], [-0.992197494], [-0.274210416], [-1.67580142], [-0.912949103], [0.0991094375], - [1.14457216], [0.438046137], [1.22382056]]; + [1.14457216], [0.438046137], [1.22382056], ]; $pca = new PCA(0.90); $reducedData = $pca->fit($data); diff --git a/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php b/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php index 0e1adbc2..a8faacb5 100644 --- a/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php +++ b/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php @@ -14,13 +14,41 @@ public function testTfIdfTransformation(): void // https://en.wikipedia.org/wiki/Tf-idf $samples = [ - [0 => 1, 1 => 1, 2 => 2, 3 => 1, 4 => 0, 5 => 0], - [0 => 1, 1 => 1, 2 => 0, 3 => 0, 4 => 2, 5 => 3], + [ + 0 => 1, + 1 => 1, + 2 => 2, + 3 => 1, + 4 => 0, + 5 => 0, + ], + [ + 0 => 1, + 1 => 1, + 2 => 0, + 3 => 0, + 4 => 2, + 5 => 3, + ], ]; $tfIdfSamples = [ - [0 => 0, 1 => 0, 2 => 0.602, 3 => 0.301, 4 => 0, 5 => 0], - [0 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 0.602, 5 => 0.903], + [ + 0 => 0, + 1 => 0, + 2 => 0.602, + 3 => 0.301, + 4 => 0, + 5 => 0, + ], + [ + 0 => 0, + 1 => 0, + 2 => 0, + 3 => 0, + 4 => 0.602, + 5 => 0.903, + ], ]; $transformer = new TfIdfTransformer($samples); diff --git a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php index 3419a29f..463570cf 100644 --- a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php +++ b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php @@ -33,9 +33,42 @@ public function testTransformationWithWhitespaceTokenizer(): void ]; $tokensCounts = [ - [0 => 1, 1 => 1, 2 => 2, 3 => 1, 4 => 1, 5 => 0, 6 => 0, 7 => 0, 8 => 0, 9 => 0], - [0 => 0, 1 => 1, 2 => 1, 3 => 0, 4 => 0, 5 => 1, 6 => 1, 7 => 0, 8 => 0, 9 => 0], - [0 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 1, 6 => 0, 7 => 2, 8 => 1, 9 => 1], + [ + 0 => 1, + 1 => 1, + 2 => 2, + 3 => 1, + 4 => 1, + 5 => 0, + 6 => 0, + 7 => 0, + 8 => 0, + 9 => 0, + ], + [ + 0 => 0, + 1 => 1, + 2 => 1, + 3 => 0, + 4 => 0, + 5 => 1, + 6 => 1, + 7 => 0, + 8 => 0, + 9 => 0, + ], + [ + 0 => 0, + 1 => 0, + 2 => 0, + 3 => 0, + 4 => 0, + 5 => 1, + 6 => 0, + 7 => 2, + 8 => 1, + 9 => 1, + ], ]; $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer()); @@ -66,10 +99,34 @@ public function testTransformationWithMinimumDocumentTokenCountFrequency(): void ]; $tokensCounts = [ - [0 => 1, 1 => 1, 2 => 0, 3 => 1, 4 => 1], - [0 => 1, 1 => 1, 2 => 0, 3 => 1, 4 => 1], - [0 => 0, 1 => 1, 2 => 0, 3 => 1, 4 => 1], - [0 => 0, 1 => 1, 2 => 0, 3 => 1, 4 => 1], + [ + 0 => 1, + 1 => 1, + 2 => 0, + 3 => 1, + 4 => 1, + ], + [ + 0 => 1, + 1 => 1, + 2 => 0, + 3 => 1, + 4 => 1, + ], + [ + 0 => 0, + 1 => 1, + 2 => 0, + 3 => 1, + 4 => 1, + ], + [ + 0 => 0, + 1 => 1, + 2 => 0, + 3 => 1, + 4 => 1, + ], ]; $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), null, 0.5); @@ -88,9 +145,39 @@ public function testTransformationWithMinimumDocumentTokenCountFrequency(): void ]; $tokensCounts = [ - [0 => 1, 1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0, 7 => 0, 8 => 0], - [0 => 1, 1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0, 7 => 0, 8 => 0], - [0 => 1, 1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0, 7 => 0, 8 => 0], + [ + 0 => 1, + 1 => 0, + 2 => 0, + 3 => 0, + 4 => 0, + 5 => 0, + 6 => 0, + 7 => 0, + 8 => 0, + ], + [ + 0 => 1, + 1 => 0, + 2 => 0, + 3 => 0, + 4 => 0, + 5 => 0, + 6 => 0, + 7 => 0, + 8 => 0, + ], + [ + 0 => 1, + 1 => 0, + 2 => 0, + 3 => 0, + 4 => 0, + 5 => 0, + 6 => 0, + 7 => 0, + 8 => 0, + ], ]; $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), null, 1); @@ -124,9 +211,36 @@ public function testTransformationWithStopWords(): void ]; $tokensCounts = [ - [0 => 1, 1 => 1, 2 => 1, 3 => 1, 4 => 0, 5 => 0, 6 => 0, 7 => 0], - [0 => 0, 1 => 1, 2 => 0, 3 => 0, 4 => 1, 5 => 1, 6 => 0, 7 => 0], - [0 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 1, 5 => 0, 6 => 1, 7 => 1], + [ + 0 => 1, + 1 => 1, + 2 => 1, + 3 => 1, + 4 => 0, + 5 => 0, + 6 => 0, + 7 => 0, + ], + [ + 0 => 0, + 1 => 1, + 2 => 0, + 3 => 0, + 4 => 1, + 5 => 1, + 6 => 0, + 7 => 0, + ], + [ + 0 => 0, + 1 => 0, + 2 => 0, + 3 => 0, + 4 => 1, + 5 => 0, + 6 => 1, + 7 => 1, + ], ]; $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), $stopWords); diff --git a/tests/Phpml/Math/ComparisonTest.php b/tests/Phpml/Math/ComparisonTest.php index d31b1ae0..2c72f5fb 100644 --- a/tests/Phpml/Math/ComparisonTest.php +++ b/tests/Phpml/Math/ComparisonTest.php @@ -23,18 +23,15 @@ public function testResult($a, $b, string $operator, bool $expected): void } /** - * @expectedException \Phpml\Exception\InvalidArgumentException - * @expectedExceptionMessage Invalid operator "~=" provided - */ + * @expectedException \Phpml\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid operator "~=" provided + */ public function testThrowExceptionWhenOperatorIsInvalid(): void { Comparison::compare(1, 1, '~='); } - /** - * @return array - */ - public function provideData() + public function provideData(): array { return [ // Greater diff --git a/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php b/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php index 688874cd..39568301 100644 --- a/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php +++ b/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php @@ -19,7 +19,7 @@ public function testSymmetricMatrixEigenPairs(): void // http://www.cs.otago.ac.nz/cosc453/student_tutorials/principal_components.pdf $matrix = [ [0.616555556, 0.615444444], - [0.614444444, 0.716555556] + [0.614444444, 0.716555556], ]; $knownEigvalues = [0.0490833989, 1.28402771]; $knownEigvectors = [[-0.735178656, 0.677873399], [-0.677873399, -0.735178656]]; @@ -43,7 +43,7 @@ public function testSymmetricMatrixEigenPairs(): void if ($i > $k) { $A[$i][$k] = $A[$k][$i]; } else { - $A[$i][$k] = rand(0, 10); + $A[$i][$k] = random_int(0, 10); } } } diff --git a/tests/Phpml/Math/MatrixTest.php b/tests/Phpml/Math/MatrixTest.php index cd9fff28..8d0e1be1 100644 --- a/tests/Phpml/Math/MatrixTest.php +++ b/tests/Phpml/Math/MatrixTest.php @@ -239,12 +239,12 @@ public function testTransposeArray(): void { $array = [ [1, 1, 1], - [2, 2, 2] + [2, 2, 2], ]; $transposed = [ [1, 2], [1, 2], - [1, 2] + [1, 2], ]; $this->assertEquals($transposed, Matrix::transposeArray($array)); diff --git a/tests/Phpml/Math/ProductTest.php b/tests/Phpml/Math/ProductTest.php index 9c3a4b4c..bd55d112 100644 --- a/tests/Phpml/Math/ProductTest.php +++ b/tests/Phpml/Math/ProductTest.php @@ -6,6 +6,7 @@ use Phpml\Math\Product; use PHPUnit\Framework\TestCase; +use stdClass; class ProductTest extends TestCase { @@ -16,6 +17,6 @@ public function testScalarProduct(): void $this->assertEquals(8, Product::scalar([2], [4])); //test for non numeric values - $this->assertEquals(0, Product::scalar(['', null, [], new \stdClass()], [null])); + $this->assertEquals(0, Product::scalar(['', null, [], new stdClass()], [null])); } } diff --git a/tests/Phpml/Math/SetTest.php b/tests/Phpml/Math/SetTest.php index a572a42a..645e6076 100644 --- a/tests/Phpml/Math/SetTest.php +++ b/tests/Phpml/Math/SetTest.php @@ -13,7 +13,7 @@ public function testUnion(): void { $union = Set::union(new Set([3, 1]), new Set([3, 2, 2])); - $this->assertInstanceOf('\Phpml\Math\Set', $union); + $this->assertInstanceOf(Set::class, $union); $this->assertEquals(new Set([1, 2, 3]), $union); $this->assertEquals(3, $union->cardinality()); } @@ -22,7 +22,7 @@ public function testIntersection(): void { $intersection = Set::intersection(new Set(['C', 'A']), new Set(['B', 'C'])); - $this->assertInstanceOf('\Phpml\Math\Set', $intersection); + $this->assertInstanceOf(Set::class, $intersection); $this->assertEquals(new Set(['C']), $intersection); $this->assertEquals(1, $intersection->cardinality()); } @@ -31,7 +31,7 @@ public function testDifference(): void { $difference = Set::difference(new Set(['C', 'A', 'B']), new Set(['A'])); - $this->assertInstanceOf('\Phpml\Math\Set', $difference); + $this->assertInstanceOf(Set::class, $difference); $this->assertEquals(new Set(['B', 'C']), $difference); $this->assertEquals(2, $difference->cardinality()); } diff --git a/tests/Phpml/Math/Statistic/CovarianceTest.php b/tests/Phpml/Math/Statistic/CovarianceTest.php index 3a8c9d38..97ec1941 100644 --- a/tests/Phpml/Math/Statistic/CovarianceTest.php +++ b/tests/Phpml/Math/Statistic/CovarianceTest.php @@ -31,7 +31,7 @@ public function testSimpleCovariance(): void ]; $knownCovariance = [ [0.616555556, 0.615444444], - [0.615444444, 0.716555556]]; + [0.615444444, 0.716555556], ]; $x = array_column($matrix, 0); $y = array_column($matrix, 1); diff --git a/tests/Phpml/Metric/ClassificationReportTest.php b/tests/Phpml/Metric/ClassificationReportTest.php index b7ff02d3..fb3471ae 100644 --- a/tests/Phpml/Metric/ClassificationReportTest.php +++ b/tests/Phpml/Metric/ClassificationReportTest.php @@ -16,11 +16,31 @@ public function testClassificationReportGenerateWithStringLabels(): void $report = new ClassificationReport($labels, $predicted); - $precision = ['cat' => 0.5, 'ant' => 0.0, 'bird' => 1.0]; - $recall = ['cat' => 1.0, 'ant' => 0.0, 'bird' => 0.67]; - $f1score = ['cat' => 0.67, 'ant' => 0.0, 'bird' => 0.80]; - $support = ['cat' => 1, 'ant' => 1, 'bird' => 3]; - $average = ['precision' => 0.75, 'recall' => 0.83, 'f1score' => 0.73]; + $precision = [ + 'cat' => 0.5, + 'ant' => 0.0, + 'bird' => 1.0, + ]; + $recall = [ + 'cat' => 1.0, + 'ant' => 0.0, + 'bird' => 0.67, + ]; + $f1score = [ + 'cat' => 0.67, + 'ant' => 0.0, + 'bird' => 0.80, + ]; + $support = [ + 'cat' => 1, + 'ant' => 1, + 'bird' => 3, + ]; + $average = [ + 'precision' => 0.75, + 'recall' => 0.83, + 'f1score' => 0.73, + ]; $this->assertEquals($precision, $report->getPrecision(), '', 0.01); $this->assertEquals($recall, $report->getRecall(), '', 0.01); @@ -36,11 +56,31 @@ public function testClassificationReportGenerateWithNumericLabels(): void $report = new ClassificationReport($labels, $predicted); - $precision = [0 => 0.5, 1 => 0.0, 2 => 1.0]; - $recall = [0 => 1.0, 1 => 0.0, 2 => 0.67]; - $f1score = [0 => 0.67, 1 => 0.0, 2 => 0.80]; - $support = [0 => 1, 1 => 1, 2 => 3]; - $average = ['precision' => 0.75, 'recall' => 0.83, 'f1score' => 0.73]; + $precision = [ + 0 => 0.5, + 1 => 0.0, + 2 => 1.0, + ]; + $recall = [ + 0 => 1.0, + 1 => 0.0, + 2 => 0.67, + ]; + $f1score = [ + 0 => 0.67, + 1 => 0.0, + 2 => 0.80, + ]; + $support = [ + 0 => 1, + 1 => 1, + 2 => 3, + ]; + $average = [ + 'precision' => 0.75, + 'recall' => 0.83, + 'f1score' => 0.73, + ]; $this->assertEquals($precision, $report->getPrecision(), '', 0.01); $this->assertEquals($recall, $report->getRecall(), '', 0.01); @@ -56,7 +96,10 @@ public function testPreventDivideByZeroWhenTruePositiveAndFalsePositiveSumEquals $report = new ClassificationReport($labels, $predicted); - $this->assertEquals([1 => 0.0, 2 => 0.5], $report->getPrecision(), '', 0.01); + $this->assertEquals([ + 1 => 0.0, + 2 => 0.5, + ], $report->getPrecision(), '', 0.01); } public function testPreventDivideByZeroWhenTruePositiveAndFalseNegativeSumEqualsZero(): void @@ -66,7 +109,11 @@ public function testPreventDivideByZeroWhenTruePositiveAndFalseNegativeSumEquals $report = new ClassificationReport($labels, $predicted); - $this->assertEquals([1 => 0.0, 2 => 1, 3 => 0], $report->getPrecision(), '', 0.01); + $this->assertEquals([ + 1 => 0.0, + 2 => 1, + 3 => 0, + ], $report->getPrecision(), '', 0.01); } public function testPreventDividedByZeroWhenPredictedLabelsAllNotMatch(): void diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php index 95e95cbb..91fc1c7f 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php @@ -19,10 +19,7 @@ public function testBinaryStepActivationFunction($expected, $value): void $this->assertEquals($expected, $binaryStep->compute($value)); } - /** - * @return array - */ - public function binaryStepProvider() + public function binaryStepProvider(): array { return [ [1, 1], diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php index f7af7c0d..58b4b87f 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php @@ -19,10 +19,7 @@ public function testGaussianActivationFunction($expected, $value): void $this->assertEquals($expected, $gaussian->compute($value), '', 0.001); } - /** - * @return array - */ - public function gaussianProvider() + public function gaussianProvider(): array { return [ [0.367, 1], diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php index 95f437f5..00348d90 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php @@ -19,10 +19,7 @@ public function testHyperbolicTangentActivationFunction($beta, $expected, $value $this->assertEquals($expected, $tanh->compute($value), '', 0.001); } - /** - * @return array - */ - public function tanhProvider() + public function tanhProvider(): array { return [ [1.0, 0.761, 1], diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php index 873520ef..7e8e7189 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php @@ -19,10 +19,7 @@ public function testPReLUActivationFunction($beta, $expected, $value): void $this->assertEquals($expected, $prelu->compute($value), '', 0.001); } - /** - * @return array - */ - public function preluProvider() + public function preluProvider(): array { return [ [0.01, 0.367, 0.367], diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php index 096a3769..d5a0ea33 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php @@ -19,10 +19,7 @@ public function testSigmoidActivationFunction($beta, $expected, $value): void $this->assertEquals($expected, $sigmoid->compute($value), '', 0.001); } - /** - * @return array - */ - public function sigmoidProvider() + public function sigmoidProvider(): array { return [ [1.0, 1, 7.25], diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php index 1800d7b0..19a0312f 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php @@ -19,16 +19,13 @@ public function testThresholdedReLUActivationFunction($theta, $expected, $value) $this->assertEquals($expected, $thresholdedReLU->compute($value)); } - /** - * @return array - */ - public function thresholdProvider() + public function thresholdProvider(): array { return [ [1.0, 0, 1.0], [0.5, 3.75, 3.75], [0.0, 0.5, 0.5], - [0.9, 0, 0.1] + [0.9, 0, 0.1], ]; } } diff --git a/tests/Phpml/NeuralNetwork/LayerTest.php b/tests/Phpml/NeuralNetwork/LayerTest.php index 284b1ebf..72d8758e 100644 --- a/tests/Phpml/NeuralNetwork/LayerTest.php +++ b/tests/Phpml/NeuralNetwork/LayerTest.php @@ -8,6 +8,7 @@ use Phpml\NeuralNetwork\Node\Bias; use Phpml\NeuralNetwork\Node\Neuron; use PHPUnit\Framework\TestCase; +use stdClass; class LayerTest extends TestCase { @@ -43,7 +44,7 @@ public function testLayerInitializationWithExplicitNodesType(): void */ public function testThrowExceptionOnInvalidNodeClass(): void { - new Layer(1, \stdClass::class); + new Layer(1, stdClass::class); } public function testAddNodesToLayer(): void diff --git a/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php b/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php index 48bee66e..c1779b80 100644 --- a/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php +++ b/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php @@ -44,10 +44,7 @@ public function testSetInputAndGetOutput(): void $this->assertEquals([0.5], $network->getOutput()); } - /** - * @return LayeredNetwork - */ - private function getLayeredNetworkMock() + private function getLayeredNetworkMock(): LayeredNetwork { return $this->getMockForAbstractClass(LayeredNetwork::class); } diff --git a/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php b/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php index 02d6dfa2..1c09eae3 100644 --- a/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php +++ b/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php @@ -7,6 +7,7 @@ use Phpml\NeuralNetwork\Node\Neuron; use Phpml\NeuralNetwork\Node\Neuron\Synapse; use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject; class SynapseTest extends TestCase { @@ -39,11 +40,9 @@ public function testSynapseWeightChange(): void } /** - * @param int $output - * - * @return \PHPUnit_Framework_MockObject_MockObject + * @param int|float $output */ - private function getNodeMock($output = 1) + private function getNodeMock($output = 1): PHPUnit_Framework_MockObject_MockObject { $node = $this->getMockBuilder(Neuron::class)->getMock(); $node->method('getOutput')->willReturn($output); diff --git a/tests/Phpml/NeuralNetwork/Node/NeuronTest.php b/tests/Phpml/NeuralNetwork/Node/NeuronTest.php index 89f1ca1b..a58f2ec9 100644 --- a/tests/Phpml/NeuralNetwork/Node/NeuronTest.php +++ b/tests/Phpml/NeuralNetwork/Node/NeuronTest.php @@ -8,6 +8,7 @@ use Phpml\NeuralNetwork\Node\Neuron; use Phpml\NeuralNetwork\Node\Neuron\Synapse; use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject; class NeuronTest extends TestCase { @@ -52,11 +53,9 @@ public function testNeuronRefresh(): void } /** - * @param int $output - * - * @return Synapse|\PHPUnit_Framework_MockObject_MockObject + * @return Synapse|PHPUnit_Framework_MockObject_MockObject */ - private function getSynapseMock($output = 2) + private function getSynapseMock(int $output = 2) { $synapse = $this->getMockBuilder(Synapse::class)->disableOriginalConstructor()->getMock(); $synapse->method('getOutput')->willReturn($output); diff --git a/tests/Phpml/PipelineTest.php b/tests/Phpml/PipelineTest.php index fc06e56f..caf1961b 100644 --- a/tests/Phpml/PipelineTest.php +++ b/tests/Phpml/PipelineTest.php @@ -72,7 +72,7 @@ public function testPipelineTransformers(): void { $transformers = [ new TokenCountVectorizer(new WordTokenizer()), - new TfIdfTransformer() + new TfIdfTransformer(), ]; $estimator = new SVC(); diff --git a/tests/Phpml/Preprocessing/NormalizerTest.php b/tests/Phpml/Preprocessing/NormalizerTest.php index 22ed1bdb..3d5940ad 100644 --- a/tests/Phpml/Preprocessing/NormalizerTest.php +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -109,8 +109,9 @@ public function testStandardNorm(): void for ($i = 0; $i < 10; ++$i) { $sample = array_fill(0, 3, 0); for ($k = 0; $k < 3; ++$k) { - $sample[$k] = rand(1, 100); + $sample[$k] = random_int(1, 100); } + // Last feature's value shared across samples. $sample[] = 1; diff --git a/tests/Phpml/Regression/LeastSquaresTest.php b/tests/Phpml/Regression/LeastSquaresTest.php index 7d835a2d..7517a9b9 100644 --- a/tests/Phpml/Regression/LeastSquaresTest.php +++ b/tests/Phpml/Regression/LeastSquaresTest.php @@ -81,7 +81,7 @@ public function testSaveAndRestore(): void $testSamples = [[9300], [10565], [15000]]; $predicted = $regression->predict($testSamples); - $filename = 'least-squares-test-'.rand(100, 999).'-'.uniqid(); + $filename = 'least-squares-test-'.random_int(100, 999).'-'.uniqid(); $filepath = tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($regression, $filepath); diff --git a/tests/Phpml/Regression/SVRTest.php b/tests/Phpml/Regression/SVRTest.php index 3cd0ee55..a220d211 100644 --- a/tests/Phpml/Regression/SVRTest.php +++ b/tests/Phpml/Regression/SVRTest.php @@ -48,7 +48,7 @@ public function testSaveAndRestore(): void $testSamples = [64]; $predicted = $regression->predict($testSamples); - $filename = 'svr-test'.rand(100, 999).'-'.uniqid(); + $filename = 'svr-test'.random_int(100, 999).'-'.uniqid(); $filepath = tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($regression, $filepath); From 946fbbc5213e43f331113c05d92c74b40aa2d80d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Votruba?= Date: Tue, 28 Nov 2017 08:00:13 +0100 Subject: [PATCH 212/328] Tests: use PHPUnit (6.4) exception methods (#165) * tests: update to PHPUnit 6.0 with rector * [cs] clean empty docs * composer: bump to PHPUnit 6.4 * tests: use class references over strings * cleanup --- composer.json | 2 +- src/Phpml/Preprocessing/Imputer.php | 4 +-- tests/Phpml/Association/AprioriTest.php | 2 +- .../Classification/MLPClassifierTest.php | 13 +++------ tests/Phpml/Clustering/KMeansTest.php | 5 ++-- .../Phpml/CrossValidation/RandomSplitTest.php | 9 ++---- tests/Phpml/Dataset/ArrayDatasetTest.php | 5 ++-- tests/Phpml/Dataset/CsvDatasetTest.php | 5 ++-- tests/Phpml/Dataset/FilesDatasetTest.php | 5 ++-- .../Phpml/FeatureExtraction/StopWordsTest.php | 5 ++-- tests/Phpml/Math/ComparisonTest.php | 7 ++--- tests/Phpml/Math/Distance/ChebyshevTest.php | 6 ++-- tests/Phpml/Math/Distance/EuclideanTest.php | 6 ++-- tests/Phpml/Math/Distance/ManhattanTest.php | 6 ++-- tests/Phpml/Math/Distance/MinkowskiTest.php | 6 ++-- tests/Phpml/Math/MatrixTest.php | 28 ++++++------------- .../Phpml/Math/Statistic/CorrelationTest.php | 5 ++-- tests/Phpml/Math/Statistic/MeanTest.php | 13 +++------ .../Math/Statistic/StandardDeviationTest.php | 9 ++---- tests/Phpml/Metric/AccuracyTest.php | 6 ++-- tests/Phpml/ModelManagerTest.php | 5 ++-- tests/Phpml/NeuralNetwork/LayerTest.php | 5 ++-- tests/Phpml/Preprocessing/NormalizerTest.php | 5 ++-- .../SupportVectorMachineTest.php | 19 +++++-------- 24 files changed, 64 insertions(+), 117 deletions(-) diff --git a/composer.json b/composer.json index 0563f3fe..7b774f29 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^6.0", + "phpunit/phpunit": "^6.4", "friendsofphp/php-cs-fixer": "^2.4", "symplify/easy-coding-standard": "dev-master as 2.5", "symplify/coding-standard": "dev-master as 2.5", diff --git a/src/Phpml/Preprocessing/Imputer.php b/src/Phpml/Preprocessing/Imputer.php index bd409480..fdf8796f 100644 --- a/src/Phpml/Preprocessing/Imputer.php +++ b/src/Phpml/Preprocessing/Imputer.php @@ -28,9 +28,9 @@ class Imputer implements Preprocessor private $axis; /** - * @var + * @var mixed[] */ - private $samples; + private $samples = []; /** * @param mixed $missingValue diff --git a/tests/Phpml/Association/AprioriTest.php b/tests/Phpml/Association/AprioriTest.php index 7b637c97..68456ffa 100644 --- a/tests/Phpml/Association/AprioriTest.php +++ b/tests/Phpml/Association/AprioriTest.php @@ -178,7 +178,7 @@ public function testEquals(): void * * @return mixed */ - public function invoke(&$object, $method, array $params = []) + public function invoke(&$object, string $method, array $params = []) { $reflection = new ReflectionClass(get_class($object)); $method = $reflection->getMethod($method); diff --git a/tests/Phpml/Classification/MLPClassifierTest.php b/tests/Phpml/Classification/MLPClassifierTest.php index 20bc5e17..ef62d063 100644 --- a/tests/Phpml/Classification/MLPClassifierTest.php +++ b/tests/Phpml/Classification/MLPClassifierTest.php @@ -5,6 +5,7 @@ namespace tests\Phpml\Classification; use Phpml\Classification\MLPClassifier; +use Phpml\Exception\InvalidArgumentException; use Phpml\ModelManager; use Phpml\NeuralNetwork\Node\Neuron; use PHPUnit\Framework\TestCase; @@ -160,19 +161,15 @@ public function testSaveAndRestore(): void $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); } - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - */ public function testThrowExceptionOnInvalidLayersNumber(): void { + $this->expectException(InvalidArgumentException::class); new MLPClassifier(2, [], [0, 1]); } - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - */ public function testThrowExceptionOnInvalidPartialTrainingClasses(): void { + $this->expectException(InvalidArgumentException::class); $classifier = new MLPClassifier(2, [2], [0, 1]); $classifier->partialTrain( [[0, 1], [1, 0]], @@ -181,11 +178,9 @@ public function testThrowExceptionOnInvalidPartialTrainingClasses(): void ); } - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - */ public function testThrowExceptionOnInvalidClassesNumber(): void { + $this->expectException(InvalidArgumentException::class); new MLPClassifier(2, [2], [0]); } diff --git a/tests/Phpml/Clustering/KMeansTest.php b/tests/Phpml/Clustering/KMeansTest.php index f4533408..d212157d 100644 --- a/tests/Phpml/Clustering/KMeansTest.php +++ b/tests/Phpml/Clustering/KMeansTest.php @@ -5,6 +5,7 @@ namespace tests\Phpml\Clustering; use Phpml\Clustering\KMeans; +use Phpml\Exception\InvalidArgumentException; use PHPUnit\Framework\TestCase; class KMeansTest extends TestCase @@ -51,11 +52,9 @@ public function testKMeansInitializationMethods(): void $this->assertCount(4, $clusters); } - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - */ public function testThrowExceptionOnInvalidClusterNumber(): void { + $this->expectException(InvalidArgumentException::class); new KMeans(0); } } diff --git a/tests/Phpml/CrossValidation/RandomSplitTest.php b/tests/Phpml/CrossValidation/RandomSplitTest.php index 070e36bc..8058fd6f 100644 --- a/tests/Phpml/CrossValidation/RandomSplitTest.php +++ b/tests/Phpml/CrossValidation/RandomSplitTest.php @@ -6,23 +6,20 @@ use Phpml\CrossValidation\RandomSplit; use Phpml\Dataset\ArrayDataset; +use Phpml\Exception\InvalidArgumentException; use PHPUnit\Framework\TestCase; class RandomSplitTest extends TestCase { - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - */ public function testThrowExceptionOnToSmallTestSize(): void { + $this->expectException(InvalidArgumentException::class); new RandomSplit(new ArrayDataset([], []), 0); } - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - */ public function testThrowExceptionOnToBigTestSize(): void { + $this->expectException(InvalidArgumentException::class); new RandomSplit(new ArrayDataset([], []), 1); } diff --git a/tests/Phpml/Dataset/ArrayDatasetTest.php b/tests/Phpml/Dataset/ArrayDatasetTest.php index 41e037be..e0a6b91e 100644 --- a/tests/Phpml/Dataset/ArrayDatasetTest.php +++ b/tests/Phpml/Dataset/ArrayDatasetTest.php @@ -5,15 +5,14 @@ namespace tests\Phpml\Dataset; use Phpml\Dataset\ArrayDataset; +use Phpml\Exception\InvalidArgumentException; use PHPUnit\Framework\TestCase; class ArrayDatasetTest extends TestCase { - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - */ public function testThrowExceptionOnInvalidArgumentsSize(): void { + $this->expectException(InvalidArgumentException::class); new ArrayDataset([0, 1], [0]); } diff --git a/tests/Phpml/Dataset/CsvDatasetTest.php b/tests/Phpml/Dataset/CsvDatasetTest.php index f5cc8518..50492537 100644 --- a/tests/Phpml/Dataset/CsvDatasetTest.php +++ b/tests/Phpml/Dataset/CsvDatasetTest.php @@ -5,15 +5,14 @@ namespace tests\Phpml\Dataset; use Phpml\Dataset\CsvDataset; +use Phpml\Exception\FileException; use PHPUnit\Framework\TestCase; class CsvDatasetTest extends TestCase { - /** - * @expectedException \Phpml\Exception\FileException - */ public function testThrowExceptionOnMissingFile(): void { + $this->expectException(FileException::class); new CsvDataset('missingFile', 3); } diff --git a/tests/Phpml/Dataset/FilesDatasetTest.php b/tests/Phpml/Dataset/FilesDatasetTest.php index 0592d061..ee08395a 100644 --- a/tests/Phpml/Dataset/FilesDatasetTest.php +++ b/tests/Phpml/Dataset/FilesDatasetTest.php @@ -5,15 +5,14 @@ namespace tests\Phpml\Dataset; use Phpml\Dataset\FilesDataset; +use Phpml\Exception\DatasetException; use PHPUnit\Framework\TestCase; class FilesDatasetTest extends TestCase { - /** - * @expectedException \Phpml\Exception\DatasetException - */ public function testThrowExceptionOnMissingRootFolder(): void { + $this->expectException(DatasetException::class); new FilesDataset('some/not/existed/path'); } diff --git a/tests/Phpml/FeatureExtraction/StopWordsTest.php b/tests/Phpml/FeatureExtraction/StopWordsTest.php index 4b715ef5..6d97a239 100644 --- a/tests/Phpml/FeatureExtraction/StopWordsTest.php +++ b/tests/Phpml/FeatureExtraction/StopWordsTest.php @@ -4,6 +4,7 @@ namespace tests\Phpml\FeatureExtraction; +use Phpml\Exception\InvalidArgumentException; use Phpml\FeatureExtraction\StopWords; use PHPUnit\Framework\TestCase; @@ -22,11 +23,9 @@ public function testCustomStopWords(): void $this->assertFalse($stopWords->isStopWord('amet')); } - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - */ public function testThrowExceptionOnInvalidLanguage(): void { + $this->expectException(InvalidArgumentException::class); StopWords::factory('Lorem'); } diff --git a/tests/Phpml/Math/ComparisonTest.php b/tests/Phpml/Math/ComparisonTest.php index 2c72f5fb..ecb58c2e 100644 --- a/tests/Phpml/Math/ComparisonTest.php +++ b/tests/Phpml/Math/ComparisonTest.php @@ -4,6 +4,7 @@ namespace tests\Phpml\Math; +use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Comparison; use PHPUnit\Framework\TestCase; @@ -22,12 +23,10 @@ public function testResult($a, $b, string $operator, bool $expected): void $this->assertEquals($expected, $result); } - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - * @expectedExceptionMessage Invalid operator "~=" provided - */ public function testThrowExceptionWhenOperatorIsInvalid(): void { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid operator "~=" provided'); Comparison::compare(1, 1, '~='); } diff --git a/tests/Phpml/Math/Distance/ChebyshevTest.php b/tests/Phpml/Math/Distance/ChebyshevTest.php index 893c0007..56d6685f 100644 --- a/tests/Phpml/Math/Distance/ChebyshevTest.php +++ b/tests/Phpml/Math/Distance/ChebyshevTest.php @@ -4,6 +4,7 @@ namespace tests\Phpml\Metric; +use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Distance\Chebyshev; use PHPUnit\Framework\TestCase; @@ -19,14 +20,11 @@ public function setUp(): void $this->distanceMetric = new Chebyshev(); } - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - */ public function testThrowExceptionOnInvalidArguments(): void { + $this->expectException(InvalidArgumentException::class); $a = [0, 1, 2]; $b = [0, 2]; - $this->distanceMetric->distance($a, $b); } diff --git a/tests/Phpml/Math/Distance/EuclideanTest.php b/tests/Phpml/Math/Distance/EuclideanTest.php index 03bf7f3c..4acb3d4c 100644 --- a/tests/Phpml/Math/Distance/EuclideanTest.php +++ b/tests/Phpml/Math/Distance/EuclideanTest.php @@ -4,6 +4,7 @@ namespace tests\Phpml\Metric; +use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Distance\Euclidean; use PHPUnit\Framework\TestCase; @@ -19,14 +20,11 @@ public function setUp(): void $this->distanceMetric = new Euclidean(); } - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - */ public function testThrowExceptionOnInvalidArguments(): void { + $this->expectException(InvalidArgumentException::class); $a = [0, 1, 2]; $b = [0, 2]; - $this->distanceMetric->distance($a, $b); } diff --git a/tests/Phpml/Math/Distance/ManhattanTest.php b/tests/Phpml/Math/Distance/ManhattanTest.php index 9c20edd1..2a058740 100644 --- a/tests/Phpml/Math/Distance/ManhattanTest.php +++ b/tests/Phpml/Math/Distance/ManhattanTest.php @@ -4,6 +4,7 @@ namespace tests\Phpml\Metric; +use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Distance\Manhattan; use PHPUnit\Framework\TestCase; @@ -19,14 +20,11 @@ public function setUp(): void $this->distanceMetric = new Manhattan(); } - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - */ public function testThrowExceptionOnInvalidArguments(): void { + $this->expectException(InvalidArgumentException::class); $a = [0, 1, 2]; $b = [0, 2]; - $this->distanceMetric->distance($a, $b); } diff --git a/tests/Phpml/Math/Distance/MinkowskiTest.php b/tests/Phpml/Math/Distance/MinkowskiTest.php index 81ecd975..a8159d7b 100644 --- a/tests/Phpml/Math/Distance/MinkowskiTest.php +++ b/tests/Phpml/Math/Distance/MinkowskiTest.php @@ -4,6 +4,7 @@ namespace tests\Phpml\Metric; +use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Distance\Minkowski; use PHPUnit\Framework\TestCase; @@ -19,14 +20,11 @@ public function setUp(): void $this->distanceMetric = new Minkowski(); } - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - */ public function testThrowExceptionOnInvalidArguments(): void { + $this->expectException(InvalidArgumentException::class); $a = [0, 1, 2]; $b = [0, 2]; - $this->distanceMetric->distance($a, $b); } diff --git a/tests/Phpml/Math/MatrixTest.php b/tests/Phpml/Math/MatrixTest.php index 8d0e1be1..fce83bff 100644 --- a/tests/Phpml/Math/MatrixTest.php +++ b/tests/Phpml/Math/MatrixTest.php @@ -4,16 +4,16 @@ namespace tests\Phpml\Math; +use Phpml\Exception\InvalidArgumentException; +use Phpml\Exception\MatrixException; use Phpml\Math\Matrix; use PHPUnit\Framework\TestCase; class MatrixTest extends TestCase { - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - */ public function testThrowExceptionOnInvalidMatrixSupplied(): void { + $this->expectException(InvalidArgumentException::class); new Matrix([[1, 2], [3]]); } @@ -29,20 +29,16 @@ public function testCreateMatrixFromFlatArray(): void $this->assertEquals($flatArray, $matrix->getColumnValues(0)); } - /** - * @expectedException \Phpml\Exception\MatrixException - */ public function testThrowExceptionOnInvalidColumnNumber(): void { + $this->expectException(MatrixException::class); $matrix = new Matrix([[1, 2, 3], [4, 5, 6]]); $matrix->getColumnValues(4); } - /** - * @expectedException \Phpml\Exception\MatrixException - */ public function testThrowExceptionOnGetDeterminantIfArrayIsNotSquare(): void { + $this->expectException(MatrixException::class); $matrix = new Matrix([[1, 2, 3], [4, 5, 6]]); $matrix->getDeterminant(); } @@ -85,14 +81,11 @@ public function testMatrixTranspose(): void $this->assertEquals($transposedMatrix, $matrix->transpose()->toArray()); } - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - */ public function testThrowExceptionOnMultiplyWhenInconsistentMatrixSupplied(): void { + $this->expectException(InvalidArgumentException::class); $matrix1 = new Matrix([[1, 2, 3], [4, 5, 6]]); $matrix2 = new Matrix([[3, 2, 1], [6, 5, 4]]); - $matrix1->multiply($matrix2); } @@ -132,26 +125,21 @@ public function testDivideByScalar(): void $this->assertEquals($quotient, $matrix->divideByScalar(2)->toArray()); } - /** - * @expectedException \Phpml\Exception\MatrixException - */ public function testThrowExceptionWhenInverseIfArrayIsNotSquare(): void { + $this->expectException(MatrixException::class); $matrix = new Matrix([[1, 2, 3], [4, 5, 6]]); $matrix->inverse(); } - /** - * @expectedException \Phpml\Exception\MatrixException - */ public function testThrowExceptionWhenInverseIfMatrixIsSingular(): void { + $this->expectException(MatrixException::class); $matrix = new Matrix([ [0, 0, 0], [0, 0, 0], [0, 0, 0], ]); - $matrix->inverse(); } diff --git a/tests/Phpml/Math/Statistic/CorrelationTest.php b/tests/Phpml/Math/Statistic/CorrelationTest.php index 7fd2cec0..c091bb2e 100644 --- a/tests/Phpml/Math/Statistic/CorrelationTest.php +++ b/tests/Phpml/Math/Statistic/CorrelationTest.php @@ -4,6 +4,7 @@ namespace test\Phpml\Math\StandardDeviation; +use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Statistic\Correlation; use PHPUnit\Framework\TestCase; @@ -29,11 +30,9 @@ public function testPearsonCorrelation(): void $this->assertEquals(0.911, Correlation::pearson($x, $y), '', $delta); } - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - */ public function testThrowExceptionOnInvalidArgumentsForPearsonCorrelation(): void { + $this->expectException(InvalidArgumentException::class); Correlation::pearson([1, 2, 4], [3, 5]); } } diff --git a/tests/Phpml/Math/Statistic/MeanTest.php b/tests/Phpml/Math/Statistic/MeanTest.php index 86553e06..f19479a9 100644 --- a/tests/Phpml/Math/Statistic/MeanTest.php +++ b/tests/Phpml/Math/Statistic/MeanTest.php @@ -4,16 +4,15 @@ namespace test\Phpml\Math\StandardDeviation; +use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Statistic\Mean; use PHPUnit\Framework\TestCase; class MeanTest extends TestCase { - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - */ public function testArithmeticThrowExceptionOnEmptyArray(): void { + $this->expectException(InvalidArgumentException::class); Mean::arithmetic([]); } @@ -25,11 +24,9 @@ public function testArithmeticMean(): void $this->assertEquals(1.7, Mean::arithmetic([0.5, 0.5, 1.5, 2.5, 3.5]), '', $delta); } - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - */ public function testMedianThrowExceptionOnEmptyArray(): void { + $this->expectException(InvalidArgumentException::class); Mean::median([]); } @@ -47,11 +44,9 @@ public function testMedianOnEvenLengthArray(): void $this->assertEquals(3.5, Mean::median($numbers)); } - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - */ public function testModeThrowExceptionOnEmptyArray(): void { + $this->expectException(InvalidArgumentException::class); Mean::mode([]); } diff --git a/tests/Phpml/Math/Statistic/StandardDeviationTest.php b/tests/Phpml/Math/Statistic/StandardDeviationTest.php index ead67b5d..4bc43928 100644 --- a/tests/Phpml/Math/Statistic/StandardDeviationTest.php +++ b/tests/Phpml/Math/Statistic/StandardDeviationTest.php @@ -4,6 +4,7 @@ namespace test\Phpml\Math\StandardDeviation; +use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Statistic\StandardDeviation; use PHPUnit\Framework\TestCase; @@ -25,19 +26,15 @@ public function testStandardDeviationOfPopulationSample(): void $this->assertEquals(50989, StandardDeviation::population($population), '', $delta); } - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - */ public function testThrowExceptionOnEmptyArrayIfNotSample(): void { + $this->expectException(InvalidArgumentException::class); StandardDeviation::population([], false); } - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - */ public function testThrowExceptionOnToSmallArray(): void { + $this->expectException(InvalidArgumentException::class); StandardDeviation::population([1]); } } diff --git a/tests/Phpml/Metric/AccuracyTest.php b/tests/Phpml/Metric/AccuracyTest.php index 885893d6..e876da19 100644 --- a/tests/Phpml/Metric/AccuracyTest.php +++ b/tests/Phpml/Metric/AccuracyTest.php @@ -7,20 +7,18 @@ use Phpml\Classification\SVC; use Phpml\CrossValidation\RandomSplit; use Phpml\Dataset\Demo\IrisDataset; +use Phpml\Exception\InvalidArgumentException; use Phpml\Metric\Accuracy; use Phpml\SupportVectorMachine\Kernel; use PHPUnit\Framework\TestCase; class AccuracyTest extends TestCase { - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - */ public function testThrowExceptionOnInvalidArguments(): void { + $this->expectException(InvalidArgumentException::class); $actualLabels = ['a', 'b', 'a', 'b']; $predictedLabels = ['a', 'a']; - Accuracy::score($actualLabels, $predictedLabels); } diff --git a/tests/Phpml/ModelManagerTest.php b/tests/Phpml/ModelManagerTest.php index e44a32ac..f47efb73 100644 --- a/tests/Phpml/ModelManagerTest.php +++ b/tests/Phpml/ModelManagerTest.php @@ -4,6 +4,7 @@ namespace tests; +use Phpml\Exception\FileException; use Phpml\ModelManager; use Phpml\Regression\LeastSquares; use PHPUnit\Framework\TestCase; @@ -23,11 +24,9 @@ public function testSaveAndRestore(): void $this->assertEquals($estimator, $restored); } - /** - * @expectedException \Phpml\Exception\FileException - */ public function testRestoreWrongFile(): void { + $this->expectException(FileException::class); $filepath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'unexisting'; $modelManager = new ModelManager(); $modelManager->restoreFromFile($filepath); diff --git a/tests/Phpml/NeuralNetwork/LayerTest.php b/tests/Phpml/NeuralNetwork/LayerTest.php index 72d8758e..11f5e6a0 100644 --- a/tests/Phpml/NeuralNetwork/LayerTest.php +++ b/tests/Phpml/NeuralNetwork/LayerTest.php @@ -4,6 +4,7 @@ namespace tests\Phpml\NeuralNetwork; +use Phpml\Exception\InvalidArgumentException; use Phpml\NeuralNetwork\Layer; use Phpml\NeuralNetwork\Node\Bias; use Phpml\NeuralNetwork\Node\Neuron; @@ -39,11 +40,9 @@ public function testLayerInitializationWithExplicitNodesType(): void } } - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - */ public function testThrowExceptionOnInvalidNodeClass(): void { + $this->expectException(InvalidArgumentException::class); new Layer(1, stdClass::class); } diff --git a/tests/Phpml/Preprocessing/NormalizerTest.php b/tests/Phpml/Preprocessing/NormalizerTest.php index 3d5940ad..5dfc2076 100644 --- a/tests/Phpml/Preprocessing/NormalizerTest.php +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -4,16 +4,15 @@ namespace tests\Phpml\Preprocessing; +use Phpml\Exception\NormalizerException; use Phpml\Preprocessing\Normalizer; use PHPUnit\Framework\TestCase; class NormalizerTest extends TestCase { - /** - * @expectedException \Phpml\Exception\NormalizerException - */ public function testThrowExceptionOnInvalidNorm(): void { + $this->expectException(NormalizerException::class); new Normalizer(99); } diff --git a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php index ebbf99f2..59154e36 100644 --- a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php @@ -4,6 +4,7 @@ namespace tests\Phpml\SupportVectorMachine; +use Phpml\Exception\InvalidArgumentException; use Phpml\SupportVectorMachine\Kernel; use Phpml\SupportVectorMachine\SupportVectorMachine; use Phpml\SupportVectorMachine\Type; @@ -81,32 +82,26 @@ public function testPredictSampleFromMultipleClassWithRbfKernel(): void $this->assertEquals('c', $predictions[2]); } - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - * @expectedExceptionMessage is not writable - */ public function testThrowExceptionWhenVarPathIsNotWritable(): void { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('is not writable'); $svm = new SupportVectorMachine(Type::C_SVC, Kernel::RBF); $svm->setVarPath('var-path'); } - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - * @expectedExceptionMessage does not exist - */ public function testThrowExceptionWhenBinPathDoesNotExist(): void { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('does not exist'); $svm = new SupportVectorMachine(Type::C_SVC, Kernel::RBF); $svm->setBinPath('bin-path'); } - /** - * @expectedException \Phpml\Exception\InvalidArgumentException - * @expectedExceptionMessage not found - */ public function testThrowExceptionWhenFileIsNotFoundInBinPath(): void { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('not found'); $svm = new SupportVectorMachine(Type::C_SVC, Kernel::RBF); $svm->setBinPath('var'); } From c4f58f7f6f35450bf276ab72e19550713b7f2ac2 Mon Sep 17 00:00:00 2001 From: Yuji Uchiyama Date: Tue, 5 Dec 2017 20:03:55 +0900 Subject: [PATCH 213/328] Fix logistic regression implementation (#169) * Fix target value of LogisticRegression * Fix probability calculation in LogisticRegression * Change the default cost function to log-likelihood * Remove redundant round function * Fix for coding standard --- .../Linear/LogisticRegression.php | 21 ++-- .../Linear/LogisticRegressionTest.php | 106 ++++++++++++++++++ 2 files changed, 118 insertions(+), 9 deletions(-) create mode 100644 tests/Phpml/Classification/Linear/LogisticRegressionTest.php diff --git a/src/Phpml/Classification/Linear/LogisticRegression.php b/src/Phpml/Classification/Linear/LogisticRegression.php index 6b8cdd55..38181617 100644 --- a/src/Phpml/Classification/Linear/LogisticRegression.php +++ b/src/Phpml/Classification/Linear/LogisticRegression.php @@ -32,7 +32,7 @@ class LogisticRegression extends Adaline * * @var string */ - protected $costFunction = 'sse'; + protected $costFunction = 'log'; /** * Regularization term: only 'L2' is supported @@ -67,7 +67,7 @@ public function __construct( int $maxIterations = 500, bool $normalizeInputs = true, int $trainingType = self::CONJUGATE_GRAD_TRAINING, - string $cost = 'sse', + string $cost = 'log', string $penalty = 'L2' ) { $trainingTypes = range(self::BATCH_TRAINING, self::CONJUGATE_GRAD_TRAINING); @@ -190,6 +190,8 @@ protected function getCostFunction(): Closure $hX = 1e-10; } + $y = $y < 0 ? 0 : 1; + $error = -$y * log($hX) - (1 - $y) * log(1 - $hX); $gradient = $hX - $y; @@ -213,6 +215,8 @@ protected function getCostFunction(): Closure $this->weights = $weights; $hX = $this->output($sample); + $y = $y < 0 ? 0 : 1; + $error = ($y - $hX) ** 2; $gradient = -($y - $hX) * $hX * (1 - $hX); @@ -243,7 +247,7 @@ protected function outputClass(array $sample): int { $output = $this->output($sample); - if (round($output) > 0.5) { + if ($output > 0.5) { return 1; } @@ -260,14 +264,13 @@ protected function outputClass(array $sample): int */ protected function predictProbability(array $sample, $label): float { - $predicted = $this->predictSampleBinary($sample); - - if ((string) $predicted == (string) $label) { - $sample = $this->checkNormalizedSample($sample); + $sample = $this->checkNormalizedSample($sample); + $probability = $this->output($sample); - return (float) abs($this->output($sample) - 0.5); + if (array_search($label, $this->labels, true) > 0) { + return $probability; } - return 0.0; + return 1 - $probability; } } diff --git a/tests/Phpml/Classification/Linear/LogisticRegressionTest.php b/tests/Phpml/Classification/Linear/LogisticRegressionTest.php new file mode 100644 index 00000000..85fc1596 --- /dev/null +++ b/tests/Phpml/Classification/Linear/LogisticRegressionTest.php @@ -0,0 +1,106 @@ +train($samples, $targets); + $this->assertEquals(0, $classifier->predict([0.1, 0.1])); + $this->assertEquals(1, $classifier->predict([0.9, 0.9])); + } + + public function testPredictMultiClassSample(): void + { + // By use of One-v-Rest, Perceptron can perform multi-class classification + // The samples should be separable by lines perpendicular to the dimensions + $samples = [ + [0, 0], [0, 1], [1, 0], [1, 1], // First group : a cluster at bottom-left corner in 2D + [5, 5], [6, 5], [5, 6], [7, 5], // Second group: another cluster at the middle-right + [3, 10], [3, 10], [3, 8], [3, 9], // Third group : cluster at the top-middle + ]; + $targets = [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2]; + + $classifier = new LogisticRegression(); + $classifier->train($samples, $targets); + $this->assertEquals(0, $classifier->predict([0.5, 0.5])); + $this->assertEquals(1, $classifier->predict([6.0, 5.0])); + $this->assertEquals(2, $classifier->predict([3.0, 9.5])); + } + + public function testPredictProbabilitySingleSample(): void + { + $samples = [[0, 0], [1, 0], [0, 1], [1, 1], [0.4, 0.4], [0.6, 0.6]]; + $targets = [0, 0, 0, 1, 0, 1]; + $classifier = new LogisticRegression(); + $classifier->train($samples, $targets); + + $property = new ReflectionProperty($classifier, 'classifiers'); + $property->setAccessible(true); + $predictor = $property->getValue($classifier)[0]; + $method = new ReflectionMethod($predictor, 'predictProbability'); + $method->setAccessible(true); + + $zero = $method->invoke($predictor, [0.1, 0.1], 0); + $one = $method->invoke($predictor, [0.1, 0.1], 1); + $this->assertEquals(1, $zero + $one, null, 1e-6); + $this->assertTrue($zero > $one); + + $zero = $method->invoke($predictor, [0.9, 0.9], 0); + $one = $method->invoke($predictor, [0.9, 0.9], 1); + $this->assertEquals(1, $zero + $one, null, 1e-6); + $this->assertTrue($zero < $one); + } + + public function testPredictProbabilityMultiClassSample(): void + { + $samples = [ + [0, 0], [0, 1], [1, 0], [1, 1], + [5, 5], [6, 5], [5, 6], [6, 6], + [3, 10], [3, 10], [3, 8], [3, 9], + ]; + $targets = [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2]; + + $classifier = new LogisticRegression(); + $classifier->train($samples, $targets); + + $property = new ReflectionProperty($classifier, 'classifiers'); + $property->setAccessible(true); + + $predictor = $property->getValue($classifier)[0]; + $method = new ReflectionMethod($predictor, 'predictProbability'); + $method->setAccessible(true); + $zero = $method->invoke($predictor, [3.0, 9.5], 0); + $not_zero = $method->invoke($predictor, [3.0, 9.5], 'not_0'); + + $predictor = $property->getValue($classifier)[1]; + $method = new ReflectionMethod($predictor, 'predictProbability'); + $method->setAccessible(true); + $one = $method->invoke($predictor, [3.0, 9.5], 1); + $not_one = $method->invoke($predictor, [3.0, 9.5], 'not_1'); + + $predictor = $property->getValue($classifier)[2]; + $method = new ReflectionMethod($predictor, 'predictProbability'); + $method->setAccessible(true); + $two = $method->invoke($predictor, [3.0, 9.5], 2); + $not_two = $method->invoke($predictor, [3.0, 9.5], 'not_2'); + + $this->assertEquals(1, $zero + $not_zero, null, 1e-6); + $this->assertEquals(1, $one + $not_one, null, 1e-6); + $this->assertEquals(1, $two + $not_two, null, 1e-6); + $this->assertTrue($zero < $two); + $this->assertTrue($one < $two); + } +} From c4ad117d285e723fa56ca334334bacc6cc21e089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Monlla=C3=B3?= Date: Tue, 5 Dec 2017 21:09:06 +0100 Subject: [PATCH 214/328] Ability to update learningRate in MLP (#160) * Allow people to update the learning rate * Test for learning rate setter --- .../multilayer-perceptron-classifier.md | 6 ++++ .../Network/MultilayerPerceptron.php | 6 ++++ .../Training/Backpropagation.php | 5 ++++ .../Network/MultilayerPerceptronTest.php | 28 +++++++++++++++++++ 4 files changed, 45 insertions(+) create mode 100644 tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php diff --git a/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md b/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md index a6b060a4..72d0b4be 100644 --- a/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md +++ b/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md @@ -45,6 +45,12 @@ $mlp->partialTrain( ``` +You can update the learning rate between partialTrain runs: + +``` +$mlp->setLearningRate(0.1); +``` + ## Predict To predict sample label use predict method. You can provide one sample or array of samples: diff --git a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php index a38e952a..5ace597c 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -95,6 +95,12 @@ public function partialTrain(array $samples, array $targets, array $classes = [] } } + public function setLearningRate(float $learningRate): void + { + $this->learningRate = $learningRate; + $this->backpropagation->setLearningRate($this->learningRate); + } + /** * @param mixed $target */ diff --git a/src/Phpml/NeuralNetwork/Training/Backpropagation.php b/src/Phpml/NeuralNetwork/Training/Backpropagation.php index 8382a8e4..df515b21 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -25,6 +25,11 @@ class Backpropagation private $prevSigmas = null; public function __construct(float $learningRate) + { + $this->setLearningRate($learningRate); + } + + public function setLearningRate(float $learningRate): void { $this->learningRate = $learningRate; } diff --git a/tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php b/tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php new file mode 100644 index 00000000..c244c276 --- /dev/null +++ b/tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php @@ -0,0 +1,28 @@ +getMockForAbstractClass( + MultilayerPerceptron::class, + [5, [3], [0, 1], 1000, null, 0.42] + ); + + $this->assertEquals(0.42, $this->readAttribute($mlp, 'learningRate')); + $backprop = $this->readAttribute($mlp, 'backpropagation'); + $this->assertEquals(0.42, $this->readAttribute($backprop, 'learningRate')); + + $mlp->setLearningRate(0.24); + $this->assertEquals(0.24, $this->readAttribute($mlp, 'learningRate')); + $backprop = $this->readAttribute($mlp, 'backpropagation'); + $this->assertEquals(0.24, $this->readAttribute($backprop, 'learningRate')); + } +} From fbbe5c57617f2e6792121f34bf938a0a82e10aae Mon Sep 17 00:00:00 2001 From: Anatoly Pashin Date: Sat, 6 Jan 2018 20:12:42 +1000 Subject: [PATCH 215/328] Update README.md (#181) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 69f0eb7e..874d0d25 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Fresh approach to Machine Learning in PHP. Algorithms, Cross Validation, Neural Network, Preprocessing, Feature Extraction and much more in one library. -PHP-ML requires PHP >= 7.0. +PHP-ML requires PHP >= 7.1. Simple example of classification: ```php From a348111e97ae89e2c969835e5671555230bdd2d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Votruba?= Date: Sat, 6 Jan 2018 13:09:33 +0100 Subject: [PATCH 216/328] Add PHPStan and level to max (#168) * tests: update to PHPUnit 6.0 with rector * fix namespaces on tests * composer + tests: use standard test namespace naming * update travis * resolve conflict * phpstan lvl 2 * phpstan lvl 3 * phpstan lvl 4 * phpstan lvl 5 * phpstan lvl 6 * phpstan lvl 7 * level max * resolve conflict * [cs] clean empty docs * composer: bump to PHPUnit 6.4 * cleanup * composer + travis: add phpstan * phpstan lvl 1 * composer: update dev deps * phpstan fixes * update Contributing with new tools * docs: link fixes, PHP version update * composer: drop php-cs-fixer, cs already handled by ecs * ecs: add old set rules * [cs] apply rest of rules --- .php_cs | 32 --- .travis.yml | 1 + CONTRIBUTING.md | 27 ++- composer.json | 23 ++- composer.lock | 184 ++++++++++-------- docs/index.md | 68 +++---- easy-coding-standard.neon | 19 ++ phpstan.neon | 19 ++ src/Phpml/Association/Apriori.php | 1 + src/Phpml/Classification/DecisionTree.php | 8 +- .../DecisionTree/DecisionTreeLeaf.php | 4 +- src/Phpml/Classification/Ensemble/Bagging.php | 2 +- src/Phpml/Classification/Linear/Adaline.php | 2 +- .../Classification/Linear/DecisionStump.php | 2 +- .../Linear/LogisticRegression.php | 16 +- .../Classification/Linear/Perceptron.php | 10 +- src/Phpml/Clustering/KMeans/Point.php | 3 + src/Phpml/Clustering/KMeans/Space.php | 5 +- src/Phpml/DimensionReduction/KernelPCA.php | 2 +- .../TokenCountVectorizer.php | 2 +- src/Phpml/Helper/OneVsRest.php | 2 +- src/Phpml/Helper/Optimizer/GD.php | 4 +- src/Phpml/Helper/Optimizer/StochasticGD.php | 6 +- .../LinearAlgebra/EigenvalueDecomposition.php | 2 + .../Math/LinearAlgebra/LUDecomposition.php | 4 +- src/Phpml/Math/Set.php | 6 +- src/Phpml/Metric/ClassificationReport.php | 1 + .../Network/MultilayerPerceptron.php | 9 +- src/Phpml/NeuralNetwork/Node/Neuron.php | 10 +- .../Training/Backpropagation.php | 8 +- src/Phpml/Preprocessing/Imputer.php | 3 +- .../SupportVectorMachine/DataTransformer.php | 2 + .../SupportVectorMachine.php | 2 +- tests/Phpml/Association/AprioriTest.php | 6 +- .../DecisionTree/DecisionTreeLeafTest.php | 2 +- .../Phpml/Classification/DecisionTreeTest.php | 2 +- .../Classification/Ensemble/AdaBoostTest.php | 2 +- .../Classification/Ensemble/BaggingTest.php | 2 +- .../Ensemble/RandomForestTest.php | 3 +- .../Classification/KNearestNeighborsTest.php | 2 +- .../Classification/Linear/AdalineTest.php | 2 +- .../Linear/DecisionStumpTest.php | 2 +- .../Classification/Linear/PerceptronTest.php | 2 +- .../Classification/MLPClassifierTest.php | 2 +- tests/Phpml/Classification/NaiveBayesTest.php | 2 +- tests/Phpml/Classification/SVCTest.php | 2 +- tests/Phpml/Clustering/DBSCANTest.php | 2 +- tests/Phpml/Clustering/FuzzyCMeansTest.php | 2 +- tests/Phpml/Clustering/KMeansTest.php | 2 +- .../Phpml/CrossValidation/RandomSplitTest.php | 2 +- .../StratifiedRandomSplitTest.php | 2 +- tests/Phpml/Dataset/ArrayDatasetTest.php | 2 +- tests/Phpml/Dataset/CsvDatasetTest.php | 2 +- tests/Phpml/Dataset/Demo/GlassDatasetTest.php | 2 +- tests/Phpml/Dataset/Demo/IrisDatasetTest.php | 2 +- tests/Phpml/Dataset/Demo/WineDatasetTest.php | 2 +- tests/Phpml/Dataset/FilesDatasetTest.php | 2 +- .../DimensionReduction/KernelPCATest.php | 2 +- tests/Phpml/DimensionReduction/LDATest.php | 2 +- tests/Phpml/DimensionReduction/PCATest.php | 2 +- .../Phpml/FeatureExtraction/StopWordsTest.php | 2 +- .../TfIdfTransformerTest.php | 2 +- .../TokenCountVectorizerTest.php | 2 +- tests/Phpml/Math/ComparisonTest.php | 6 +- tests/Phpml/Math/Distance/ChebyshevTest.php | 2 +- tests/Phpml/Math/Distance/EuclideanTest.php | 2 +- tests/Phpml/Math/Distance/ManhattanTest.php | 2 +- tests/Phpml/Math/Distance/MinkowskiTest.php | 2 +- tests/Phpml/Math/Kernel/RBFTest.php | 2 +- .../LinearAlgebra/EigenDecompositionTest.php | 2 +- tests/Phpml/Math/MatrixTest.php | 2 +- tests/Phpml/Math/ProductTest.php | 2 +- tests/Phpml/Math/SetTest.php | 2 +- .../Phpml/Math/Statistic/CorrelationTest.php | 2 +- tests/Phpml/Math/Statistic/CovarianceTest.php | 2 +- tests/Phpml/Math/Statistic/GaussianTest.php | 2 +- tests/Phpml/Math/Statistic/MeanTest.php | 2 +- .../Math/Statistic/StandardDeviationTest.php | 2 +- tests/Phpml/Metric/AccuracyTest.php | 2 +- .../Phpml/Metric/ClassificationReportTest.php | 2 +- tests/Phpml/Metric/ConfusionMatrixTest.php | 2 +- tests/Phpml/ModelManagerTest.php | 2 +- .../ActivationFunction/BinaryStepTest.php | 2 +- .../ActivationFunction/GaussianTest.php | 2 +- .../HyperboliTangentTest.php | 2 +- .../ActivationFunction/PReLUTest.php | 2 +- .../ActivationFunction/SigmoidTest.php | 2 +- .../ThresholdedReLUTest.php | 2 +- tests/Phpml/NeuralNetwork/LayerTest.php | 2 +- .../Network/LayeredNetworkTest.php | 8 +- tests/Phpml/NeuralNetwork/Node/BiasTest.php | 2 +- tests/Phpml/NeuralNetwork/Node/InputTest.php | 2 +- .../NeuralNetwork/Node/Neuron/SynapseTest.php | 6 +- tests/Phpml/NeuralNetwork/Node/NeuronTest.php | 3 +- tests/Phpml/PipelineTest.php | 2 +- tests/Phpml/Preprocessing/ImputerTest.php | 2 +- tests/Phpml/Preprocessing/NormalizerTest.php | 2 +- tests/Phpml/Regression/LeastSquaresTest.php | 2 +- tests/Phpml/Regression/SVRTest.php | 2 +- .../DataTransformerTest.php | 2 +- .../SupportVectorMachineTest.php | 2 +- .../Tokenization/WhitespaceTokenizerTest.php | 2 +- .../Phpml/Tokenization/WordTokenizerTest.php | 2 +- 103 files changed, 365 insertions(+), 284 deletions(-) delete mode 100644 .php_cs create mode 100644 phpstan.neon diff --git a/.php_cs b/.php_cs deleted file mode 100644 index 9fb8baad..00000000 --- a/.php_cs +++ /dev/null @@ -1,32 +0,0 @@ -setRules([ - '@PSR2' => true, - 'array_syntax' => ['syntax' => 'short'], - 'binary_operator_spaces' => ['align_double_arrow' => false, 'align_equals' => false], - 'blank_line_after_opening_tag' => true, - 'blank_line_before_return' => true, - 'cast_spaces' => true, - 'concat_space' => ['spacing' => 'none'], - 'declare_strict_types' => true, - 'method_separation' => true, - 'no_blank_lines_after_class_opening' => true, - 'no_spaces_around_offset' => ['positions' => ['inside', 'outside']], - 'no_unneeded_control_parentheses' => true, - 'no_unused_imports' => true, - 'phpdoc_align' => true, - 'phpdoc_no_access' => true, - 'phpdoc_separation' => true, - 'pre_increment' => true, - 'single_quote' => true, - 'trim_array_spaces' => true, - 'single_blank_line_before_namespace' => true, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->in(__DIR__ . '/src') - ->in(__DIR__ . '/tests') - ) - ->setRiskyAllowed(true) - ->setUsingCache(false); diff --git a/.travis.yml b/.travis.yml index 8f8a68ba..bf26816e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,7 @@ install: script: - vendor/bin/phpunit $PHPUNIT_FLAGS - if [[ $STATIC_ANALYSIS != "" ]]; then vendor/bin/ecs check src tests; fi + - if [[ $STATIC_ANALYSIS != "" ]]; then vendor/bin/phpstan.phar analyse src tests --level max --configuration phpstan.neon; fi after_success: - | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7d2f4512..b2d5f9e3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,31 +6,40 @@ code base clean, unified and future proof. ## Branch -You should only open pull requests against the master branch. +You should only open pull requests against the `master` branch. ## Unit-Tests Please try to add a test for your pull-request. You can run the unit-tests by calling: -``` -bin/phpunit +```bash +vendor/bin/phpunit ``` ## Travis -GitHub automatically run your pull request through Travis CI against PHP 7. -If you break the tests, I cannot merge your code, so please make sure that your code is working -before opening up a Pull-Request. +GitHub automatically run your pull request through Travis CI. +If you break the tests, I cannot merge your code, so please make sure that your code is working before opening up a Pull-Request. ## Merge Please allow me time to review your pull requests. I will give my best to review everything as fast as possible, but cannot always live up to my own expectations. -## Coding Standards +## Coding Standards & Static Analysis + +When contributing code to PHP-ML, you must follow its coding standards. To do that, just run: + +```bash +vendor/bin/ecs check src tests --fix +``` +[More about EasyCodingStandard](https://github.com/Symplify/EasyCodingStandard) + +Code has to also pass static analysis by [PHPStan](https://github.com/phpstan/phpstan): -When contributing code to PHP-ML, you must follow its coding standards. It's as easy as executing `./bin/php-cs-fixer fix` in root directory. +```bash +vendor/bin/phpstan.phar analyse src tests --level max --configuration phpstan.neon +``` -More about PHP-CS-Fixer: [http://cs.sensiolabs.org/](http://cs.sensiolabs.org/) ## Documentation diff --git a/composer.json b/composer.json index 7b774f29..b08d6f8f 100644 --- a/composer.json +++ b/composer.json @@ -11,19 +11,24 @@ "email": "arkadiusz.kondas@gmail.com" } ], - "autoload": { - "psr-4": { - "Phpml\\": "src/Phpml" - } - }, "require": { "php": "^7.1" }, "require-dev": { "phpunit/phpunit": "^6.4", - "friendsofphp/php-cs-fixer": "^2.4", - "symplify/easy-coding-standard": "dev-master as 2.5", - "symplify/coding-standard": "dev-master as 2.5", - "symplify/package-builder": "dev-master#3604bea as 2.5" + "symplify/easy-coding-standard": "v3.0.0-RC3", + "symplify/coding-standard": "v3.0.0-RC3", + "symplify/package-builder": "v3.0.0-RC3", + "phpstan/phpstan-shim": "^0.8" + }, + "autoload": { + "psr-4": { + "Phpml\\": "src/Phpml" + } + }, + "autoload-dev": { + "psr-4": { + "Phpml\\Tests\\": "tests/Phpml" + } } } diff --git a/composer.lock b/composer.lock index e10b2d84..4236a2d8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "0b709f785c1e62498755f557e49e1ba4", + "content-hash": "032ab1160f58aff496453a86648f7012", "packages": [], "packages-dev": [ { @@ -247,16 +247,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v2.8.1", + "version": "v2.8.2", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "04f71e56e03ba2627e345e8c949c80dcef0e683e" + "reference": "b331701944cbe492e466d2b46b2880068803eb08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/04f71e56e03ba2627e345e8c949c80dcef0e683e", - "reference": "04f71e56e03ba2627e345e8c949c80dcef0e683e", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/b331701944cbe492e466d2b46b2880068803eb08", + "reference": "b331701944cbe492e466d2b46b2880068803eb08", "shasum": "" }, "require": { @@ -323,7 +323,7 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2017-11-09T13:31:39+00:00" + "time": "2017-11-19T22:51:25+00:00" }, { "name": "gecko-packages/gecko-php-unit", @@ -1219,16 +1219,16 @@ }, { "name": "phpspec/prophecy", - "version": "v1.7.2", + "version": "1.7.3", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6" + "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", - "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf", + "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf", "shasum": "" }, "require": { @@ -1240,7 +1240,7 @@ }, "require-dev": { "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8 || ^5.6.5" + "phpunit/phpunit": "^4.8.35 || ^5.7" }, "type": "library", "extra": { @@ -1278,7 +1278,43 @@ "spy", "stub" ], - "time": "2017-09-04T11:05:03+00:00" + "time": "2017-11-24T13:59:53+00:00" + }, + { + "name": "phpstan/phpstan-shim", + "version": "0.8.5", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-shim.git", + "reference": "0b174a61fd99dea61f15ea6bd3bc424389f273d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-shim/zipball/0b174a61fd99dea61f15ea6bd3bc424389f273d4", + "reference": "0b174a61fd99dea61f15ea6bd3bc424389f273d4", + "shasum": "" + }, + "require": { + "php": "~7.0" + }, + "provide": { + "phpstan/phpstan": "0.8" + }, + "bin": [ + "phpstan.phar" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.8-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan Phar distribution", + "time": "2017-10-24T04:16:00+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1346,16 +1382,16 @@ }, { "name": "phpunit/php-file-iterator", - "version": "1.4.2", + "version": "1.4.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" + "reference": "8ebba84e5bd74fc5fdeb916b38749016c7232f93" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5", - "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/8ebba84e5bd74fc5fdeb916b38749016c7232f93", + "reference": "8ebba84e5bd74fc5fdeb916b38749016c7232f93", "shasum": "" }, "require": { @@ -1389,7 +1425,7 @@ "filesystem", "iterator" ], - "time": "2016-10-03T07:40:28+00:00" + "time": "2017-11-24T15:00:59+00:00" }, { "name": "phpunit/php-text-template", @@ -2418,7 +2454,7 @@ }, { "name": "symfony/config", - "version": "v3.3.12", + "version": "v3.3.13", "source": { "type": "git", "url": "https://github.com/symfony/config.git", @@ -2480,16 +2516,16 @@ }, { "name": "symfony/console", - "version": "v3.3.12", + "version": "v3.3.13", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "099302cc53e57cbb7414fd9f3ace40e5e2767c0b" + "reference": "63cd7960a0a522c3537f6326706d7f3b8de65805" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/099302cc53e57cbb7414fd9f3ace40e5e2767c0b", - "reference": "099302cc53e57cbb7414fd9f3ace40e5e2767c0b", + "url": "https://api.github.com/repos/symfony/console/zipball/63cd7960a0a522c3537f6326706d7f3b8de65805", + "reference": "63cd7960a0a522c3537f6326706d7f3b8de65805", "shasum": "" }, "require": { @@ -2544,11 +2580,11 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2017-11-12T16:53:41+00:00" + "time": "2017-11-16T15:24:32+00:00" }, { "name": "symfony/debug", - "version": "v3.3.12", + "version": "v3.3.13", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", @@ -2604,7 +2640,7 @@ }, { "name": "symfony/dependency-injection", - "version": "v3.3.12", + "version": "v3.3.13", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", @@ -2674,7 +2710,7 @@ }, { "name": "symfony/event-dispatcher", - "version": "v3.3.12", + "version": "v3.3.13", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", @@ -2737,7 +2773,7 @@ }, { "name": "symfony/filesystem", - "version": "v3.3.12", + "version": "v3.3.13", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", @@ -2786,7 +2822,7 @@ }, { "name": "symfony/finder", - "version": "v3.3.12", + "version": "v3.3.13", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", @@ -2835,7 +2871,7 @@ }, { "name": "symfony/http-foundation", - "version": "v3.3.12", + "version": "v3.3.13", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", @@ -2888,16 +2924,16 @@ }, { "name": "symfony/http-kernel", - "version": "v3.3.12", + "version": "v3.3.13", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "371ed63691c1ee8749613a6b48cf0e0cfa2b01e7" + "reference": "a2a942172b742217ab2ccd9399494af2aa17c766" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/371ed63691c1ee8749613a6b48cf0e0cfa2b01e7", - "reference": "371ed63691c1ee8749613a6b48cf0e0cfa2b01e7", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/a2a942172b742217ab2ccd9399494af2aa17c766", + "reference": "a2a942172b742217ab2ccd9399494af2aa17c766", "shasum": "" }, "require": { @@ -2970,11 +3006,11 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2017-11-13T19:37:21+00:00" + "time": "2017-11-16T18:14:43+00:00" }, { "name": "symfony/options-resolver", - "version": "v3.3.12", + "version": "v3.3.13", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", @@ -3201,7 +3237,7 @@ }, { "name": "symfony/process", - "version": "v3.3.12", + "version": "v3.3.13", "source": { "type": "git", "url": "https://github.com/symfony/process.git", @@ -3250,7 +3286,7 @@ }, { "name": "symfony/stopwatch", - "version": "v3.3.12", + "version": "v3.3.13", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", @@ -3299,7 +3335,7 @@ }, { "name": "symfony/yaml", - "version": "v3.3.12", + "version": "v3.3.13", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", @@ -3354,16 +3390,16 @@ }, { "name": "symplify/coding-standard", - "version": "dev-master", + "version": "v3.0.0-RC3", "source": { "type": "git", "url": "https://github.com/Symplify/CodingStandard.git", - "reference": "309fd562066cdc86b81375ff080b1ee2f900778e" + "reference": "0a3958f1cb6ce733def98f3abdf52a4e6c723879" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/CodingStandard/zipball/309fd562066cdc86b81375ff080b1ee2f900778e", - "reference": "309fd562066cdc86b81375ff080b1ee2f900778e", + "url": "https://api.github.com/repos/Symplify/CodingStandard/zipball/0a3958f1cb6ce733def98f3abdf52a4e6c723879", + "reference": "0a3958f1cb6ce733def98f3abdf52a4e6c723879", "shasum": "" }, "require": { @@ -3382,6 +3418,11 @@ "symplify/package-builder": "^2.5|^3.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, "autoload": { "psr-4": { "Symplify\\CodingStandard\\": "src", @@ -3394,20 +3435,20 @@ "MIT" ], "description": "Set of Symplify rules for PHP_CodeSniffer.", - "time": "2017-11-16 00:38:24" + "time": "2017-11-18T01:05:00+00:00" }, { "name": "symplify/easy-coding-standard", - "version": "dev-master", + "version": "v3.0.0-RC3", "source": { "type": "git", "url": "https://github.com/Symplify/EasyCodingStandard.git", - "reference": "4bac5271050f063b4455bd870cc215e0db57ddf8" + "reference": "7f2e7728a184c72945da482b23eb05b796f6502c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/EasyCodingStandard/zipball/4bac5271050f063b4455bd870cc215e0db57ddf8", - "reference": "4bac5271050f063b4455bd870cc215e0db57ddf8", + "url": "https://api.github.com/repos/Symplify/EasyCodingStandard/zipball/7f2e7728a184c72945da482b23eb05b796f6502c", + "reference": "7f2e7728a184c72945da482b23eb05b796f6502c", "shasum": "" }, "require": { @@ -3440,6 +3481,11 @@ "bin/easy-coding-standard.php" ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, "autoload": { "psr-4": { "Symplify\\EasyCodingStandard\\": "src", @@ -3455,20 +3501,20 @@ "MIT" ], "description": "Use Coding Standard with 0-knowledge of PHP-CS-Fixer and PHP_CodeSniffer.", - "time": "2017-11-16 15:36:21" + "time": "2017-11-18T14:13:17+00:00" }, { "name": "symplify/package-builder", - "version": "dev-master", + "version": "v3.0.0-RC3", "source": { "type": "git", "url": "https://github.com/Symplify/PackageBuilder.git", - "reference": "3604beadddfdee295b978d87475a834810a0ffd4" + "reference": "c86f75165ed2370563a9d4ff6604bbb24cffd5e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/PackageBuilder/zipball/3604beadddfdee295b978d87475a834810a0ffd4", - "reference": "3604beadddfdee295b978d87475a834810a0ffd4", + "url": "https://api.github.com/repos/Symplify/PackageBuilder/zipball/c86f75165ed2370563a9d4ff6604bbb24cffd5e5", + "reference": "c86f75165ed2370563a9d4ff6604bbb24cffd5e5", "shasum": "" }, "require": { @@ -3486,6 +3532,11 @@ "tracy/tracy": "^2.4" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, "autoload": { "psr-4": { "Symplify\\PackageBuilder\\": "src" @@ -3496,7 +3547,7 @@ "MIT" ], "description": "Dependency Injection, Console and Kernel toolkit for Symplify packages.", - "time": "2017-11-16T01:05:48+00:00" + "time": "2017-11-17T13:58:38+00:00" }, { "name": "theseer/tokenizer", @@ -3656,31 +3707,12 @@ "time": "2016-11-23T20:04:58+00:00" } ], - "aliases": [ - { - "alias": "2.5", - "alias_normalized": "2.5.0.0", - "version": "9999999-dev", - "package": "symplify/easy-coding-standard" - }, - { - "alias": "2.5", - "alias_normalized": "2.5.0.0", - "version": "9999999-dev", - "package": "symplify/coding-standard" - }, - { - "alias": "2.5", - "alias_normalized": "2.5.0.0", - "version": "9999999-dev", - "package": "symplify/package-builder" - } - ], + "aliases": [], "minimum-stability": "stable", "stability-flags": { - "symplify/easy-coding-standard": 20, - "symplify/coding-standard": 20, - "symplify/package-builder": 20 + "symplify/easy-coding-standard": 5, + "symplify/coding-standard": 5, + "symplify/package-builder": 5 }, "prefer-stable": false, "prefer-lowest": false, diff --git a/docs/index.md b/docs/index.md index ef3a0e9b..f817b0ac 100644 --- a/docs/index.md +++ b/docs/index.md @@ -17,11 +17,11 @@ Fresh approach to Machine Learning in PHP. Algorithms, Cross Validation, Neural Network, Preprocessing, Feature Extraction and much more in one library. -PHP-ML requires PHP >= 7.0. +PHP-ML requires PHP >= 7.1. Simple example of classification: ```php -require_once 'vendor/autoload.php'; +require_once __DIR__ . '/vendor/autoload.php'; use Phpml\Classification\KNearestNeighbors; @@ -54,57 +54,57 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( ## Features * Association rule Lerning - * [Apriori](machine-learning/association/apriori/) + * [Apriori](machine-learning/association/apriori.md) * Classification - * [SVC](machine-learning/classification/svc/) - * [k-Nearest Neighbors](machine-learning/classification/k-nearest-neighbors/) - * [Naive Bayes](machine-learning/classification/naive-bayes/) + * [SVC](machine-learning/classification/svc.md) + * [k-Nearest Neighbors](machine-learning/classification/k-nearest-neighbors.md) + * [Naive Bayes](machine-learning/classification/naive-bayes.md) * Regression - * [Least Squares](machine-learning/regression/least-squares/) - * [SVR](machine-learning/regression/svr/) + * [Least Squares](machine-learning/regression/least-squares.md) + * [SVR](machine-learning/regression/svr.md) * Clustering - * [k-Means](machine-learning/clustering/k-means/) - * [DBSCAN](machine-learning/clustering/dbscan/) + * [k-Means](machine-learning/clustering/k-means.md) + * [DBSCAN](machine-learning/clustering/dbscan.md) * Metric - * [Accuracy](machine-learning/metric/accuracy/) - * [Confusion Matrix](machine-learning/metric/confusion-matrix/) - * [Classification Report](machine-learning/metric/classification-report/) + * [Accuracy](machine-learning/metric/accuracy.md) + * [Confusion Matrix](machine-learning/metric/confusion-matrix.md) + * [Classification Report](machine-learning/metric/classification-report.md) * Workflow * [Pipeline](machine-learning/workflow/pipeline) * Neural Network - * [Multilayer Perceptron Classifier](machine-learning/neural-network/multilayer-perceptron-classifier/) + * [Multilayer Perceptron Classifier](machine-learning/neural-network/multilayer-perceptron-classifier.md) * Cross Validation - * [Random Split](machine-learning/cross-validation/random-split/) - * [Stratified Random Split](machine-learning/cross-validation/stratified-random-split/) + * [Random Split](machine-learning/cross-validation/random-split.md) + * [Stratified Random Split](machine-learning/cross-validation/stratified-random-split.md) * Preprocessing - * [Normalization](machine-learning/preprocessing/normalization/) - * [Imputation missing values](machine-learning/preprocessing/imputation-missing-values/) + * [Normalization](machine-learning/preprocessing/normalization.md) + * [Imputation missing values](machine-learning/preprocessing/imputation-missing-values.md) * Feature Extraction - * [Token Count Vectorizer](machine-learning/feature-extraction/token-count-vectorizer/) - * [Tf-idf Transformer](machine-learning/feature-extraction/tf-idf-transformer/) + * [Token Count Vectorizer](machine-learning/feature-extraction/token-count-vectorizer.md) + * [Tf-idf Transformer](machine-learning/feature-extraction/tf-idf-transformer.md) * Datasets - * [Array](machine-learning/datasets/array-dataset/) - * [CSV](machine-learning/datasets/csv-dataset/) - * [Files](machine-learning/datasets/files-dataset/) + * [Array](machine-learning/datasets/array-dataset.md) + * [CSV](machine-learning/datasets/csv-dataset.md) + * [Files](machine-learning/datasets/files-dataset.md) * Ready to use: - * [Iris](machine-learning/datasets/demo/iris/) - * [Wine](machine-learning/datasets/demo/wine/) - * [Glass](machine-learning/datasets/demo/glass/) + * [Iris](machine-learning/datasets/demo/iris.md) + * [Wine](machine-learning/datasets/demo/wine.md) + * [Glass](machine-learning/datasets/demo/glass.md) * Models management - * [Persistency](machine-learning/model-manager/persistency/) + * [Persistency](machine-learning/model-manager/persistency.md) * Math - * [Distance](math/distance/) - * [Matrix](math/matrix/) - * [Set](math/set/) - * [Statistic](math/statistic/) + * [Distance](math/distance.md) + * [Matrix](math/matrix.md) + * [Set](math/set.md) + * [Statistic](math/statistic.md) ## Contribute -- Issue Tracker: github.com/php-ai/php-ml/issues -- Source Code: github.com/php-ai/php-ml +- Issue Tracker: [github.com/php-ai/php-ml/issues](https://github.com/php-ai/php-ml/issues) +- Source Code: [github.com/php-ai/php-ml](https://github.com/php-ai/php-ml) -You can find more about contributing in [CONTRIBUTING.md](CONTRIBUTING.md). +You can find more about contributing in [CONTRIBUTING.md](../CONTRIBUTING.md). ## License diff --git a/easy-coding-standard.neon b/easy-coding-standard.neon index 14d87709..9155b106 100644 --- a/easy-coding-standard.neon +++ b/easy-coding-standard.neon @@ -11,6 +11,25 @@ includes: #- vendor/symplify/easy-coding-standard/config/common/strict.neon checkers: + # spacing + - PhpCsFixer\Fixer\PhpTag\BlankLineAfterOpeningTagFixer + - PhpCsFixer\Fixer\Whitespace\BlankLineBeforeStatementFixer + - PhpCsFixer\Fixer\CastNotation\CastSpacesFixer + PhpCsFixer\Fixer\Operator\ConcatSpaceFixer: + spacing: none + - PhpCsFixer\Fixer\ClassNotation\MethodSeparationFixer + - PhpCsFixer\Fixer\ClassNotation\NoBlankLinesAfterClassOpeningFixer + PhpCsFixer\Fixer\Whitespace\NoSpacesAroundOffsetFixer: + positions: ['inside', 'outside'] + PhpCsFixer\Fixer\Operator\BinaryOperatorSpacesFixer: + align_double_arrow: false + align_equals: false + + # phpdoc + - PhpCsFixer\Fixer\Phpdoc\PhpdocSeparationFixer + - PhpCsFixer\Fixer\Phpdoc\PhpdocAlignFixer + + # Symplify - Symplify\CodingStandard\Fixer\Import\ImportNamespacedNameFixer - Symplify\CodingStandard\Fixer\Php\ClassStringToClassConstantFixer - Symplify\CodingStandard\Fixer\Property\ArrayPropertyDefaultValueFixer diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 00000000..19e3c208 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,19 @@ +parameters: + ignoreErrors: + - '#Phpml\\Dataset\\FilesDataset::__construct\(\) does not call parent constructor from Phpml\\Dataset\\ArrayDataset#' + - '#Parameter \#2 \$predictedLabels of static method Phpml\\Metric\\Accuracy::score\(\) expects mixed\[\], mixed\[\]\|string given#' + + # should be always defined + - '#Undefined variable: \$j#' + + # bugged + - '#expects [a-z\\\|\[\]]*, [a-z\\\|\(\)\[\]]*\[\] given#' + + # mock + - '#Parameter \#1 \$node of class Phpml\\NeuralNetwork\\Node\\Neuron\\Synapse constructor expects Phpml\\NeuralNetwork\\Node, Phpml\\NeuralNetwork\\Node\\Neuron\|PHPUnit_Framework_MockObject_MockObject given#' + - '#Parameter \#1 \$(activationFunction|synapse) of class Phpml\\NeuralNetwork\\Node\\Neuron constructor expects Phpml\\NeuralNetwork\\ActivationFunction|null, Phpml\\NeuralNetwork\\ActivationFunction\\BinaryStep|PHPUnit_Framework_MockObject_MockObject given#' + + # probably known value + - '#Method Phpml\\Classification\\DecisionTree::getBestSplit\(\) should return Phpml\\Classification\\DecisionTree\\DecisionTreeLeaf but returns Phpml\\Classification\\DecisionTree\\DecisionTreeLeaf\|null#' + - '#Method Phpml\\Classification\\Linear\\DecisionStump::getBestNumericalSplit\(\) should return mixed\[\] but returns \(float\|int\|mixed\|string\)\[\]\|null#' + - '#Method Phpml\\Classification\\Linear\\DecisionStump::getBestNominalSplit\(\) should return mixed\[\] but returns mixed\[\]\|null#' diff --git a/src/Phpml/Association/Apriori.php b/src/Phpml/Association/Apriori.php index e13f556f..76d86245 100644 --- a/src/Phpml/Association/Apriori.php +++ b/src/Phpml/Association/Apriori.php @@ -244,6 +244,7 @@ private function candidates(array $samples): array foreach ((array) $this->samples as $sample) { if ($this->subset($sample, $candidate)) { $candidates[] = $candidate; + continue 2; } } diff --git a/src/Phpml/Classification/DecisionTree.php b/src/Phpml/Classification/DecisionTree.php index 5bb730b6..d6603224 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -59,14 +59,14 @@ class DecisionTree implements Classifier private $selectedFeatures = []; /** - * @var array + * @var array|null */ - private $featureImportances = null; + private $featureImportances; /** - * @var array + * @var array|null */ - private $columnNames = null; + private $columnNames; public function __construct(int $maxDepth = 10) { diff --git a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php index f3f9449c..51644728 100644 --- a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -9,7 +9,7 @@ class DecisionTreeLeaf { /** - * @var string + * @var string|int */ public $value; @@ -52,7 +52,7 @@ class DecisionTreeLeaf public $classValue = ''; /** - * @var bool + * @var bool|int */ public $isTerminal = false; diff --git a/src/Phpml/Classification/Ensemble/Bagging.php b/src/Phpml/Classification/Ensemble/Bagging.php index 6fa1ec80..a3d8e5e0 100644 --- a/src/Phpml/Classification/Ensemble/Bagging.php +++ b/src/Phpml/Classification/Ensemble/Bagging.php @@ -31,7 +31,7 @@ class Bagging implements Classifier protected $numClassifier; /** - * @var Classifier + * @var string */ protected $classifier = DecisionTree::class; diff --git a/src/Phpml/Classification/Linear/Adaline.php b/src/Phpml/Classification/Linear/Adaline.php index cda746f3..de2e1525 100644 --- a/src/Phpml/Classification/Linear/Adaline.php +++ b/src/Phpml/Classification/Linear/Adaline.php @@ -21,7 +21,7 @@ class Adaline extends Perceptron /** * Training type may be either 'Batch' or 'Online' learning * - * @var string + * @var string|int */ protected $trainingType; diff --git a/src/Phpml/Classification/Linear/DecisionStump.php b/src/Phpml/Classification/Linear/DecisionStump.php index 3f6eb586..439ea561 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -224,7 +224,7 @@ protected function getBestNominalSplit(array $samples, array $targets, int $col) foreach ($distinctVals as $val) { [$errorRate, $prob] = $this->calculateErrorRate($targets, $val, $operator, $values); - if ($split == null || $split['trainingErrorRate'] < $errorRate) { + if ($split === null || $split['trainingErrorRate'] < $errorRate) { $split = [ 'value' => $val, 'operator' => $operator, diff --git a/src/Phpml/Classification/Linear/LogisticRegression.php b/src/Phpml/Classification/Linear/LogisticRegression.php index 38181617..b5955873 100644 --- a/src/Phpml/Classification/Linear/LogisticRegression.php +++ b/src/Phpml/Classification/Linear/LogisticRegression.php @@ -119,19 +119,25 @@ public function setLambda(float $lambda): void * * @throws \Exception */ - protected function runTraining(array $samples, array $targets) + protected function runTraining(array $samples, array $targets): void { $callback = $this->getCostFunction(); switch ($this->trainingType) { case self::BATCH_TRAINING: - return $this->runGradientDescent($samples, $targets, $callback, true); + $this->runGradientDescent($samples, $targets, $callback, true); + + return; case self::ONLINE_TRAINING: - return $this->runGradientDescent($samples, $targets, $callback, false); + $this->runGradientDescent($samples, $targets, $callback, false); + + return; case self::CONJUGATE_GRAD_TRAINING: - return $this->runConjugateGradient($samples, $targets, $callback); + $this->runConjugateGradient($samples, $targets, $callback); + + return; default: throw new Exception('Logistic regression has invalid training type: %s.', $this->trainingType); @@ -143,7 +149,7 @@ protected function runTraining(array $samples, array $targets) */ protected function runConjugateGradient(array $samples, array $targets, Closure $gradientFunc): void { - if (empty($this->optimizer)) { + if ($this->optimizer === null) { $this->optimizer = (new ConjugateGradient($this->featureCount)) ->setMaxIterations($this->maxIterations); } diff --git a/src/Phpml/Classification/Linear/Perceptron.php b/src/Phpml/Classification/Linear/Perceptron.php index 0db7496d..68382276 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -19,7 +19,7 @@ class Perceptron implements Classifier, IncrementalEstimator use Predictable, OneVsRest; /** - * @var \Phpml\Helper\Optimizer\Optimizer + * @var \Phpml\Helper\Optimizer\Optimizer|GD|StochasticGD|null */ protected $optimizer; @@ -34,7 +34,7 @@ class Perceptron implements Classifier, IncrementalEstimator protected $featureCount = 0; /** - * @var array + * @var array|null */ protected $weights = []; @@ -67,8 +67,8 @@ class Perceptron implements Classifier, IncrementalEstimator * Initalize a perceptron classifier with given learning rate and maximum * number of iterations used while training the perceptron * - * @param float $learningRate Value between 0.0(exclusive) and 1.0(inclusive) - * @param int $maxIterations Must be at least 1 + * @param float $learningRate Value between 0.0(exclusive) and 1.0(inclusive) + * @param int $maxIterations Must be at least 1 * * @throws \Exception */ @@ -178,7 +178,7 @@ protected function runGradientDescent(array $samples, array $targets, Closure $g { $class = $isBatch ? GD::class : StochasticGD::class; - if (empty($this->optimizer)) { + if ($this->optimizer === null) { $this->optimizer = (new $class($this->featureCount)) ->setLearningRate($this->learningRate) ->setMaxIterations($this->maxIterations) diff --git a/src/Phpml/Clustering/KMeans/Point.php b/src/Phpml/Clustering/KMeans/Point.php index 6aa40a92..8c918a74 100644 --- a/src/Phpml/Clustering/KMeans/Point.php +++ b/src/Phpml/Clustering/KMeans/Point.php @@ -48,12 +48,15 @@ public function getDistanceWith(self $point, bool $precise = true) */ public function getClosest(array $points) { + $minPoint = null; + foreach ($points as $point) { $distance = $this->getDistanceWith($point, false); if (!isset($minDistance)) { $minDistance = $distance; $minPoint = $point; + continue; } diff --git a/src/Phpml/Clustering/KMeans/Space.php b/src/Phpml/Clustering/KMeans/Space.php index 0d4adf5a..371bbc31 100644 --- a/src/Phpml/Clustering/KMeans/Space.php +++ b/src/Phpml/Clustering/KMeans/Space.php @@ -45,7 +45,7 @@ public function newPoint(array $coordinates): Point } /** - * @param null $data + * @param null $data */ public function addPoint(array $coordinates, $data = null): void { @@ -124,10 +124,12 @@ protected function initializeClusters(int $clustersNumber, int $initMethod): arr switch ($initMethod) { case KMeans::INIT_RANDOM: $clusters = $this->initializeRandomClusters($clustersNumber); + break; case KMeans::INIT_KMEANS_PLUS_PLUS: $clusters = $this->initializeKMPPClusters($clustersNumber); + break; default: @@ -200,6 +202,7 @@ protected function initializeKMPPClusters(int $clustersNumber): array } $clusters[] = new Cluster($this, $point->getCoordinates()); + break; } } diff --git a/src/Phpml/DimensionReduction/KernelPCA.php b/src/Phpml/DimensionReduction/KernelPCA.php index 1981cb58..65bb324c 100644 --- a/src/Phpml/DimensionReduction/KernelPCA.php +++ b/src/Phpml/DimensionReduction/KernelPCA.php @@ -30,7 +30,7 @@ class KernelPCA extends PCA /** * Gamma value used by the kernel * - * @var float + * @var float|null */ protected $gamma; diff --git a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php index e0bd4024..e0d4e107 100644 --- a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php +++ b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php @@ -15,7 +15,7 @@ class TokenCountVectorizer implements Transformer private $tokenizer; /** - * @var StopWords + * @var StopWords|null */ private $stopWords; diff --git a/src/Phpml/Helper/OneVsRest.php b/src/Phpml/Helper/OneVsRest.php index 4f661ba5..1c0ca933 100644 --- a/src/Phpml/Helper/OneVsRest.php +++ b/src/Phpml/Helper/OneVsRest.php @@ -33,7 +33,7 @@ public function train(array $samples, array $targets): void // Clears previous stuff. $this->reset(); - $this->trainBylabel($samples, $targets); + $this->trainByLabel($samples, $targets); } /** diff --git a/src/Phpml/Helper/Optimizer/GD.php b/src/Phpml/Helper/Optimizer/GD.php index 4eadf281..11577c9d 100644 --- a/src/Phpml/Helper/Optimizer/GD.php +++ b/src/Phpml/Helper/Optimizer/GD.php @@ -15,9 +15,9 @@ class GD extends StochasticGD /** * Number of samples given * - * @var int + * @var int|null */ - protected $sampleCount = null; + protected $sampleCount; public function runOptimization(array $samples, array $targets, Closure $gradientCb): array { diff --git a/src/Phpml/Helper/Optimizer/StochasticGD.php b/src/Phpml/Helper/Optimizer/StochasticGD.php index 07ad216e..18a5f0ca 100644 --- a/src/Phpml/Helper/Optimizer/StochasticGD.php +++ b/src/Phpml/Helper/Optimizer/StochasticGD.php @@ -31,7 +31,7 @@ class StochasticGD extends Optimizer * Callback function to get the gradient and cost value * for a specific set of theta (ϴ) and a pair of sample & target * - * @var \Closure + * @var \Closure|null */ protected $gradientCb = null; @@ -144,7 +144,7 @@ public function setMaxIterations(int $maxIterations) * The cost function to minimize and the gradient of the function are to be * handled by the callback function provided as the third parameter of the method. */ - public function runOptimization(array $samples, array $targets, Closure $gradientCb): array + public function runOptimization(array $samples, array $targets, Closure $gradientCb): ?array { $this->samples = $samples; $this->targets = $targets; @@ -181,7 +181,7 @@ public function runOptimization(array $samples, array $targets, Closure $gradien // Solution in the pocket is better than or equal to the last state // so, we use this solution - return $this->theta = $bestTheta; + return $this->theta = (array) $bestTheta; } /** diff --git a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php index 4d7f662a..17303091 100644 --- a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -88,6 +88,8 @@ class EigenvalueDecomposition private $cdivi; + private $A; + /** * Constructor: Check for symmetry, then construct the eigenvalue decomposition */ diff --git a/src/Phpml/Math/LinearAlgebra/LUDecomposition.php b/src/Phpml/Math/LinearAlgebra/LUDecomposition.php index 6ebd8cbe..151e2ccd 100644 --- a/src/Phpml/Math/LinearAlgebra/LUDecomposition.php +++ b/src/Phpml/Math/LinearAlgebra/LUDecomposition.php @@ -212,9 +212,9 @@ public function getDoublePivot() /** * Is the matrix nonsingular? * - * @return true if U, and hence A, is nonsingular. + * @return bool true if U, and hence A, is nonsingular. */ - public function isNonsingular() + public function isNonsingular(): bool { for ($j = 0; $j < $this->n; ++$j) { if ($this->LU[$j][$j] == 0) { diff --git a/src/Phpml/Math/Set.php b/src/Phpml/Math/Set.php index a67d5c22..dcb02e69 100644 --- a/src/Phpml/Math/Set.php +++ b/src/Phpml/Math/Set.php @@ -15,7 +15,7 @@ class Set implements IteratorAggregate private $elements = []; /** - * @param string[]|int[]|float[] $elements + * @param string[]|int[]|float[]|bool[] $elements */ public function __construct(array $elements = []) { @@ -83,7 +83,7 @@ public static function power(self $a): array } /** - * @param string|int|float $element + * @param string|int|float|bool $element */ public function add($element): self { @@ -91,7 +91,7 @@ public function add($element): self } /** - * @param string[]|int[]|float[] $elements + * @param string[]|int[]|float[]|bool[] $elements */ public function addAll(array $elements): self { diff --git a/src/Phpml/Metric/ClassificationReport.php b/src/Phpml/Metric/ClassificationReport.php index 0f27b066..0c3198fd 100644 --- a/src/Phpml/Metric/ClassificationReport.php +++ b/src/Phpml/Metric/ClassificationReport.php @@ -91,6 +91,7 @@ private function computeAverage(): void $values = array_filter($this->{$metric}); if (empty($values)) { $this->average[$metric] = 0.0; + continue; } diff --git a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php index 5ace597c..1a997be2 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -26,14 +26,14 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, protected $classes = []; /** - * @var ActivationFunction + * @var ActivationFunction|null */ protected $activationFunction; /** * @var Backpropagation */ - protected $backpropagation = null; + protected $backpropagation; /** * @var int @@ -50,6 +50,11 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, */ private $learningRate; + /** + * @var int + */ + private $iterations; + /** * @throws InvalidArgumentException */ diff --git a/src/Phpml/NeuralNetwork/Node/Neuron.php b/src/Phpml/NeuralNetwork/Node/Neuron.php index a6c10e61..2dff6009 100644 --- a/src/Phpml/NeuralNetwork/Node/Neuron.php +++ b/src/Phpml/NeuralNetwork/Node/Neuron.php @@ -24,13 +24,11 @@ class Neuron implements Node /** * @var float */ - protected $output; + protected $output = 0.0; public function __construct(?ActivationFunction $activationFunction = null) { $this->activationFunction = $activationFunction ?: new Sigmoid(); - $this->synapses = []; - $this->output = 0; } public function addSynapse(Synapse $synapse): void @@ -48,8 +46,8 @@ public function getSynapses() public function getOutput(): float { - if ($this->output === 0) { - $sum = 0; + if ($this->output === 0.0) { + $sum = 0.0; foreach ($this->synapses as $synapse) { $sum += $synapse->getOutput(); } @@ -62,6 +60,6 @@ public function getOutput(): float public function reset(): void { - $this->output = 0; + $this->output = 0.0; } } diff --git a/src/Phpml/NeuralNetwork/Training/Backpropagation.php b/src/Phpml/NeuralNetwork/Training/Backpropagation.php index df515b21..4144c30e 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -15,14 +15,14 @@ class Backpropagation private $learningRate; /** - * @var array + * @var array|null */ - private $sigmas = null; + private $sigmas; /** - * @var array + * @var array|null */ - private $prevSigmas = null; + private $prevSigmas; public function __construct(float $learningRate) { diff --git a/src/Phpml/Preprocessing/Imputer.php b/src/Phpml/Preprocessing/Imputer.php index fdf8796f..593756c2 100644 --- a/src/Phpml/Preprocessing/Imputer.php +++ b/src/Phpml/Preprocessing/Imputer.php @@ -33,8 +33,7 @@ class Imputer implements Preprocessor private $samples = []; /** - * @param mixed $missingValue - * @param array|null $samples + * @param mixed $missingValue */ public function __construct($missingValue, Strategy $strategy, int $axis = self::AXIS_COLUMN, array $samples = []) { diff --git a/src/Phpml/SupportVectorMachine/DataTransformer.php b/src/Phpml/SupportVectorMachine/DataTransformer.php index 2ce938e6..6516cbfd 100644 --- a/src/Phpml/SupportVectorMachine/DataTransformer.php +++ b/src/Phpml/SupportVectorMachine/DataTransformer.php @@ -9,6 +9,8 @@ class DataTransformer public static function trainingSet(array $samples, array $labels, bool $targets = false): string { $set = ''; + $numericLabels = []; + if (!$targets) { $numericLabels = self::numericLabels($labels); } diff --git a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php index 2415edac..4565332e 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -37,7 +37,7 @@ class SupportVectorMachine private $degree; /** - * @var float + * @var float|null */ private $gamma; diff --git a/tests/Phpml/Association/AprioriTest.php b/tests/Phpml/Association/AprioriTest.php index 68456ffa..8b95237f 100644 --- a/tests/Phpml/Association/AprioriTest.php +++ b/tests/Phpml/Association/AprioriTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Classification; +namespace Phpml\Tests\Association; use Phpml\Association\Apriori; use Phpml\ModelManager; @@ -173,8 +173,8 @@ public function testEquals(): void /** * Invokes objects method. Private/protected will be set accessible. * - * @param string $method Method name to be called - * @param array $params Array of params to be passed + * @param string $method Method name to be called + * @param array $params Array of params to be passed * * @return mixed */ diff --git a/tests/Phpml/Classification/DecisionTree/DecisionTreeLeafTest.php b/tests/Phpml/Classification/DecisionTree/DecisionTreeLeafTest.php index 626ed649..abcf10da 100644 --- a/tests/Phpml/Classification/DecisionTree/DecisionTreeLeafTest.php +++ b/tests/Phpml/Classification/DecisionTree/DecisionTreeLeafTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Classification\DecisionTree; +namespace Phpml\Tests\Classification\DecisionTree; use Phpml\Classification\DecisionTree\DecisionTreeLeaf; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/Classification/DecisionTreeTest.php b/tests/Phpml/Classification/DecisionTreeTest.php index 8533500b..9478a2ac 100644 --- a/tests/Phpml/Classification/DecisionTreeTest.php +++ b/tests/Phpml/Classification/DecisionTreeTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Classification; +namespace Phpml\Tests\Classification; use Phpml\Classification\DecisionTree; use Phpml\ModelManager; diff --git a/tests/Phpml/Classification/Ensemble/AdaBoostTest.php b/tests/Phpml/Classification/Ensemble/AdaBoostTest.php index e01066c9..7677c319 100644 --- a/tests/Phpml/Classification/Ensemble/AdaBoostTest.php +++ b/tests/Phpml/Classification/Ensemble/AdaBoostTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Classification\Ensemble; +namespace Phpml\Tests\Classification\Ensemble; use Phpml\Classification\Ensemble\AdaBoost; use Phpml\ModelManager; diff --git a/tests/Phpml/Classification/Ensemble/BaggingTest.php b/tests/Phpml/Classification/Ensemble/BaggingTest.php index 175b79a7..69c4d010 100644 --- a/tests/Phpml/Classification/Ensemble/BaggingTest.php +++ b/tests/Phpml/Classification/Ensemble/BaggingTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Classification\Ensemble; +namespace Phpml\Tests\Classification\Ensemble; use Phpml\Classification\DecisionTree; use Phpml\Classification\Ensemble\Bagging; diff --git a/tests/Phpml/Classification/Ensemble/RandomForestTest.php b/tests/Phpml/Classification/Ensemble/RandomForestTest.php index f2871e3f..ea0cce12 100644 --- a/tests/Phpml/Classification/Ensemble/RandomForestTest.php +++ b/tests/Phpml/Classification/Ensemble/RandomForestTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Classification\Ensemble; +namespace Phpml\Tests\Classification\Ensemble; use Phpml\Classification\DecisionTree; use Phpml\Classification\Ensemble\RandomForest; @@ -21,6 +21,7 @@ public function testOtherBaseClassifier(): void $this->assertEquals(1, 1); } } + protected function getClassifier($numBaseClassifiers = 50) { $classifier = new RandomForest($numBaseClassifiers); diff --git a/tests/Phpml/Classification/KNearestNeighborsTest.php b/tests/Phpml/Classification/KNearestNeighborsTest.php index d9114b5f..d96152a4 100644 --- a/tests/Phpml/Classification/KNearestNeighborsTest.php +++ b/tests/Phpml/Classification/KNearestNeighborsTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Classification; +namespace Phpml\Tests\Classification; use Phpml\Classification\KNearestNeighbors; use Phpml\Math\Distance\Chebyshev; diff --git a/tests/Phpml/Classification/Linear/AdalineTest.php b/tests/Phpml/Classification/Linear/AdalineTest.php index 0a90a189..c8ca7529 100644 --- a/tests/Phpml/Classification/Linear/AdalineTest.php +++ b/tests/Phpml/Classification/Linear/AdalineTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Classification\Linear; +namespace Phpml\Tests\Classification\Linear; use Phpml\Classification\Linear\Adaline; use Phpml\ModelManager; diff --git a/tests/Phpml/Classification/Linear/DecisionStumpTest.php b/tests/Phpml/Classification/Linear/DecisionStumpTest.php index 7fbabec9..93c8595d 100644 --- a/tests/Phpml/Classification/Linear/DecisionStumpTest.php +++ b/tests/Phpml/Classification/Linear/DecisionStumpTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Classification\Linear; +namespace Phpml\Tests\Classification\Linear; use Phpml\Classification\Linear\DecisionStump; use Phpml\ModelManager; diff --git a/tests/Phpml/Classification/Linear/PerceptronTest.php b/tests/Phpml/Classification/Linear/PerceptronTest.php index 17b1db8b..35af855c 100644 --- a/tests/Phpml/Classification/Linear/PerceptronTest.php +++ b/tests/Phpml/Classification/Linear/PerceptronTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Classification\Linear; +namespace Phpml\Tests\Classification\Linear; use Phpml\Classification\Linear\Perceptron; use Phpml\ModelManager; diff --git a/tests/Phpml/Classification/MLPClassifierTest.php b/tests/Phpml/Classification/MLPClassifierTest.php index ef62d063..ef40618d 100644 --- a/tests/Phpml/Classification/MLPClassifierTest.php +++ b/tests/Phpml/Classification/MLPClassifierTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Classification; +namespace Phpml\Tests\Classification; use Phpml\Classification\MLPClassifier; use Phpml\Exception\InvalidArgumentException; diff --git a/tests/Phpml/Classification/NaiveBayesTest.php b/tests/Phpml/Classification/NaiveBayesTest.php index c423c6db..8312e9ca 100644 --- a/tests/Phpml/Classification/NaiveBayesTest.php +++ b/tests/Phpml/Classification/NaiveBayesTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Classification; +namespace Phpml\Tests\Classification; use Phpml\Classification\NaiveBayes; use Phpml\ModelManager; diff --git a/tests/Phpml/Classification/SVCTest.php b/tests/Phpml/Classification/SVCTest.php index 0941b346..0709e047 100644 --- a/tests/Phpml/Classification/SVCTest.php +++ b/tests/Phpml/Classification/SVCTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Classification; +namespace Phpml\Tests\Classification; use Phpml\Classification\SVC; use Phpml\ModelManager; diff --git a/tests/Phpml/Clustering/DBSCANTest.php b/tests/Phpml/Clustering/DBSCANTest.php index f12fb036..c0d0401b 100644 --- a/tests/Phpml/Clustering/DBSCANTest.php +++ b/tests/Phpml/Clustering/DBSCANTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Clustering; +namespace Phpml\Tests\Clustering; use Phpml\Clustering\DBSCAN; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/Clustering/FuzzyCMeansTest.php b/tests/Phpml/Clustering/FuzzyCMeansTest.php index 7b194220..1c3af15d 100644 --- a/tests/Phpml/Clustering/FuzzyCMeansTest.php +++ b/tests/Phpml/Clustering/FuzzyCMeansTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Clustering; +namespace Phpml\Tests\Clustering; use Phpml\Clustering\FuzzyCMeans; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/Clustering/KMeansTest.php b/tests/Phpml/Clustering/KMeansTest.php index d212157d..dedf9814 100644 --- a/tests/Phpml/Clustering/KMeansTest.php +++ b/tests/Phpml/Clustering/KMeansTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Clustering; +namespace Phpml\Tests\Clustering; use Phpml\Clustering\KMeans; use Phpml\Exception\InvalidArgumentException; diff --git a/tests/Phpml/CrossValidation/RandomSplitTest.php b/tests/Phpml/CrossValidation/RandomSplitTest.php index 8058fd6f..75f60ff5 100644 --- a/tests/Phpml/CrossValidation/RandomSplitTest.php +++ b/tests/Phpml/CrossValidation/RandomSplitTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\CrossValidation; +namespace Phpml\Tests\CrossValidation; use Phpml\CrossValidation\RandomSplit; use Phpml\Dataset\ArrayDataset; diff --git a/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php b/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php index e953ca7f..5309dc69 100644 --- a/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php +++ b/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\CrossValidation; +namespace Phpml\Tests\CrossValidation; use Phpml\CrossValidation\StratifiedRandomSplit; use Phpml\Dataset\ArrayDataset; diff --git a/tests/Phpml/Dataset/ArrayDatasetTest.php b/tests/Phpml/Dataset/ArrayDatasetTest.php index e0a6b91e..bca9e434 100644 --- a/tests/Phpml/Dataset/ArrayDatasetTest.php +++ b/tests/Phpml/Dataset/ArrayDatasetTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Dataset; +namespace Phpml\Tests\Dataset; use Phpml\Dataset\ArrayDataset; use Phpml\Exception\InvalidArgumentException; diff --git a/tests/Phpml/Dataset/CsvDatasetTest.php b/tests/Phpml/Dataset/CsvDatasetTest.php index 50492537..90eb1d09 100644 --- a/tests/Phpml/Dataset/CsvDatasetTest.php +++ b/tests/Phpml/Dataset/CsvDatasetTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Dataset; +namespace Phpml\Tests\Dataset; use Phpml\Dataset\CsvDataset; use Phpml\Exception\FileException; diff --git a/tests/Phpml/Dataset/Demo/GlassDatasetTest.php b/tests/Phpml/Dataset/Demo/GlassDatasetTest.php index 8feef452..0d873e65 100644 --- a/tests/Phpml/Dataset/Demo/GlassDatasetTest.php +++ b/tests/Phpml/Dataset/Demo/GlassDatasetTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Dataset\Demo; +namespace Phpml\Tests\Dataset\Demo; use Phpml\Dataset\Demo\GlassDataset; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/Dataset/Demo/IrisDatasetTest.php b/tests/Phpml/Dataset/Demo/IrisDatasetTest.php index faa48c67..4fb2ee23 100644 --- a/tests/Phpml/Dataset/Demo/IrisDatasetTest.php +++ b/tests/Phpml/Dataset/Demo/IrisDatasetTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Dataset\Demo; +namespace Phpml\Tests\Dataset\Demo; use Phpml\Dataset\Demo\IrisDataset; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/Dataset/Demo/WineDatasetTest.php b/tests/Phpml/Dataset/Demo/WineDatasetTest.php index e0324b41..1b7a982f 100644 --- a/tests/Phpml/Dataset/Demo/WineDatasetTest.php +++ b/tests/Phpml/Dataset/Demo/WineDatasetTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Dataset\Demo; +namespace Phpml\Tests\Dataset\Demo; use Phpml\Dataset\Demo\WineDataset; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/Dataset/FilesDatasetTest.php b/tests/Phpml/Dataset/FilesDatasetTest.php index ee08395a..08b568d9 100644 --- a/tests/Phpml/Dataset/FilesDatasetTest.php +++ b/tests/Phpml/Dataset/FilesDatasetTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Dataset; +namespace Phpml\Tests\Dataset; use Phpml\Dataset\FilesDataset; use Phpml\Exception\DatasetException; diff --git a/tests/Phpml/DimensionReduction/KernelPCATest.php b/tests/Phpml/DimensionReduction/KernelPCATest.php index 3f37e3c0..05a4138e 100644 --- a/tests/Phpml/DimensionReduction/KernelPCATest.php +++ b/tests/Phpml/DimensionReduction/KernelPCATest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\DimensionReduction; +namespace Phpml\Tests\DimensionReduction; use Phpml\DimensionReduction\KernelPCA; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/DimensionReduction/LDATest.php b/tests/Phpml/DimensionReduction/LDATest.php index 42a02836..2803a4b4 100644 --- a/tests/Phpml/DimensionReduction/LDATest.php +++ b/tests/Phpml/DimensionReduction/LDATest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\DimensionReduction; +namespace Phpml\Tests\DimensionReduction; use Phpml\Dataset\Demo\IrisDataset; use Phpml\DimensionReduction\LDA; diff --git a/tests/Phpml/DimensionReduction/PCATest.php b/tests/Phpml/DimensionReduction/PCATest.php index 38b47444..337b2533 100644 --- a/tests/Phpml/DimensionReduction/PCATest.php +++ b/tests/Phpml/DimensionReduction/PCATest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\DimensionReduction; +namespace Phpml\Tests\DimensionReduction; use Phpml\DimensionReduction\PCA; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/FeatureExtraction/StopWordsTest.php b/tests/Phpml/FeatureExtraction/StopWordsTest.php index 6d97a239..5780c393 100644 --- a/tests/Phpml/FeatureExtraction/StopWordsTest.php +++ b/tests/Phpml/FeatureExtraction/StopWordsTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\FeatureExtraction; +namespace Phpml\Tests\FeatureExtraction; use Phpml\Exception\InvalidArgumentException; use Phpml\FeatureExtraction\StopWords; diff --git a/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php b/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php index a8faacb5..30bc0e57 100644 --- a/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php +++ b/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\FeatureExtraction; +namespace Phpml\Tests\FeatureExtraction; use Phpml\FeatureExtraction\TfIdfTransformer; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php index 463570cf..aaba5fc1 100644 --- a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php +++ b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\FeatureExtraction; +namespace Phpml\Tests\FeatureExtraction; use Phpml\FeatureExtraction\StopWords; use Phpml\FeatureExtraction\TokenCountVectorizer; diff --git a/tests/Phpml/Math/ComparisonTest.php b/tests/Phpml/Math/ComparisonTest.php index ecb58c2e..0118ee46 100644 --- a/tests/Phpml/Math/ComparisonTest.php +++ b/tests/Phpml/Math/ComparisonTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Math; +namespace Phpml\Tests\Math; use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Comparison; @@ -11,8 +11,8 @@ class ComparisonTest extends TestCase { /** - * @param mixed $a - * @param mixed $b + * @param mixed $a + * @param mixed $b * * @dataProvider provideData */ diff --git a/tests/Phpml/Math/Distance/ChebyshevTest.php b/tests/Phpml/Math/Distance/ChebyshevTest.php index 56d6685f..262927b6 100644 --- a/tests/Phpml/Math/Distance/ChebyshevTest.php +++ b/tests/Phpml/Math/Distance/ChebyshevTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Metric; +namespace Phpml\Tests\Math\Distance; use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Distance\Chebyshev; diff --git a/tests/Phpml/Math/Distance/EuclideanTest.php b/tests/Phpml/Math/Distance/EuclideanTest.php index 4acb3d4c..734bbd2c 100644 --- a/tests/Phpml/Math/Distance/EuclideanTest.php +++ b/tests/Phpml/Math/Distance/EuclideanTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Metric; +namespace Phpml\Tests\Math\Distance; use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Distance\Euclidean; diff --git a/tests/Phpml/Math/Distance/ManhattanTest.php b/tests/Phpml/Math/Distance/ManhattanTest.php index 2a058740..2eb9f06d 100644 --- a/tests/Phpml/Math/Distance/ManhattanTest.php +++ b/tests/Phpml/Math/Distance/ManhattanTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Metric; +namespace Phpml\Tests\Math\Distance; use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Distance\Manhattan; diff --git a/tests/Phpml/Math/Distance/MinkowskiTest.php b/tests/Phpml/Math/Distance/MinkowskiTest.php index a8159d7b..6c7b8975 100644 --- a/tests/Phpml/Math/Distance/MinkowskiTest.php +++ b/tests/Phpml/Math/Distance/MinkowskiTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Metric; +namespace Phpml\Tests\Math\Distance; use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Distance\Minkowski; diff --git a/tests/Phpml/Math/Kernel/RBFTest.php b/tests/Phpml/Math/Kernel/RBFTest.php index 3ed7017d..08f795d8 100644 --- a/tests/Phpml/Math/Kernel/RBFTest.php +++ b/tests/Phpml/Math/Kernel/RBFTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace test\Phpml\Math\Kernel; +namespace Phpml\Tests\Math\Kernel; use Phpml\Math\Kernel\RBF; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php b/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php index 39568301..47c47988 100644 --- a/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php +++ b/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Math\LinearAlgebra; +namespace Phpml\Tests\Math\LinearAlgebra; use Phpml\Math\LinearAlgebra\EigenvalueDecomposition; use Phpml\Math\Matrix; diff --git a/tests/Phpml/Math/MatrixTest.php b/tests/Phpml/Math/MatrixTest.php index fce83bff..da535bfe 100644 --- a/tests/Phpml/Math/MatrixTest.php +++ b/tests/Phpml/Math/MatrixTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Math; +namespace Phpml\Tests\Math; use Phpml\Exception\InvalidArgumentException; use Phpml\Exception\MatrixException; diff --git a/tests/Phpml/Math/ProductTest.php b/tests/Phpml/Math/ProductTest.php index bd55d112..da7450b9 100644 --- a/tests/Phpml/Math/ProductTest.php +++ b/tests/Phpml/Math/ProductTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Math; +namespace Phpml\Tests\Math; use Phpml\Math\Product; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/Math/SetTest.php b/tests/Phpml/Math/SetTest.php index 645e6076..2335c73a 100644 --- a/tests/Phpml/Math/SetTest.php +++ b/tests/Phpml/Math/SetTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Math; +namespace Phpml\Tests\Math; use Phpml\Math\Set; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/Math/Statistic/CorrelationTest.php b/tests/Phpml/Math/Statistic/CorrelationTest.php index c091bb2e..eebb0652 100644 --- a/tests/Phpml/Math/Statistic/CorrelationTest.php +++ b/tests/Phpml/Math/Statistic/CorrelationTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace test\Phpml\Math\StandardDeviation; +namespace Phpml\Tests\Math\Statistic; use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Statistic\Correlation; diff --git a/tests/Phpml/Math/Statistic/CovarianceTest.php b/tests/Phpml/Math/Statistic/CovarianceTest.php index 97ec1941..2b648545 100644 --- a/tests/Phpml/Math/Statistic/CovarianceTest.php +++ b/tests/Phpml/Math/Statistic/CovarianceTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Math\Statistic; +namespace Phpml\Tests\Math\Statistic; use Phpml\Math\Statistic\Covariance; use Phpml\Math\Statistic\Mean; diff --git a/tests/Phpml/Math/Statistic/GaussianTest.php b/tests/Phpml/Math/Statistic/GaussianTest.php index 8030f859..e4627f44 100644 --- a/tests/Phpml/Math/Statistic/GaussianTest.php +++ b/tests/Phpml/Math/Statistic/GaussianTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace test\Phpml\Math\StandardDeviation; +namespace Phpml\Tests\Math\Statistic; use Phpml\Math\Statistic\Gaussian; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/Math/Statistic/MeanTest.php b/tests/Phpml/Math/Statistic/MeanTest.php index f19479a9..3b980326 100644 --- a/tests/Phpml/Math/Statistic/MeanTest.php +++ b/tests/Phpml/Math/Statistic/MeanTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace test\Phpml\Math\StandardDeviation; +namespace Phpml\Tests\Math\Statistic; use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Statistic\Mean; diff --git a/tests/Phpml/Math/Statistic/StandardDeviationTest.php b/tests/Phpml/Math/Statistic/StandardDeviationTest.php index 4bc43928..8333740c 100644 --- a/tests/Phpml/Math/Statistic/StandardDeviationTest.php +++ b/tests/Phpml/Math/Statistic/StandardDeviationTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace test\Phpml\Math\StandardDeviation; +namespace Phpml\Tests\Math\Statistic; use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Statistic\StandardDeviation; diff --git a/tests/Phpml/Metric/AccuracyTest.php b/tests/Phpml/Metric/AccuracyTest.php index e876da19..08d1c44a 100644 --- a/tests/Phpml/Metric/AccuracyTest.php +++ b/tests/Phpml/Metric/AccuracyTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Metric; +namespace Phpml\Tests\Metric; use Phpml\Classification\SVC; use Phpml\CrossValidation\RandomSplit; diff --git a/tests/Phpml/Metric/ClassificationReportTest.php b/tests/Phpml/Metric/ClassificationReportTest.php index fb3471ae..483f7696 100644 --- a/tests/Phpml/Metric/ClassificationReportTest.php +++ b/tests/Phpml/Metric/ClassificationReportTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Metric; +namespace Phpml\Tests\Metric; use Phpml\Metric\ClassificationReport; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/Metric/ConfusionMatrixTest.php b/tests/Phpml/Metric/ConfusionMatrixTest.php index f958bdff..590aff89 100644 --- a/tests/Phpml/Metric/ConfusionMatrixTest.php +++ b/tests/Phpml/Metric/ConfusionMatrixTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Metric; +namespace Phpml\Tests\Metric; use Phpml\Metric\ConfusionMatrix; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/ModelManagerTest.php b/tests/Phpml/ModelManagerTest.php index f47efb73..48ab7479 100644 --- a/tests/Phpml/ModelManagerTest.php +++ b/tests/Phpml/ModelManagerTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests; +namespace Phpml\Tests; use Phpml\Exception\FileException; use Phpml\ModelManager; diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php index 91fc1c7f..acc7977c 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\NeuralNetwork\ActivationFunction; +namespace Phpml\Tests\NeuralNetwork\ActivationFunction; use Phpml\NeuralNetwork\ActivationFunction\BinaryStep; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php index 58b4b87f..2b087937 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\NeuralNetwork\ActivationFunction; +namespace Phpml\Tests\NeuralNetwork\ActivationFunction; use Phpml\NeuralNetwork\ActivationFunction\Gaussian; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php index 00348d90..91e7eba1 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\NeuralNetwork\ActivationFunction; +namespace Phpml\Tests\NeuralNetwork\ActivationFunction; use Phpml\NeuralNetwork\ActivationFunction\HyperbolicTangent; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php index 7e8e7189..f4070605 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\NeuralNetwork\ActivationFunction; +namespace Phpml\Tests\NeuralNetwork\ActivationFunction; use Phpml\NeuralNetwork\ActivationFunction\PReLU; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php index d5a0ea33..30b50f86 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\NeuralNetwork\ActivationFunction; +namespace Phpml\Tests\NeuralNetwork\ActivationFunction; use Phpml\NeuralNetwork\ActivationFunction\Sigmoid; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php index 19a0312f..f46ff024 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\NeuralNetwork\ActivationFunction; +namespace Phpml\Tests\NeuralNetwork\ActivationFunction; use Phpml\NeuralNetwork\ActivationFunction\ThresholdedReLU; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/NeuralNetwork/LayerTest.php b/tests/Phpml/NeuralNetwork/LayerTest.php index 11f5e6a0..baa3f9a3 100644 --- a/tests/Phpml/NeuralNetwork/LayerTest.php +++ b/tests/Phpml/NeuralNetwork/LayerTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\NeuralNetwork; +namespace Phpml\Tests\NeuralNetwork; use Phpml\Exception\InvalidArgumentException; use Phpml\NeuralNetwork\Layer; diff --git a/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php b/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php index c1779b80..21f5e9cf 100644 --- a/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php +++ b/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php @@ -2,12 +2,13 @@ declare(strict_types=1); -namespace tests\Phpml\NeuralNetwork\Network; +namespace Phpml\Tests\NeuralNetwork\Network; use Phpml\NeuralNetwork\Layer; use Phpml\NeuralNetwork\Network\LayeredNetwork; use Phpml\NeuralNetwork\Node\Input; use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject; class LayeredNetworkTest extends TestCase { @@ -44,7 +45,10 @@ public function testSetInputAndGetOutput(): void $this->assertEquals([0.5], $network->getOutput()); } - private function getLayeredNetworkMock(): LayeredNetwork + /** + * @return LayeredNetwork|PHPUnit_Framework_MockObject_MockObject + */ + private function getLayeredNetworkMock() { return $this->getMockForAbstractClass(LayeredNetwork::class); } diff --git a/tests/Phpml/NeuralNetwork/Node/BiasTest.php b/tests/Phpml/NeuralNetwork/Node/BiasTest.php index ee311f5a..e42a737c 100644 --- a/tests/Phpml/NeuralNetwork/Node/BiasTest.php +++ b/tests/Phpml/NeuralNetwork/Node/BiasTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\NeuralNetwork\Node; +namespace Phpml\Tests\NeuralNetwork\Node; use Phpml\NeuralNetwork\Node\Bias; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/NeuralNetwork/Node/InputTest.php b/tests/Phpml/NeuralNetwork/Node/InputTest.php index 2d3be71f..09ca8319 100644 --- a/tests/Phpml/NeuralNetwork/Node/InputTest.php +++ b/tests/Phpml/NeuralNetwork/Node/InputTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\NeuralNetwork\Node; +namespace Phpml\Tests\NeuralNetwork\Node; use Phpml\NeuralNetwork\Node\Input; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php b/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php index 1c09eae3..973d2161 100644 --- a/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php +++ b/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\NeuralNetwork\Node\Neuron; +namespace Phpml\Tests\NeuralNetwork\Node\Neuron; use Phpml\NeuralNetwork\Node\Neuron; use Phpml\NeuralNetwork\Node\Neuron\Synapse; @@ -41,8 +41,10 @@ public function testSynapseWeightChange(): void /** * @param int|float $output + * + * @return Neuron|PHPUnit_Framework_MockObject_MockObject */ - private function getNodeMock($output = 1): PHPUnit_Framework_MockObject_MockObject + private function getNodeMock($output = 1) { $node = $this->getMockBuilder(Neuron::class)->getMock(); $node->method('getOutput')->willReturn($output); diff --git a/tests/Phpml/NeuralNetwork/Node/NeuronTest.php b/tests/Phpml/NeuralNetwork/Node/NeuronTest.php index a58f2ec9..03e309d1 100644 --- a/tests/Phpml/NeuralNetwork/Node/NeuronTest.php +++ b/tests/Phpml/NeuralNetwork/Node/NeuronTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\NeuralNetwork\Node; +namespace Phpml\Tests\NeuralNetwork\Node; use Phpml\NeuralNetwork\ActivationFunction\BinaryStep; use Phpml\NeuralNetwork\Node\Neuron; @@ -22,6 +22,7 @@ public function testNeuronInitialization(): void public function testNeuronActivationFunction(): void { + /** @var BinaryStep|PHPUnit_Framework_MockObject_MockObject $activationFunction */ $activationFunction = $this->getMockBuilder(BinaryStep::class)->getMock(); $activationFunction->method('compute')->with(0)->willReturn($output = 0.69); diff --git a/tests/Phpml/PipelineTest.php b/tests/Phpml/PipelineTest.php index caf1961b..e45675d4 100644 --- a/tests/Phpml/PipelineTest.php +++ b/tests/Phpml/PipelineTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests; +namespace Phpml\Tests; use Phpml\Classification\SVC; use Phpml\FeatureExtraction\TfIdfTransformer; diff --git a/tests/Phpml/Preprocessing/ImputerTest.php b/tests/Phpml/Preprocessing/ImputerTest.php index 658b4545..c229c151 100644 --- a/tests/Phpml/Preprocessing/ImputerTest.php +++ b/tests/Phpml/Preprocessing/ImputerTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Preprocessing; +namespace Phpml\Tests\Preprocessing; use Phpml\Preprocessing\Imputer; use Phpml\Preprocessing\Imputer\Strategy\MeanStrategy; diff --git a/tests/Phpml/Preprocessing/NormalizerTest.php b/tests/Phpml/Preprocessing/NormalizerTest.php index 5dfc2076..ea762da0 100644 --- a/tests/Phpml/Preprocessing/NormalizerTest.php +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Preprocessing; +namespace Phpml\Tests\Preprocessing; use Phpml\Exception\NormalizerException; use Phpml\Preprocessing\Normalizer; diff --git a/tests/Phpml/Regression/LeastSquaresTest.php b/tests/Phpml/Regression/LeastSquaresTest.php index 7517a9b9..71215bc6 100644 --- a/tests/Phpml/Regression/LeastSquaresTest.php +++ b/tests/Phpml/Regression/LeastSquaresTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Regression; +namespace Phpml\Tests\Regression; use Phpml\ModelManager; use Phpml\Regression\LeastSquares; diff --git a/tests/Phpml/Regression/SVRTest.php b/tests/Phpml/Regression/SVRTest.php index a220d211..d2fdefbb 100644 --- a/tests/Phpml/Regression/SVRTest.php +++ b/tests/Phpml/Regression/SVRTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Regression; +namespace Phpml\Tests\Regression; use Phpml\ModelManager; use Phpml\Regression\SVR; diff --git a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php b/tests/Phpml/SupportVectorMachine/DataTransformerTest.php index b1f85229..1db1fdf8 100644 --- a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php +++ b/tests/Phpml/SupportVectorMachine/DataTransformerTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\SupportVectorMachine; +namespace Phpml\Tests\SupportVectorMachine; use Phpml\SupportVectorMachine\DataTransformer; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php index 59154e36..180b8d33 100644 --- a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\SupportVectorMachine; +namespace Phpml\Tests\SupportVectorMachine; use Phpml\Exception\InvalidArgumentException; use Phpml\SupportVectorMachine\Kernel; diff --git a/tests/Phpml/Tokenization/WhitespaceTokenizerTest.php b/tests/Phpml/Tokenization/WhitespaceTokenizerTest.php index f9d7c731..b9e40c0d 100644 --- a/tests/Phpml/Tokenization/WhitespaceTokenizerTest.php +++ b/tests/Phpml/Tokenization/WhitespaceTokenizerTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Tokenization; +namespace Phpml\Tests\Tokenization; use Phpml\Tokenization\WhitespaceTokenizer; use PHPUnit\Framework\TestCase; diff --git a/tests/Phpml/Tokenization/WordTokenizerTest.php b/tests/Phpml/Tokenization/WordTokenizerTest.php index 607c327f..d18edb6e 100644 --- a/tests/Phpml/Tokenization/WordTokenizerTest.php +++ b/tests/Phpml/Tokenization/WordTokenizerTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Tokenization; +namespace Phpml\Tests\Tokenization; use Phpml\Tokenization\WordTokenizer; use PHPUnit\Framework\TestCase; From 6660645ecdeb35a02e6ddd7b5bf997fafdead994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Votruba?= Date: Sat, 6 Jan 2018 21:25:47 +0100 Subject: [PATCH 217/328] Update dev dependencies (#187) * composer: update dev dependencies * phpstan fixes * phpstan fixes * phpstan fixes * phpstan fixes * drop probably forgotten humbug configs * apply cs * fix cs bug * compsoer: add coding standard and phsptan dev friendly scripts * ecs: add skipped errors * cs: fix PHP 7.1 * fix cs * ecs: exclude strict fixer that break code * ecs: cleanup commented sets * travis: use composer scripts for testing to prevent duplicated setup --- .gitignore | 1 - .travis.yml | 4 +- composer.json | 14 +- composer.lock | 738 +++++++++--------- easy-coding-standard.neon | 25 +- humbug.json.dist | 11 - phpstan.neon | 18 +- src/Phpml/Classification/DecisionTree.php | 10 +- .../DecisionTree/DecisionTreeLeaf.php | 8 +- .../Classification/Ensemble/RandomForest.php | 4 +- .../Classification/Linear/DecisionStump.php | 10 +- .../Classification/Linear/Perceptron.php | 6 +- src/Phpml/Classification/NaiveBayes.php | 2 +- src/Phpml/Clustering/FuzzyCMeans.php | 2 +- src/Phpml/DimensionReduction/KernelPCA.php | 6 +- src/Phpml/FeatureExtraction/StopWords.php | 2 +- src/Phpml/Helper/OneVsRest.php | 2 +- .../Helper/Optimizer/ConjugateGradient.php | 2 +- src/Phpml/Helper/Optimizer/Optimizer.php | 2 +- src/Phpml/Math/Set.php | 8 +- .../Training/Backpropagation.php | 12 +- src/Phpml/Regression/LeastSquares.php | 1 + .../Linear/LogisticRegressionTest.php | 12 +- tests/Phpml/Metric/AccuracyTest.php | 2 +- .../Network/MultilayerPerceptronTest.php | 3 +- 25 files changed, 454 insertions(+), 451 deletions(-) delete mode 100644 humbug.json.dist diff --git a/.gitignore b/.gitignore index 38ef5a0b..3ffb1da1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ /vendor/ -humbuglog.* .php_cs.cache /build diff --git a/.travis.yml b/.travis.yml index bf26816e..f415daf7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,8 +30,8 @@ install: script: - vendor/bin/phpunit $PHPUNIT_FLAGS - - if [[ $STATIC_ANALYSIS != "" ]]; then vendor/bin/ecs check src tests; fi - - if [[ $STATIC_ANALYSIS != "" ]]; then vendor/bin/phpstan.phar analyse src tests --level max --configuration phpstan.neon; fi + - if [[ $STATIC_ANALYSIS != "" ]]; then composer check-cs; fi + - if [[ $STATIC_ANALYSIS != "" ]]; then composer phpstan; fi after_success: - | diff --git a/composer.json b/composer.json index b08d6f8f..149ce27d 100644 --- a/composer.json +++ b/composer.json @@ -15,11 +15,10 @@ "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^6.4", - "symplify/easy-coding-standard": "v3.0.0-RC3", - "symplify/coding-standard": "v3.0.0-RC3", - "symplify/package-builder": "v3.0.0-RC3", - "phpstan/phpstan-shim": "^0.8" + "phpunit/phpunit": "^6.5", + "symplify/easy-coding-standard": "^3.1", + "symplify/coding-standard": "^3.1", + "phpstan/phpstan-shim": "^0.9" }, "autoload": { "psr-4": { @@ -30,5 +29,10 @@ "psr-4": { "Phpml\\Tests\\": "tests/Phpml" } + }, + "scripts": { + "check-cs": "vendor/bin/ecs check src tests bin", + "fix-cs": "vendor/bin/ecs check src tests bin --fix", + "phpstan": "vendor/bin/phpstan.phar analyse src tests bin --level max --configuration phpstan.neon" } } diff --git a/composer.lock b/composer.lock index 4236a2d8..93187863 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "032ab1160f58aff496453a86648f7012", + "content-hash": "1efc0df70ee999e80ff6a3770fd3b6c0", "packages": [], "packages-dev": [ { @@ -71,16 +71,16 @@ }, { "name": "doctrine/annotations", - "version": "v1.5.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "5beebb01b025c94e93686b7a0ed3edae81fe3e7f" + "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/5beebb01b025c94e93686b7a0ed3edae81fe3e7f", - "reference": "5beebb01b025c94e93686b7a0ed3edae81fe3e7f", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", + "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", "shasum": "" }, "require": { @@ -89,12 +89,12 @@ }, "require-dev": { "doctrine/cache": "1.*", - "phpunit/phpunit": "^5.7" + "phpunit/phpunit": "^6.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.5.x-dev" + "dev-master": "1.6.x-dev" } }, "autoload": { @@ -135,7 +135,7 @@ "docblock", "parser" ], - "time": "2017-07-22T10:58:02+00:00" + "time": "2017-12-06T07:11:42+00:00" }, { "name": "doctrine/instantiator", @@ -247,16 +247,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v2.8.2", + "version": "v2.9.0", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "b331701944cbe492e466d2b46b2880068803eb08" + "reference": "454ddbe65da6a9297446f442bad244e8a99a9a38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/b331701944cbe492e466d2b46b2880068803eb08", - "reference": "b331701944cbe492e466d2b46b2880068803eb08", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/454ddbe65da6a9297446f442bad244e8a99a9a38", + "reference": "454ddbe65da6a9297446f442bad244e8a99a9a38", "shasum": "" }, "require": { @@ -283,7 +283,8 @@ "require-dev": { "johnkary/phpunit-speedtrap": "^1.1 || ^2.0@dev", "justinrainbow/json-schema": "^5.0", - "php-coveralls/php-coveralls": "^1.0.2", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.0", "php-cs-fixer/accessible-object": "^1.0", "phpunit/phpunit": "^5.7.23 || ^6.4.3", "symfony/phpunit-bridge": "^3.2.2 || ^4.0" @@ -323,7 +324,7 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2017-11-19T22:51:25+00:00" + "time": "2017-12-08T16:36:20+00:00" }, { "name": "gecko-packages/gecko-php-unit", @@ -1127,29 +1128,35 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "4.1.1", + "version": "4.2.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "2d3d238c433cf69caeb4842e97a3223a116f94b2" + "reference": "66465776cfc249844bde6d117abff1d22e06c2da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/2d3d238c433cf69caeb4842e97a3223a116f94b2", - "reference": "2d3d238c433cf69caeb4842e97a3223a116f94b2", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/66465776cfc249844bde6d117abff1d22e06c2da", + "reference": "66465776cfc249844bde6d117abff1d22e06c2da", "shasum": "" }, "require": { "php": "^7.0", - "phpdocumentor/reflection-common": "^1.0@dev", + "phpdocumentor/reflection-common": "^1.0.0", "phpdocumentor/type-resolver": "^0.4.0", "webmozart/assert": "^1.0" }, "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^4.4" + "doctrine/instantiator": "~1.0.5", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.4" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, "autoload": { "psr-4": { "phpDocumentor\\Reflection\\": [ @@ -1168,7 +1175,7 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2017-08-30T18:51:59+00:00" + "time": "2017-11-27T17:38:31+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -1282,31 +1289,32 @@ }, { "name": "phpstan/phpstan-shim", - "version": "0.8.5", + "version": "0.9.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-shim.git", - "reference": "0b174a61fd99dea61f15ea6bd3bc424389f273d4" + "reference": "e3bea4f40f14316cf76390e7fd58181dca840977" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-shim/zipball/0b174a61fd99dea61f15ea6bd3bc424389f273d4", - "reference": "0b174a61fd99dea61f15ea6bd3bc424389f273d4", + "url": "https://api.github.com/repos/phpstan/phpstan-shim/zipball/e3bea4f40f14316cf76390e7fd58181dca840977", + "reference": "e3bea4f40f14316cf76390e7fd58181dca840977", "shasum": "" }, "require": { "php": "~7.0" }, - "provide": { - "phpstan/phpstan": "0.8" + "replace": { + "phpstan/phpstan": "self.version" }, "bin": [ + "phpstan", "phpstan.phar" ], "type": "library", "extra": { "branch-alias": { - "dev-master": "0.8-dev" + "dev-master": "0.9-dev" } }, "notification-url": "https://packagist.org/downloads/", @@ -1314,20 +1322,20 @@ "MIT" ], "description": "PHPStan Phar distribution", - "time": "2017-10-24T04:16:00+00:00" + "time": "2017-12-02T20:14:45+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "5.2.3", + "version": "5.3.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "8e1d2397d8adf59a3f12b2878a3aaa66d1ab189d" + "reference": "661f34d0bd3f1a7225ef491a70a020ad23a057a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/8e1d2397d8adf59a3f12b2878a3aaa66d1ab189d", - "reference": "8e1d2397d8adf59a3f12b2878a3aaa66d1ab189d", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/661f34d0bd3f1a7225ef491a70a020ad23a057a1", + "reference": "661f34d0bd3f1a7225ef491a70a020ad23a057a1", "shasum": "" }, "require": { @@ -1336,14 +1344,13 @@ "php": "^7.0", "phpunit/php-file-iterator": "^1.4.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^2.0", + "phpunit/php-token-stream": "^2.0.1", "sebastian/code-unit-reverse-lookup": "^1.0.1", "sebastian/environment": "^3.0", "sebastian/version": "^2.0.1", "theseer/tokenizer": "^1.1" }, "require-dev": { - "ext-xdebug": "^2.5", "phpunit/phpunit": "^6.0" }, "suggest": { @@ -1352,7 +1359,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.2.x-dev" + "dev-master": "5.3.x-dev" } }, "autoload": { @@ -1367,7 +1374,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -1378,20 +1385,20 @@ "testing", "xunit" ], - "time": "2017-11-03T13:47:33+00:00" + "time": "2017-12-06T09:29:45+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "1.4.3", + "version": "1.4.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "8ebba84e5bd74fc5fdeb916b38749016c7232f93" + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/8ebba84e5bd74fc5fdeb916b38749016c7232f93", - "reference": "8ebba84e5bd74fc5fdeb916b38749016c7232f93", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", "shasum": "" }, "require": { @@ -1425,7 +1432,7 @@ "filesystem", "iterator" ], - "time": "2017-11-24T15:00:59+00:00" + "time": "2017-11-27T13:52:08+00:00" }, { "name": "phpunit/php-text-template", @@ -1519,16 +1526,16 @@ }, { "name": "phpunit/php-token-stream", - "version": "2.0.1", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "9a02332089ac48e704c70f6cefed30c224e3c0b0" + "reference": "791198a2c6254db10131eecfe8c06670700904db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/9a02332089ac48e704c70f6cefed30c224e3c0b0", - "reference": "9a02332089ac48e704c70f6cefed30c224e3c0b0", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", + "reference": "791198a2c6254db10131eecfe8c06670700904db", "shasum": "" }, "require": { @@ -1564,20 +1571,20 @@ "keywords": [ "tokenizer" ], - "time": "2017-08-20T05:47:52+00:00" + "time": "2017-11-27T05:48:46+00:00" }, { "name": "phpunit/phpunit", - "version": "6.4.4", + "version": "6.5.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "562f7dc75d46510a4ed5d16189ae57fbe45a9932" + "reference": "83d27937a310f2984fd575686138597147bdc7df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/562f7dc75d46510a4ed5d16189ae57fbe45a9932", - "reference": "562f7dc75d46510a4ed5d16189ae57fbe45a9932", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/83d27937a310f2984fd575686138597147bdc7df", + "reference": "83d27937a310f2984fd575686138597147bdc7df", "shasum": "" }, "require": { @@ -1591,12 +1598,12 @@ "phar-io/version": "^1.0", "php": "^7.0", "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^5.2.2", - "phpunit/php-file-iterator": "^1.4.2", + "phpunit/php-code-coverage": "^5.3", + "phpunit/php-file-iterator": "^1.4.3", "phpunit/php-text-template": "^1.2.1", "phpunit/php-timer": "^1.0.9", - "phpunit/phpunit-mock-objects": "^4.0.3", - "sebastian/comparator": "^2.0.2", + "phpunit/phpunit-mock-objects": "^5.0.5", + "sebastian/comparator": "^2.1", "sebastian/diff": "^2.0", "sebastian/environment": "^3.1", "sebastian/exporter": "^3.1", @@ -1622,7 +1629,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.4.x-dev" + "dev-master": "6.5.x-dev" } }, "autoload": { @@ -1648,33 +1655,33 @@ "testing", "xunit" ], - "time": "2017-11-08T11:26:09+00:00" + "time": "2017-12-17T06:31:19+00:00" }, { "name": "phpunit/phpunit-mock-objects", - "version": "4.0.4", + "version": "5.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "2f789b59ab89669015ad984afa350c4ec577ade0" + "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/2f789b59ab89669015ad984afa350c4ec577ade0", - "reference": "2f789b59ab89669015ad984afa350c4ec577ade0", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/33fd41a76e746b8fa96d00b49a23dadfa8334cdf", + "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.5", "php": "^7.0", "phpunit/php-text-template": "^1.2.1", - "sebastian/exporter": "^3.0" + "sebastian/exporter": "^3.1" }, "conflict": { "phpunit/phpunit": "<6.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^6.5" }, "suggest": { "ext-soap": "*" @@ -1682,7 +1689,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0.x-dev" + "dev-master": "5.0.x-dev" } }, "autoload": { @@ -1697,7 +1704,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -1707,7 +1714,7 @@ "mock", "xunit" ], - "time": "2017-08-03T14:08:16+00:00" + "time": "2018-01-06T05:45:45+00:00" }, { "name": "psr/container", @@ -1852,16 +1859,16 @@ }, { "name": "sebastian/comparator", - "version": "2.1.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "1174d9018191e93cb9d719edec01257fc05f8158" + "reference": "b11c729f95109b56a0fe9650c6a63a0fcd8c439f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1174d9018191e93cb9d719edec01257fc05f8158", - "reference": "1174d9018191e93cb9d719edec01257fc05f8158", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/b11c729f95109b56a0fe9650c6a63a0fcd8c439f", + "reference": "b11c729f95109b56a0fe9650c6a63a0fcd8c439f", "shasum": "" }, "require": { @@ -1912,7 +1919,7 @@ "compare", "equality" ], - "time": "2017-11-03T07:16:52+00:00" + "time": "2017-12-22T14:50:35+00:00" }, { "name": "sebastian/diff", @@ -2366,27 +2373,27 @@ }, { "name": "slevomat/coding-standard", - "version": "4.0.0", + "version": "4.2.1", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "bab653d0f7f2e3ed13796f7803067d252f00a25a" + "reference": "998b5e96ce36a55d7821d17f39d296a17c05b481" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/bab653d0f7f2e3ed13796f7803067d252f00a25a", - "reference": "bab653d0f7f2e3ed13796f7803067d252f00a25a", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/998b5e96ce36a55d7821d17f39d296a17c05b481", + "reference": "998b5e96ce36a55d7821d17f39d296a17c05b481", "shasum": "" }, "require": { - "php": "^7.0", - "squizlabs/php_codesniffer": "^3.0.1" + "php": "^7.1", + "squizlabs/php_codesniffer": "^3.0.2" }, "require-dev": { "jakub-onderka/php-parallel-lint": "0.9.2", "phing/phing": "2.16", - "phpstan/phpstan": "0.8.4", - "phpunit/phpunit": "6.3.0" + "phpstan/phpstan": "0.9.1", + "phpunit/phpunit": "6.5.5" }, "type": "phpcodesniffer-standard", "autoload": { @@ -2399,20 +2406,20 @@ "MIT" ], "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "time": "2017-09-15T17:47:36+00:00" + "time": "2018-01-04T14:00:21+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.1.1", + "version": "3.2.2", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "d667e245d5dcd4d7bf80f26f2c947d476b66213e" + "reference": "d7c00c3000ac0ce79c96fcbfef86b49a71158cd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/d667e245d5dcd4d7bf80f26f2c947d476b66213e", - "reference": "d667e245d5dcd4d7bf80f26f2c947d476b66213e", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/d7c00c3000ac0ce79c96fcbfef86b49a71158cd1", + "reference": "d7c00c3000ac0ce79c96fcbfef86b49a71158cd1", "shasum": "" }, "require": { @@ -2450,34 +2457,32 @@ "phpcs", "standards" ], - "time": "2017-10-16T22:40:25+00:00" + "time": "2017-12-19T21:44:46+00:00" }, { "name": "symfony/config", - "version": "v3.3.13", + "version": "v4.0.3", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "8d2649077dc54dfbaf521d31f217383d82303c5f" + "reference": "0e86d267db0851cf55f339c97df00d693fe8592f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/8d2649077dc54dfbaf521d31f217383d82303c5f", - "reference": "8d2649077dc54dfbaf521d31f217383d82303c5f", + "url": "https://api.github.com/repos/symfony/config/zipball/0e86d267db0851cf55f339c97df00d693fe8592f", + "reference": "0e86d267db0851cf55f339c97df00d693fe8592f", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/filesystem": "~2.8|~3.0" + "php": "^7.1.3", + "symfony/filesystem": "~3.4|~4.0" }, "conflict": { - "symfony/dependency-injection": "<3.3", - "symfony/finder": "<3.3" + "symfony/finder": "<3.4" }, "require-dev": { - "symfony/dependency-injection": "~3.3", - "symfony/finder": "~3.3", - "symfony/yaml": "~3.0" + "symfony/finder": "~3.4|~4.0", + "symfony/yaml": "~3.4|~4.0" }, "suggest": { "symfony/yaml": "To use the yaml reference dumper" @@ -2485,7 +2490,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -2512,48 +2517,48 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2017-11-07T14:16:22+00:00" + "time": "2018-01-03T07:38:00+00:00" }, { "name": "symfony/console", - "version": "v3.3.13", + "version": "v4.0.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "63cd7960a0a522c3537f6326706d7f3b8de65805" + "reference": "fe0e69d7162cba0885791cf7eea5f0d7bc0f897e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/63cd7960a0a522c3537f6326706d7f3b8de65805", - "reference": "63cd7960a0a522c3537f6326706d7f3b8de65805", + "url": "https://api.github.com/repos/symfony/console/zipball/fe0e69d7162cba0885791cf7eea5f0d7bc0f897e", + "reference": "fe0e69d7162cba0885791cf7eea5f0d7bc0f897e", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/debug": "~2.8|~3.0", + "php": "^7.1.3", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/dependency-injection": "<3.3" + "symfony/dependency-injection": "<3.4", + "symfony/process": "<3.3" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~3.3", - "symfony/dependency-injection": "~3.3", - "symfony/event-dispatcher": "~2.8|~3.0", - "symfony/filesystem": "~2.8|~3.0", - "symfony/process": "~2.8|~3.0" + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0" }, "suggest": { "psr/log": "For using the console logger", "symfony/event-dispatcher": "", - "symfony/filesystem": "", + "symfony/lock": "", "symfony/process": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -2580,36 +2585,36 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2017-11-16T15:24:32+00:00" + "time": "2018-01-03T07:38:00+00:00" }, { "name": "symfony/debug", - "version": "v3.3.13", + "version": "v4.0.3", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "74557880e2846b5c84029faa96b834da37e29810" + "reference": "9ae4223a661b56a9abdce144de4886cca37f198f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/74557880e2846b5c84029faa96b834da37e29810", - "reference": "74557880e2846b5c84029faa96b834da37e29810", + "url": "https://api.github.com/repos/symfony/debug/zipball/9ae4223a661b56a9abdce144de4886cca37f198f", + "reference": "9ae4223a661b56a9abdce144de4886cca37f198f", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", + "php": "^7.1.3", "psr/log": "~1.0" }, "conflict": { - "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + "symfony/http-kernel": "<3.4" }, "require-dev": { - "symfony/http-kernel": "~2.8|~3.0" + "symfony/http-kernel": "~3.4|~4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -2636,38 +2641,39 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2017-11-10T16:38:39+00:00" + "time": "2018-01-03T17:15:19+00:00" }, { "name": "symfony/dependency-injection", - "version": "v3.3.13", + "version": "v4.0.3", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "4e84f5af2c2d51ee3dee72df40b7fc08f49b4ab8" + "reference": "67bf5e4f4da85624f30a5e43b7f43225c8b71959" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/4e84f5af2c2d51ee3dee72df40b7fc08f49b4ab8", - "reference": "4e84f5af2c2d51ee3dee72df40b7fc08f49b4ab8", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/67bf5e4f4da85624f30a5e43b7f43225c8b71959", + "reference": "67bf5e4f4da85624f30a5e43b7f43225c8b71959", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", + "php": "^7.1.3", "psr/container": "^1.0" }, "conflict": { - "symfony/config": "<3.3.1", - "symfony/finder": "<3.3", - "symfony/yaml": "<3.3" + "symfony/config": "<3.4", + "symfony/finder": "<3.4", + "symfony/proxy-manager-bridge": "<3.4", + "symfony/yaml": "<3.4" }, "provide": { "psr/container-implementation": "1.0" }, "require-dev": { - "symfony/config": "~3.3", - "symfony/expression-language": "~2.8|~3.0", - "symfony/yaml": "~3.3" + "symfony/config": "~3.4|~4.0", + "symfony/expression-language": "~3.4|~4.0", + "symfony/yaml": "~3.4|~4.0" }, "suggest": { "symfony/config": "", @@ -2679,7 +2685,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -2706,34 +2712,34 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2017-11-13T18:10:32+00:00" + "time": "2018-01-04T15:52:56+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v3.3.13", + "version": "v4.0.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "271d8c27c3ec5ecee6e2ac06016232e249d638d9" + "reference": "74d33aac36208c4d6757807d9f598f0133a3a4eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/271d8c27c3ec5ecee6e2ac06016232e249d638d9", - "reference": "271d8c27c3ec5ecee6e2ac06016232e249d638d9", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/74d33aac36208c4d6757807d9f598f0133a3a4eb", + "reference": "74d33aac36208c4d6757807d9f598f0133a3a4eb", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": "^7.1.3" }, "conflict": { - "symfony/dependency-injection": "<3.3" + "symfony/dependency-injection": "<3.4" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~2.8|~3.0", - "symfony/dependency-injection": "~3.3", - "symfony/expression-language": "~2.8|~3.0", - "symfony/stopwatch": "~2.8|~3.0" + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/expression-language": "~3.4|~4.0", + "symfony/stopwatch": "~3.4|~4.0" }, "suggest": { "symfony/dependency-injection": "", @@ -2742,7 +2748,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -2769,29 +2775,29 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2017-11-05T15:47:03+00:00" + "time": "2018-01-03T07:38:00+00:00" }, { "name": "symfony/filesystem", - "version": "v3.3.13", + "version": "v4.0.3", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "77db266766b54db3ee982fe51868328b887ce15c" + "reference": "760e47a4ee64b4c48f4b30017011e09d4c0f05ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/77db266766b54db3ee982fe51868328b887ce15c", - "reference": "77db266766b54db3ee982fe51868328b887ce15c", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/760e47a4ee64b4c48f4b30017011e09d4c0f05ed", + "reference": "760e47a4ee64b4c48f4b30017011e09d4c0f05ed", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": "^7.1.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -2818,29 +2824,29 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2017-11-07T14:12:55+00:00" + "time": "2018-01-03T07:38:00+00:00" }, { "name": "symfony/finder", - "version": "v3.3.13", + "version": "v4.0.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "138af5ec075d4b1d1bd19de08c38a34bb2d7d880" + "reference": "8b08180f2b7ccb41062366b9ad91fbc4f1af8601" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/138af5ec075d4b1d1bd19de08c38a34bb2d7d880", - "reference": "138af5ec075d4b1d1bd19de08c38a34bb2d7d880", + "url": "https://api.github.com/repos/symfony/finder/zipball/8b08180f2b7ccb41062366b9ad91fbc4f1af8601", + "reference": "8b08180f2b7ccb41062366b9ad91fbc4f1af8601", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": "^7.1.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -2867,33 +2873,33 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2017-11-05T15:47:03+00:00" + "time": "2018-01-03T07:38:00+00:00" }, { "name": "symfony/http-foundation", - "version": "v3.3.13", + "version": "v4.0.3", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "5943f0f19817a7e05992d20a90729b0dc93faf36" + "reference": "03fe5171e35966f43453e2e5c15d7fe65f7fb23b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/5943f0f19817a7e05992d20a90729b0dc93faf36", - "reference": "5943f0f19817a7e05992d20a90729b0dc93faf36", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/03fe5171e35966f43453e2e5c15d7fe65f7fb23b", + "reference": "03fe5171e35966f43453e2e5c15d7fe65f7fb23b", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", + "php": "^7.1.3", "symfony/polyfill-mbstring": "~1.1" }, "require-dev": { - "symfony/expression-language": "~2.8|~3.0" + "symfony/expression-language": "~3.4|~4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -2920,66 +2926,66 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2017-11-13T18:13:16+00:00" + "time": "2018-01-03T17:15:19+00:00" }, { "name": "symfony/http-kernel", - "version": "v3.3.13", + "version": "v4.0.3", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "a2a942172b742217ab2ccd9399494af2aa17c766" + "reference": "f707ed09d3b5799a26c985de480d48b48540d41a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/a2a942172b742217ab2ccd9399494af2aa17c766", - "reference": "a2a942172b742217ab2ccd9399494af2aa17c766", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f707ed09d3b5799a26c985de480d48b48540d41a", + "reference": "f707ed09d3b5799a26c985de480d48b48540d41a", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", + "php": "^7.1.3", "psr/log": "~1.0", - "symfony/debug": "~2.8|~3.0", - "symfony/event-dispatcher": "~2.8|~3.0", - "symfony/http-foundation": "^3.3.11" + "symfony/debug": "~3.4|~4.0", + "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/http-foundation": "~3.4|~4.0" }, "conflict": { - "symfony/config": "<2.8", - "symfony/dependency-injection": "<3.3", - "symfony/var-dumper": "<3.3", + "symfony/config": "<3.4", + "symfony/dependency-injection": "<3.4", + "symfony/var-dumper": "<3.4", "twig/twig": "<1.34|<2.4,>=2" }, + "provide": { + "psr/log-implementation": "1.0" + }, "require-dev": { "psr/cache": "~1.0", - "symfony/browser-kit": "~2.8|~3.0", - "symfony/class-loader": "~2.8|~3.0", - "symfony/config": "~2.8|~3.0", - "symfony/console": "~2.8|~3.0", - "symfony/css-selector": "~2.8|~3.0", - "symfony/dependency-injection": "~3.3", - "symfony/dom-crawler": "~2.8|~3.0", - "symfony/expression-language": "~2.8|~3.0", - "symfony/finder": "~2.8|~3.0", - "symfony/process": "~2.8|~3.0", - "symfony/routing": "~2.8|~3.0", - "symfony/stopwatch": "~2.8|~3.0", - "symfony/templating": "~2.8|~3.0", - "symfony/translation": "~2.8|~3.0", - "symfony/var-dumper": "~3.3" + "symfony/browser-kit": "~3.4|~4.0", + "symfony/config": "~3.4|~4.0", + "symfony/console": "~3.4|~4.0", + "symfony/css-selector": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/dom-crawler": "~3.4|~4.0", + "symfony/expression-language": "~3.4|~4.0", + "symfony/finder": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0", + "symfony/routing": "~3.4|~4.0", + "symfony/stopwatch": "~3.4|~4.0", + "symfony/templating": "~3.4|~4.0", + "symfony/translation": "~3.4|~4.0", + "symfony/var-dumper": "~3.4|~4.0" }, "suggest": { "symfony/browser-kit": "", - "symfony/class-loader": "", "symfony/config": "", "symfony/console": "", "symfony/dependency-injection": "", - "symfony/finder": "", "symfony/var-dumper": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -3006,29 +3012,29 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2017-11-16T18:14:43+00:00" + "time": "2018-01-05T08:54:25+00:00" }, { "name": "symfony/options-resolver", - "version": "v3.3.13", + "version": "v4.0.3", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "623d9c210a137205f7e6e98166105625402cbb2f" + "reference": "30d9240b30696a69e893534c9fc4a5c72ab6689b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/623d9c210a137205f7e6e98166105625402cbb2f", - "reference": "623d9c210a137205f7e6e98166105625402cbb2f", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/30d9240b30696a69e893534c9fc4a5c72ab6689b", + "reference": "30d9240b30696a69e893534c9fc4a5c72ab6689b", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": "^7.1.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -3060,7 +3066,7 @@ "configuration", "options" ], - "time": "2017-11-05T15:47:03+00:00" + "time": "2018-01-03T07:38:00+00:00" }, { "name": "symfony/polyfill-mbstring", @@ -3237,25 +3243,25 @@ }, { "name": "symfony/process", - "version": "v3.3.13", + "version": "v4.0.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "a56a3989fb762d7b19a0cf8e7693ee99a6ffb78d" + "reference": "2145b3e8137e463b1051b79440a59b38220944f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/a56a3989fb762d7b19a0cf8e7693ee99a6ffb78d", - "reference": "a56a3989fb762d7b19a0cf8e7693ee99a6ffb78d", + "url": "https://api.github.com/repos/symfony/process/zipball/2145b3e8137e463b1051b79440a59b38220944f0", + "reference": "2145b3e8137e463b1051b79440a59b38220944f0", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": "^7.1.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -3282,29 +3288,29 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2017-11-13T15:31:11+00:00" + "time": "2018-01-03T07:38:00+00:00" }, { "name": "symfony/stopwatch", - "version": "v3.3.13", + "version": "v4.0.3", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "1e93c3139ef6c799831fe03efd0fb1c7aecb3365" + "reference": "d52321f0e2b596bd03b5d1dd6eebe71caa925704" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/1e93c3139ef6c799831fe03efd0fb1c7aecb3365", - "reference": "1e93c3139ef6c799831fe03efd0fb1c7aecb3365", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/d52321f0e2b596bd03b5d1dd6eebe71caa925704", + "reference": "d52321f0e2b596bd03b5d1dd6eebe71caa925704", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": "^7.1.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -3331,27 +3337,30 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2017-11-10T19:02:53+00:00" + "time": "2018-01-03T07:38:00+00:00" }, { "name": "symfony/yaml", - "version": "v3.3.13", + "version": "v4.0.3", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "0938408c4faa518d95230deabb5f595bf0de31b9" + "reference": "b84f646b9490d2101e2c25ddeec77ceefbda2eee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/0938408c4faa518d95230deabb5f595bf0de31b9", - "reference": "0938408c4faa518d95230deabb5f595bf0de31b9", + "url": "https://api.github.com/repos/symfony/yaml/zipball/b84f646b9490d2101e2c25ddeec77ceefbda2eee", + "reference": "b84f646b9490d2101e2c25ddeec77ceefbda2eee", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": "^7.1.3" + }, + "conflict": { + "symfony/console": "<3.4" }, "require-dev": { - "symfony/console": "~2.8|~3.0" + "symfony/console": "~3.4|~4.0" }, "suggest": { "symfony/console": "For validating YAML files using the lint command" @@ -3359,7 +3368,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -3386,48 +3395,86 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2017-11-10T18:26:04+00:00" + "time": "2018-01-03T07:38:00+00:00" + }, + { + "name": "symplify/better-reflection-docblock", + "version": "v3.1.2", + "source": { + "type": "git", + "url": "https://github.com/Symplify/BetterReflectionDocBlock.git", + "reference": "7746ed526ffedfb4907a7ff83606a9e0f1e55c56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Symplify/BetterReflectionDocBlock/zipball/7746ed526ffedfb4907a7ff83606a9e0f1e55c56", + "reference": "7746ed526ffedfb4907a7ff83606a9e0f1e55c56", + "shasum": "" + }, + "require": { + "php": "^7.1", + "phpdocumentor/reflection-docblock": "4.2", + "symplify/package-builder": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symplify\\BetterReflectionDocBlock\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slim wrapper around phpdocumentor/reflection-docblock with better DX and simpler API.", + "time": "2018-01-02T22:35:18+00:00" }, { "name": "symplify/coding-standard", - "version": "v3.0.0-RC3", + "version": "v3.1.2", "source": { "type": "git", "url": "https://github.com/Symplify/CodingStandard.git", - "reference": "0a3958f1cb6ce733def98f3abdf52a4e6c723879" + "reference": "0985870bd373d65c69747c2ae854761497f96aac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/CodingStandard/zipball/0a3958f1cb6ce733def98f3abdf52a4e6c723879", - "reference": "0a3958f1cb6ce733def98f3abdf52a4e6c723879", + "url": "https://api.github.com/repos/Symplify/CodingStandard/zipball/0985870bd373d65c69747c2ae854761497f96aac", + "reference": "0985870bd373d65c69747c2ae854761497f96aac", "shasum": "" }, "require": { - "friendsofphp/php-cs-fixer": "^2.8", - "nette/finder": "^2.4|^3.0", - "nette/utils": "^2.4|^3.0", + "friendsofphp/php-cs-fixer": "^2.9", + "nette/finder": "^2.4", + "nette/utils": "^2.4", "php": "^7.1", - "slevomat/coding-standard": "^4.0", - "squizlabs/php_codesniffer": "^3.1" + "phpdocumentor/reflection-docblock": "4.2", + "squizlabs/php_codesniffer": "^3.2", + "symplify/token-runner": "^3.1" }, "require-dev": { - "gecko-packages/gecko-php-unit": "3.0 as 2.2", "nette/application": "^2.4", - "phpunit/phpunit": "^6.4", - "symplify/easy-coding-standard": "^2.5|^3.0", - "symplify/package-builder": "^2.5|^3.0" + "phpunit/phpunit": "^6.5", + "symplify/easy-coding-standard": "^3.1", + "symplify/package-builder": "^3.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.1-dev" } }, "autoload": { "psr-4": { - "Symplify\\CodingStandard\\": "src", - "Symplify\\CodingStandard\\SniffTokenWrapper\\": "packages/SniffTokenWrapper/src", - "Symplify\\CodingStandard\\FixerTokenWrapper\\": "packages/FixerTokenWrapper/src" + "Symplify\\CodingStandard\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -3435,45 +3482,44 @@ "MIT" ], "description": "Set of Symplify rules for PHP_CodeSniffer.", - "time": "2017-11-18T01:05:00+00:00" + "time": "2018-01-03T00:42:03+00:00" }, { "name": "symplify/easy-coding-standard", - "version": "v3.0.0-RC3", + "version": "v3.1.2", "source": { "type": "git", "url": "https://github.com/Symplify/EasyCodingStandard.git", - "reference": "7f2e7728a184c72945da482b23eb05b796f6502c" + "reference": "0018936e9acecfa6df0919e2e05923d0b3677435" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/EasyCodingStandard/zipball/7f2e7728a184c72945da482b23eb05b796f6502c", - "reference": "7f2e7728a184c72945da482b23eb05b796f6502c", + "url": "https://api.github.com/repos/Symplify/EasyCodingStandard/zipball/0018936e9acecfa6df0919e2e05923d0b3677435", + "reference": "0018936e9acecfa6df0919e2e05923d0b3677435", "shasum": "" }, "require": { - "friendsofphp/php-cs-fixer": "^2.8", - "nette/caching": "^2.4|^3.0", - "nette/di": "^2.4|^3.0", - "nette/neon": "^2.4|^3.0", + "friendsofphp/php-cs-fixer": "^2.9", + "nette/caching": "^2.4", + "nette/di": "^2.4", + "nette/neon": "^2.4", "nette/robot-loader": "^2.4|^3.0.1", - "nette/utils": "^2.4|^3.0", + "nette/utils": "^2.4", "php": "^7.1", - "sebastian/diff": "^1.4|^2.0", - "slevomat/coding-standard": "^4.0", - "squizlabs/php_codesniffer": "^3.1", - "symfony/config": "^3.3|^4.0", - "symfony/console": "^3.3|^4.0", - "symfony/dependency-injection": "^3.3|^4.0", - "symfony/finder": "^3.3|^4.0", - "symfony/http-kernel": "^3.3|^4.0", - "symfony/yaml": "^3.3|^4.0", - "symplify/coding-standard": "^2.5|^3.0", - "symplify/package-builder": "^2.5|^3.0", - "tracy/tracy": "^2.4|^3.0" + "slevomat/coding-standard": "^4.1", + "squizlabs/php_codesniffer": "^3.2", + "symfony/config": "^4.0", + "symfony/console": "^4.0", + "symfony/dependency-injection": "^4.0", + "symfony/finder": "^4.0", + "symfony/http-kernel": "^4.0", + "symfony/yaml": "^4.0", + "symplify/coding-standard": "^3.1", + "symplify/package-builder": "^3.1", + "symplify/token-runner": "^3.1" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^6.4" }, "bin": [ "bin/easy-coding-standard", @@ -3481,11 +3527,6 @@ "bin/easy-coding-standard.php" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, "autoload": { "psr-4": { "Symplify\\EasyCodingStandard\\": "src", @@ -3501,42 +3542,38 @@ "MIT" ], "description": "Use Coding Standard with 0-knowledge of PHP-CS-Fixer and PHP_CodeSniffer.", - "time": "2017-11-18T14:13:17+00:00" + "time": "2018-01-03T00:41:52+00:00" }, { "name": "symplify/package-builder", - "version": "v3.0.0-RC3", + "version": "v3.1.2", "source": { "type": "git", "url": "https://github.com/Symplify/PackageBuilder.git", - "reference": "c86f75165ed2370563a9d4ff6604bbb24cffd5e5" + "reference": "0149e25615b98df5cdb25a155a1f10002cf1958a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/PackageBuilder/zipball/c86f75165ed2370563a9d4ff6604bbb24cffd5e5", - "reference": "c86f75165ed2370563a9d4ff6604bbb24cffd5e5", + "url": "https://api.github.com/repos/Symplify/PackageBuilder/zipball/0149e25615b98df5cdb25a155a1f10002cf1958a", + "reference": "0149e25615b98df5cdb25a155a1f10002cf1958a", "shasum": "" }, "require": { - "nette/di": "^2.4|^3.0", - "nette/neon": "^2.4|^3.0", + "nette/di": "^2.4", + "nette/neon": "^2.4", "php": "^7.1", - "symfony/config": "^3.3|^4.0", - "symfony/console": "^3.3|^4.0", - "symfony/dependency-injection": "^3.3|^4.0", - "symfony/http-kernel": "^3.3|^4.0", - "symfony/yaml": "^3.3|^4.0" + "symfony/config": "^4.0", + "symfony/console": "^4.0", + "symfony/dependency-injection": "^4.0", + "symfony/finder": "^4.0", + "symfony/http-kernel": "^4.0", + "symfony/yaml": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^6.4", + "phpunit/phpunit": "^6.5", "tracy/tracy": "^2.4" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, "autoload": { "psr-4": { "Symplify\\PackageBuilder\\": "src" @@ -3547,114 +3584,87 @@ "MIT" ], "description": "Dependency Injection, Console and Kernel toolkit for Symplify packages.", - "time": "2017-11-17T13:58:38+00:00" + "time": "2018-01-02T22:35:18+00:00" }, { - "name": "theseer/tokenizer", - "version": "1.1.0", + "name": "symplify/token-runner", + "version": "v3.1.2", "source": { "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + "url": "https://github.com/Symplify/TokenRunner.git", + "reference": "5c4cc4f24507b6cbdb33026dfad5b46660c5b3ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "url": "https://api.github.com/repos/Symplify/TokenRunner/zipball/5c4cc4f24507b6cbdb33026dfad5b46660c5b3ec", + "reference": "5c4cc4f24507b6cbdb33026dfad5b46660c5b3ec", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.0" + "friendsofphp/php-cs-fixer": "^2.9", + "nette/finder": "^2.4", + "nette/utils": "^2.4", + "php": "^7.1", + "phpdocumentor/reflection-docblock": "^4.2", + "squizlabs/php_codesniffer": "^3.2", + "symplify/better-reflection-docblock": "^3.1", + "symplify/package-builder": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.5" }, "type": "library", "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Symplify\\TokenRunner\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - } + "MIT" ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2017-04-07T12:08:54+00:00" + "description": "Set of utils for PHP_CodeSniffer and PHP CS Fixer.", + "time": "2018-01-02T22:35:18+00:00" }, { - "name": "tracy/tracy", - "version": "v2.4.10", + "name": "theseer/tokenizer", + "version": "1.1.0", "source": { "type": "git", - "url": "https://github.com/nette/tracy.git", - "reference": "5b302790edd71924dfe4ec44f499ef61df3f53a2" + "url": "https://github.com/theseer/tokenizer.git", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/tracy/zipball/5b302790edd71924dfe4ec44f499ef61df3f53a2", - "reference": "5b302790edd71924dfe4ec44f499ef61df3f53a2", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", "shasum": "" }, "require": { - "ext-json": "*", - "ext-session": "*", - "php": ">=5.4.4" - }, - "require-dev": { - "nette/di": "~2.3", - "nette/tester": "~1.7" - }, - "suggest": { - "https://nette.org/donate": "Please support Tracy via a donation" + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.4-dev" - } - }, "autoload": { "classmap": [ - "src" - ], - "files": [ - "src/shortcuts.php" + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause", - "GPL-2.0", - "GPL-3.0" + "BSD-3-Clause" ], "authors": [ { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" } ], - "description": "😎 Tracy: the addictive tool to ease debugging PHP code for cool developers. Friendly design, logging, profiler, advanced features like debugging AJAX calls or CLI support. You will love it.", - "homepage": "https://tracy.nette.org", - "keywords": [ - "Xdebug", - "debug", - "debugger", - "nette", - "profiler" - ], - "time": "2017-10-04T18:43:42+00:00" + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2017-04-07T12:08:54+00:00" }, { "name": "webmozart/assert", @@ -3709,11 +3719,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "symplify/easy-coding-standard": 5, - "symplify/coding-standard": 5, - "symplify/package-builder": 5 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/easy-coding-standard.neon b/easy-coding-standard.neon index 9155b106..abf30aff 100644 --- a/easy-coding-standard.neon +++ b/easy-coding-standard.neon @@ -1,14 +1,8 @@ includes: - vendor/symplify/easy-coding-standard/config/psr2.neon - - vendor/symplify/easy-coding-standard/config/php70.neon + - vendor/symplify/easy-coding-standard/config/php71.neon - vendor/symplify/easy-coding-standard/config/clean-code.neon - - vendor/symplify/easy-coding-standard/config/common/array.neon - - vendor/symplify/easy-coding-standard/config/common/docblock.neon - - vendor/symplify/easy-coding-standard/config/common/namespaces.neon - - vendor/symplify/easy-coding-standard/config/common/control-structures.neon - - # many errors, need help - #- vendor/symplify/easy-coding-standard/config/common/strict.neon + - vendor/symplify/easy-coding-standard/config/common.neon checkers: # spacing @@ -33,13 +27,16 @@ checkers: - Symplify\CodingStandard\Fixer\Import\ImportNamespacedNameFixer - Symplify\CodingStandard\Fixer\Php\ClassStringToClassConstantFixer - Symplify\CodingStandard\Fixer\Property\ArrayPropertyDefaultValueFixer - - Symplify\CodingStandard\Fixer\ClassNotation\PropertyAndConstantSeparationFixer - Symplify\CodingStandard\Fixer\ArrayNotation\StandaloneLineInMultilineArrayFixer parameters: exclude_checkers: # from strict.neon - PhpCsFixer\Fixer\PhpUnit\PhpUnitStrictFixer + - PhpCsFixer\Fixer\Strict\StrictComparisonFixer + # personal prefference + - PhpCsFixer\Fixer\Operator\NotOperatorWithSuccessorSpaceFixer + skip: PhpCsFixer\Fixer\Alias\RandomApiMigrationFixer: # random_int() breaks code @@ -47,6 +44,15 @@ parameters: SlevomatCodingStandard\Sniffs\Classes\UnusedPrivateElementsSniff: # magic calls - src/Phpml/Preprocessing/Normalizer.php + PhpCsFixer\Fixer\StringNotation\ExplicitStringVariableFixer: + # bugged + - src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php + Symplify\CodingStandard\Fixer\Commenting\RemoveUselessDocBlockFixer: + # bug in fixer + - src/Phpml/Math/LinearAlgebra/LUDecomposition.php + PhpCsFixer\Fixer\FunctionNotation\VoidReturnFixer: + # covariant return types + - src/Phpml/Classification/Linear/Perceptron.php skip_codes: # missing typehints @@ -56,3 +62,4 @@ parameters: - SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingTraversableReturnTypeHintSpecification - SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingPropertyTypeHint - SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingTraversablePropertyTypeHintSpecification + - PHP_CodeSniffer\Standards\Generic\Sniffs\CodeAnalysis\AssignmentInConditionSniff.Found diff --git a/humbug.json.dist b/humbug.json.dist deleted file mode 100644 index 2535633a..00000000 --- a/humbug.json.dist +++ /dev/null @@ -1,11 +0,0 @@ -{ - "source": { - "directories": [ - "src" - ] - }, - "timeout": 10, - "logs": { - "text": "humbuglog.txt" - } -} diff --git a/phpstan.neon b/phpstan.neon index 19e3c208..0366c23e 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,19 +1,15 @@ parameters: ignoreErrors: - '#Phpml\\Dataset\\FilesDataset::__construct\(\) does not call parent constructor from Phpml\\Dataset\\ArrayDataset#' - - '#Parameter \#2 \$predictedLabels of static method Phpml\\Metric\\Accuracy::score\(\) expects mixed\[\], mixed\[\]\|string given#' - # should be always defined - - '#Undefined variable: \$j#' + # mocks + - '#PHPUnit_Framework_MockObject_MockObject#' - # bugged - - '#expects [a-z\\\|\[\]]*, [a-z\\\|\(\)\[\]]*\[\] given#' - - # mock - - '#Parameter \#1 \$node of class Phpml\\NeuralNetwork\\Node\\Neuron\\Synapse constructor expects Phpml\\NeuralNetwork\\Node, Phpml\\NeuralNetwork\\Node\\Neuron\|PHPUnit_Framework_MockObject_MockObject given#' - - '#Parameter \#1 \$(activationFunction|synapse) of class Phpml\\NeuralNetwork\\Node\\Neuron constructor expects Phpml\\NeuralNetwork\\ActivationFunction|null, Phpml\\NeuralNetwork\\ActivationFunction\\BinaryStep|PHPUnit_Framework_MockObject_MockObject given#' + # wide range cases + - '#Call to function count\(\) with argument type array\|Phpml\\Clustering\\KMeans\\Point will always result in number 1#' + - '#Parameter \#1 \$coordinates of class Phpml\\Clustering\\KMeans\\Point constructor expects array, array\|Phpml\\Clustering\\KMeans\\Point given#' # probably known value + - '#Variable \$j might not be defined#' - '#Method Phpml\\Classification\\DecisionTree::getBestSplit\(\) should return Phpml\\Classification\\DecisionTree\\DecisionTreeLeaf but returns Phpml\\Classification\\DecisionTree\\DecisionTreeLeaf\|null#' - - '#Method Phpml\\Classification\\Linear\\DecisionStump::getBestNumericalSplit\(\) should return mixed\[\] but returns \(float\|int\|mixed\|string\)\[\]\|null#' - - '#Method Phpml\\Classification\\Linear\\DecisionStump::getBestNominalSplit\(\) should return mixed\[\] but returns mixed\[\]\|null#' + - '#Call to an undefined method Phpml\\Helper\\Optimizer\\Optimizer::getCostValues\(\)#' diff --git a/src/Phpml/Classification/DecisionTree.php b/src/Phpml/Classification/DecisionTree.php index d6603224..1d5a7548 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -64,9 +64,9 @@ class DecisionTree implements Classifier private $featureImportances; /** - * @var array|null + * @var array */ - private $columnNames; + private $columnNames = []; public function __construct(int $maxDepth = 10) { @@ -89,7 +89,7 @@ public function train(array $samples, array $targets): void // If column names are given or computed before, then there is no // need to init it and accidentally remove the previous given names - if ($this->columnNames === null) { + if ($this->columnNames === []) { $this->columnNames = range(0, $this->featureCount - 1); } elseif (count($this->columnNames) > $this->featureCount) { $this->columnNames = array_slice($this->columnNames, 0, $this->featureCount); @@ -380,9 +380,9 @@ protected function preprocess(array $samples): array $median = Mean::median($values); foreach ($values as &$value) { if ($value <= $median) { - $value = "<= $median"; + $value = "<= ${median}"; } else { - $value = "> $median"; + $value = "> ${median}"; } } } diff --git a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php index 51644728..cc53eeac 100644 --- a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -122,7 +122,7 @@ public function getNodeImpurityDecrease(int $parentRecordCount): float public function getHTML($columnNames = null): string { if ($this->isTerminal) { - $value = "$this->classValue"; + $value = "${this}->classValue"; } else { $value = $this->value; if ($columnNames !== null) { @@ -132,13 +132,13 @@ public function getHTML($columnNames = null): string } if (!preg_match('/^[<>=]{1,2}/', (string) $value)) { - $value = "=$value"; + $value = "=${value}"; } - $value = "$col $value
Gini: ".number_format($this->giniIndex, 2); + $value = "${col} ${value}
Gini: ".number_format($this->giniIndex, 2); } - $str = ""; + $str = "
$value
"; if ($this->leftLeaf || $this->rightLeaf) { $str .= ''; diff --git a/src/Phpml/Classification/Ensemble/RandomForest.php b/src/Phpml/Classification/Ensemble/RandomForest.php index 59f19c13..1f2d1f5c 100644 --- a/src/Phpml/Classification/Ensemble/RandomForest.php +++ b/src/Phpml/Classification/Ensemble/RandomForest.php @@ -148,7 +148,7 @@ protected function initSingleClassifier(Classifier $classifier): Classifier } return $classifier - ->setColumnNames($this->columnNames) - ->setNumFeatures($featureCount); + ->setColumnNames($this->columnNames) + ->setNumFeatures($featureCount); } } diff --git a/src/Phpml/Classification/Linear/DecisionStump.php b/src/Phpml/Classification/Linear/DecisionStump.php index 439ea561..b0f7dfa0 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -86,7 +86,7 @@ public function __construct(int $columnIndex = self::AUTO_SELECT) public function __toString(): string { - return "IF $this->column $this->operator $this->value ". + return "IF ${this}->column ${this}->operator ${this}->value ". 'THEN '.$this->binaryLabels[0].' '. 'ELSE '.$this->binaryLabels[1]; } @@ -176,14 +176,14 @@ protected function getBestNumericalSplit(array $samples, array $targets, int $co $maxValue = max($values); $stepSize = ($maxValue - $minValue) / $this->numSplitCount; - $split = null; + $split = []; foreach (['<=', '>'] as $operator) { // Before trying all possible split points, let's first try // the average value for the cut point $threshold = array_sum($values) / (float) count($values); [$errorRate, $prob] = $this->calculateErrorRate($targets, $threshold, $operator, $values); - if ($split == null || $errorRate < $split['trainingErrorRate']) { + if ($split === [] || $errorRate < $split['trainingErrorRate']) { $split = [ 'value' => $threshold, 'operator' => $operator, @@ -218,13 +218,13 @@ protected function getBestNominalSplit(array $samples, array $targets, int $col) $valueCounts = array_count_values($values); $distinctVals = array_keys($valueCounts); - $split = null; + $split = []; foreach (['=', '!='] as $operator) { foreach ($distinctVals as $val) { [$errorRate, $prob] = $this->calculateErrorRate($targets, $val, $operator, $values); - if ($split === null || $split['trainingErrorRate'] < $errorRate) { + if ($split === [] || $split['trainingErrorRate'] < $errorRate) { $split = [ 'value' => $val, 'operator' => $operator, diff --git a/src/Phpml/Classification/Linear/Perceptron.php b/src/Phpml/Classification/Linear/Perceptron.php index 68382276..c78e7883 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -34,7 +34,7 @@ class Perceptron implements Classifier, IncrementalEstimator protected $featureCount = 0; /** - * @var array|null + * @var array */ protected $weights = []; @@ -146,7 +146,7 @@ protected function resetBinary(): void $this->labels = []; $this->optimizer = null; $this->featureCount = 0; - $this->weights = null; + $this->weights = []; $this->costValues = []; } @@ -174,7 +174,7 @@ protected function runTraining(array $samples, array $targets) * Executes a Gradient Descent algorithm for * the given cost function */ - protected function runGradientDescent(array $samples, array $targets, Closure $gradientFunc, bool $isBatch = false): void + protected function runGradientDescent(array $samples, array $targets, Closure $gradientFunc, bool $isBatch = false) { $class = $isBatch ? GD::class : StochasticGD::class; diff --git a/src/Phpml/Classification/NaiveBayes.php b/src/Phpml/Classification/NaiveBayes.php index a470fd42..38c857db 100644 --- a/src/Phpml/Classification/NaiveBayes.php +++ b/src/Phpml/Classification/NaiveBayes.php @@ -147,7 +147,7 @@ private function sampleProbability(array $sample, int $feature, string $label): return $this->discreteProb[$label][$feature][$value]; } - $std = $this->std[$label][$feature] ; + $std = $this->std[$label][$feature]; $mean = $this->mean[$label][$feature]; // Calculate the probability density by use of normal/Gaussian distribution // Ref: https://en.wikipedia.org/wiki/Normal_distribution diff --git a/src/Phpml/Clustering/FuzzyCMeans.php b/src/Phpml/Clustering/FuzzyCMeans.php index d3be1011..7e177ed5 100644 --- a/src/Phpml/Clustering/FuzzyCMeans.php +++ b/src/Phpml/Clustering/FuzzyCMeans.php @@ -78,7 +78,7 @@ public function getMembershipMatrix(): array } /** - * @param array|Point[] $samples + * @param Point[]|int[][] $samples */ public function cluster(array $samples): array { diff --git a/src/Phpml/DimensionReduction/KernelPCA.php b/src/Phpml/DimensionReduction/KernelPCA.php index 65bb324c..5dcc7a0c 100644 --- a/src/Phpml/DimensionReduction/KernelPCA.php +++ b/src/Phpml/DimensionReduction/KernelPCA.php @@ -156,9 +156,9 @@ protected function centerMatrix(array $matrix, int $n): array $N_K_N = $N->multiply($K_N); return $K->subtract($N_K) - ->subtract($K_N) - ->add($N_K_N) - ->toArray(); + ->subtract($K_N) + ->add($N_K_N) + ->toArray(); } /** diff --git a/src/Phpml/FeatureExtraction/StopWords.php b/src/Phpml/FeatureExtraction/StopWords.php index fdb985f7..f8fc69e8 100644 --- a/src/Phpml/FeatureExtraction/StopWords.php +++ b/src/Phpml/FeatureExtraction/StopWords.php @@ -25,7 +25,7 @@ public function isStopWord(string $token): bool public static function factory(string $language = 'English'): self { - $className = __NAMESPACE__."\\StopWords\\$language"; + $className = __NAMESPACE__."\\StopWords\\${language}"; if (!class_exists($className)) { throw InvalidArgumentException::invalidStopWordsLanguage($language); diff --git a/src/Phpml/Helper/OneVsRest.php b/src/Phpml/Helper/OneVsRest.php index 1c0ca933..e68b10d0 100644 --- a/src/Phpml/Helper/OneVsRest.php +++ b/src/Phpml/Helper/OneVsRest.php @@ -164,7 +164,7 @@ abstract protected function predictSampleBinary(array $sample); */ private function binarizeTargets(array $targets, $label): array { - $notLabel = "not_$label"; + $notLabel = "not_${label}"; foreach ($targets as $key => $target) { $targets[$key] = $target == $label ? $label : $notLabel; } diff --git a/src/Phpml/Helper/Optimizer/ConjugateGradient.php b/src/Phpml/Helper/Optimizer/ConjugateGradient.php index 153ffcb2..a034af00 100644 --- a/src/Phpml/Helper/Optimizer/ConjugateGradient.php +++ b/src/Phpml/Helper/Optimizer/ConjugateGradient.php @@ -171,7 +171,7 @@ protected function getBeta(array $newTheta): float $dNew = array_sum($this->gradient($newTheta)); $dOld = array_sum($this->gradient($this->theta)) + 1e-100; - return $dNew ** 2 / $dOld ** 2; + return $dNew ** 2 / $dOld ** 2; } /** diff --git a/src/Phpml/Helper/Optimizer/Optimizer.php b/src/Phpml/Helper/Optimizer/Optimizer.php index 2b25acde..7ef317cc 100644 --- a/src/Phpml/Helper/Optimizer/Optimizer.php +++ b/src/Phpml/Helper/Optimizer/Optimizer.php @@ -47,7 +47,7 @@ public function __construct(int $dimensions) public function setInitialTheta(array $theta) { if (count($theta) != $this->dimensions) { - throw new Exception("Number of values in the weights array should be $this->dimensions"); + throw new Exception("Number of values in the weights array should be ${this}->dimensions"); } $this->theta = $theta; diff --git a/src/Phpml/Math/Set.php b/src/Phpml/Math/Set.php index dcb02e69..fab2923a 100644 --- a/src/Phpml/Math/Set.php +++ b/src/Phpml/Math/Set.php @@ -10,7 +10,7 @@ class Set implements IteratorAggregate { /** - * @var string[]|int[]|float[] + * @var string[]|int[]|float[]|bool[] */ private $elements = []; @@ -135,7 +135,7 @@ public function containsAll(array $elements): bool } /** - * @return string[]|int[]|float[] + * @return string[]|int[]|float[]|bool[] */ public function toArray(): array { @@ -160,9 +160,9 @@ public function cardinality(): int /** * Removes duplicates and rewrites index. * - * @param string[]|int[]|float[] $elements + * @param string[]|int[]|float[]|bool[] $elements * - * @return string[]|int[]|float[] + * @return string[]|int[]|float[]|bool[] */ private static function sanitize(array $elements): array { diff --git a/src/Phpml/NeuralNetwork/Training/Backpropagation.php b/src/Phpml/NeuralNetwork/Training/Backpropagation.php index 4144c30e..fd09d95c 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -15,14 +15,14 @@ class Backpropagation private $learningRate; /** - * @var array|null + * @var array */ - private $sigmas; + private $sigmas = []; /** - * @var array|null + * @var array */ - private $prevSigmas; + private $prevSigmas = []; public function __construct(float $learningRate) { @@ -57,8 +57,8 @@ public function backpropagate(array $layers, $targetClass): void } // Clean some memory (also it helps make MLP persistency & children more maintainable). - $this->sigmas = null; - $this->prevSigmas = null; + $this->sigmas = []; + $this->prevSigmas = []; } private function getSigma(Neuron $neuron, int $targetClass, int $key, bool $lastLayer): float diff --git a/src/Phpml/Regression/LeastSquares.php b/src/Phpml/Regression/LeastSquares.php index 6ecfafc8..d00ebf51 100644 --- a/src/Phpml/Regression/LeastSquares.php +++ b/src/Phpml/Regression/LeastSquares.php @@ -10,6 +10,7 @@ class LeastSquares implements Regression { use Predictable; + /** * @var array */ diff --git a/tests/Phpml/Classification/Linear/LogisticRegressionTest.php b/tests/Phpml/Classification/Linear/LogisticRegressionTest.php index 85fc1596..f60d3080 100644 --- a/tests/Phpml/Classification/Linear/LogisticRegressionTest.php +++ b/tests/Phpml/Classification/Linear/LogisticRegressionTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\Classification\Linear; +namespace Phpml\Tests\Classification\Linear; use Phpml\Classification\Linear\LogisticRegression; use PHPUnit\Framework\TestCase; @@ -55,12 +55,12 @@ public function testPredictProbabilitySingleSample(): void $zero = $method->invoke($predictor, [0.1, 0.1], 0); $one = $method->invoke($predictor, [0.1, 0.1], 1); - $this->assertEquals(1, $zero + $one, null, 1e-6); + $this->assertEquals(1, $zero + $one, '', 1e-6); $this->assertTrue($zero > $one); $zero = $method->invoke($predictor, [0.9, 0.9], 0); $one = $method->invoke($predictor, [0.9, 0.9], 1); - $this->assertEquals(1, $zero + $one, null, 1e-6); + $this->assertEquals(1, $zero + $one, '', 1e-6); $this->assertTrue($zero < $one); } @@ -97,9 +97,9 @@ public function testPredictProbabilityMultiClassSample(): void $two = $method->invoke($predictor, [3.0, 9.5], 2); $not_two = $method->invoke($predictor, [3.0, 9.5], 'not_2'); - $this->assertEquals(1, $zero + $not_zero, null, 1e-6); - $this->assertEquals(1, $one + $not_one, null, 1e-6); - $this->assertEquals(1, $two + $not_two, null, 1e-6); + $this->assertEquals(1, $zero + $not_zero, '', 1e-6); + $this->assertEquals(1, $one + $not_one, '', 1e-6); + $this->assertEquals(1, $two + $not_two, '', 1e-6); $this->assertTrue($zero < $two); $this->assertTrue($one < $two); } diff --git a/tests/Phpml/Metric/AccuracyTest.php b/tests/Phpml/Metric/AccuracyTest.php index 08d1c44a..e2260c45 100644 --- a/tests/Phpml/Metric/AccuracyTest.php +++ b/tests/Phpml/Metric/AccuracyTest.php @@ -45,7 +45,7 @@ public function testAccuracyOnDemoDataset(): void $classifier = new SVC(Kernel::RBF); $classifier->train($dataset->getTrainSamples(), $dataset->getTrainLabels()); - $predicted = $classifier->predict($dataset->getTestSamples()); + $predicted = (array) $classifier->predict($dataset->getTestSamples()); $accuracy = Accuracy::score($dataset->getTestLabels(), $predicted); diff --git a/tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php b/tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php index c244c276..885c1e14 100644 --- a/tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php +++ b/tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace tests\Phpml\NeuralNetwork\Network; +namespace Phpml\Tests\NeuralNetwork\Network; use Phpml\NeuralNetwork\Network\MultilayerPerceptron; use PHPUnit\Framework\TestCase; @@ -11,6 +11,7 @@ class MultilayerPerceptronTest extends TestCase { public function testLearningRateSetter(): void { + /** @var MultilayerPerceptron $mlp */ $mlp = $this->getMockForAbstractClass( MultilayerPerceptron::class, [5, [3], [0, 1], 1000, null, 0.42] From 5a691635d7633f964404e9a743d44d1da2e66e54 Mon Sep 17 00:00:00 2001 From: Miguel Piedrafita Date: Sun, 7 Jan 2018 10:57:14 +0100 Subject: [PATCH 218/328] Update license year (#183) * Update license year * Update LICENSE --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index bd5cb2fd..c90077cb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016 Arkadiusz Kondas +Copyright (c) 2016-2018 Arkadiusz Kondas Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 9938cf29113d8797a58a9d2f526f286d8c65f53d Mon Sep 17 00:00:00 2001 From: Yuji Uchiyama Date: Tue, 9 Jan 2018 18:53:02 +0900 Subject: [PATCH 219/328] Rewrite DBSCAN (#185) * Add testcases to DBSCAN * Fix DBSCAN implementation * Refactoring DBSCAN implementation * Fix coding style --- src/Phpml/Clustering/DBSCAN.php | 80 ++++++++++++++++++--------- tests/Phpml/Clustering/DBSCANTest.php | 34 ++++++++++++ 2 files changed, 87 insertions(+), 27 deletions(-) diff --git a/src/Phpml/Clustering/DBSCAN.php b/src/Phpml/Clustering/DBSCAN.php index 3546ebf0..e96c5ffd 100644 --- a/src/Phpml/Clustering/DBSCAN.php +++ b/src/Phpml/Clustering/DBSCAN.php @@ -4,12 +4,13 @@ namespace Phpml\Clustering; -use array_merge; use Phpml\Math\Distance; use Phpml\Math\Distance\Euclidean; class DBSCAN implements Clusterer { + private const NOISE = -1; + /** * @var float */ @@ -38,57 +39,82 @@ public function __construct(float $epsilon = 0.5, int $minSamples = 3, ?Distance public function cluster(array $samples): array { - $clusters = []; - $visited = []; + $labels = []; + $n = 0; foreach ($samples as $index => $sample) { - if (isset($visited[$index])) { + if (isset($labels[$index])) { continue; } - $visited[$index] = true; + $neighborIndices = $this->getIndicesInRegion($sample, $samples); + + if (count($neighborIndices) < $this->minSamples) { + $labels[$index] = self::NOISE; - $regionSamples = $this->getSamplesInRegion($sample, $samples); - if (count($regionSamples) >= $this->minSamples) { - $clusters[] = $this->expandCluster($regionSamples, $visited); + continue; } + + $labels[$index] = $n; + + $this->expandCluster($samples, $neighborIndices, $labels, $n); + + ++$n; } - return $clusters; + return $this->groupByCluster($samples, $labels, $n); } - private function getSamplesInRegion(array $localSample, array $samples): array + private function expandCluster(array $samples, array $seeds, array &$labels, int $n): void { - $region = []; + while (($index = array_pop($seeds)) !== null) { + if (isset($labels[$index])) { + if ($labels[$index] === self::NOISE) { + $labels[$index] = $n; + } + + continue; + } + + $labels[$index] = $n; + + $sample = $samples[$index]; + $neighborIndices = $this->getIndicesInRegion($sample, $samples); + + if (count($neighborIndices) >= $this->minSamples) { + $seeds = array_unique(array_merge($seeds, $neighborIndices)); + } + } + } + + private function getIndicesInRegion(array $center, array $samples): array + { + $indices = []; foreach ($samples as $index => $sample) { - if ($this->distanceMetric->distance($localSample, $sample) < $this->epsilon) { - $region[$index] = $sample; + if ($this->distanceMetric->distance($center, $sample) < $this->epsilon) { + $indices[] = $index; } } - return $region; + return $indices; } - private function expandCluster(array $samples, array &$visited): array + private function groupByCluster(array $samples, array $labels, int $n): array { - $cluster = []; + $clusters = array_fill(0, $n, []); - $clusterMerge = [[]]; foreach ($samples as $index => $sample) { - if (!isset($visited[$index])) { - $visited[$index] = true; - $regionSamples = $this->getSamplesInRegion($sample, $samples); - if (count($regionSamples) > $this->minSamples) { - $clusterMerge[] = $regionSamples; - } + if ($labels[$index] !== self::NOISE) { + $clusters[$labels[$index]][$index] = $sample; } - - $cluster[$index] = $sample; } - $cluster = array_merge($cluster, ...$clusterMerge); + // Reindex (i.e. to 0, 1, 2, ...) integer indices for backword compatibility + foreach ($clusters as $index => $cluster) { + $clusters[$index] = array_merge($cluster, []); + } - return $cluster; + return $clusters; } } diff --git a/tests/Phpml/Clustering/DBSCANTest.php b/tests/Phpml/Clustering/DBSCANTest.php index c0d0401b..3c6d08d8 100644 --- a/tests/Phpml/Clustering/DBSCANTest.php +++ b/tests/Phpml/Clustering/DBSCANTest.php @@ -59,4 +59,38 @@ public function testDBSCANSamplesClusteringAssociative(): void $this->assertEquals($clustered, $dbscan->cluster($samples)); } + + public function testClusterEpsilonSmall(): void + { + $samples = [[0], [1], [2]]; + $clustered = [ + ]; + + $dbscan = new DBSCAN($epsilon = 0.5, $minSamples = 2); + + $this->assertEquals($clustered, $dbscan->cluster($samples)); + } + + public function testClusterEpsilonBoundary(): void + { + $samples = [[0], [1], [2]]; + $clustered = [ + ]; + + $dbscan = new DBSCAN($epsilon = 1.0, $minSamples = 2); + + $this->assertEquals($clustered, $dbscan->cluster($samples)); + } + + public function testClusterEpsilonLarge(): void + { + $samples = [[0], [1], [2]]; + $clustered = [ + [[0], [1], [2]], + ]; + + $dbscan = new DBSCAN($epsilon = 1.5, $minSamples = 2); + + $this->assertEquals($clustered, $dbscan->cluster($samples)); + } } From e83f7b95d5f9dbbcc56cfa6e64a119632be8fbbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Monlla=C3=B3?= Date: Tue, 9 Jan 2018 11:09:59 +0100 Subject: [PATCH 220/328] Fix activation functions support (#163) - Backpropagation using the neuron activation functions derivative - instead of hardcoded sigmoid derivative - Added missing activation functions derivatives - Sigmoid forced for the output layer - Updated ThresholdedReLU default threshold to 0 (acts as a ReLU) - Unit tests for derivatives - Unit tests for classifiers using different activation functions - Added missing docs --- .../multilayer-perceptron-classifier.md | 2 ++ .../NeuralNetwork/ActivationFunction.php | 6 ++++ .../ActivationFunction/BinaryStep.php | 13 ++++++++ .../ActivationFunction/Gaussian.php | 9 ++++++ .../ActivationFunction/HyperbolicTangent.php | 9 ++++++ .../ActivationFunction/PReLU.php | 9 ++++++ .../ActivationFunction/Sigmoid.php | 9 ++++++ .../ActivationFunction/ThresholdedReLU.php | 11 ++++++- .../Network/MultilayerPerceptron.php | 6 +++- src/Phpml/NeuralNetwork/Node/Neuron.php | 17 ++++++++-- .../Training/Backpropagation.php | 2 +- .../Classification/MLPClassifierTest.php | 32 +++++++++++++++++++ .../ActivationFunction/BinaryStepTest.php | 19 +++++++++++ .../ActivationFunction/GaussianTest.php | 23 +++++++++++++ .../HyperboliTangentTest.php | 24 ++++++++++++++ .../ActivationFunction/PReLUTest.php | 23 +++++++++++++ .../ActivationFunction/SigmoidTest.php | 24 ++++++++++++++ .../ThresholdedReLUTest.php | 22 +++++++++++++ 18 files changed, 254 insertions(+), 6 deletions(-) diff --git a/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md b/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md index 72d0b4be..5acf0933 100644 --- a/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md +++ b/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md @@ -66,4 +66,6 @@ $mlp->predict([[1, 1, 1, 1], [0, 0, 0, 0]]); * BinaryStep * Gaussian * HyperbolicTangent +* Parametric Rectified Linear Unit * Sigmoid (default) +* Thresholded Rectified Linear Unit diff --git a/src/Phpml/NeuralNetwork/ActivationFunction.php b/src/Phpml/NeuralNetwork/ActivationFunction.php index 5b914257..30adf4d9 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction.php @@ -10,4 +10,10 @@ interface ActivationFunction * @param float|int $value */ public function compute($value): float; + + /** + * @param float|int $value + * @param float|int $computedvalue + */ + public function differentiate($value, $computedvalue): float; } diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/BinaryStep.php b/src/Phpml/NeuralNetwork/ActivationFunction/BinaryStep.php index 764bc4e4..56ea7eb6 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/BinaryStep.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/BinaryStep.php @@ -15,4 +15,17 @@ public function compute($value): float { return $value >= 0 ? 1.0 : 0.0; } + + /** + * @param float|int $value + * @param float|int $computedvalue + */ + public function differentiate($value, $computedvalue): float + { + if ($value === 0 || $value === 0.0) { + return 1; + } + + return 0; + } } diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php b/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php index da428a4e..8871b583 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php @@ -15,4 +15,13 @@ public function compute($value): float { return exp(-pow($value, 2)); } + + /** + * @param float|int $value + * @param float|int $calculatedvalue + */ + public function differentiate($value, $calculatedvalue): float + { + return -2 * $value * $calculatedvalue; + } } diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/HyperbolicTangent.php b/src/Phpml/NeuralNetwork/ActivationFunction/HyperbolicTangent.php index 63786060..7aa96148 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/HyperbolicTangent.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/HyperbolicTangent.php @@ -25,4 +25,13 @@ public function compute($value): float { return tanh($this->beta * $value); } + + /** + * @param float|int $value + * @param float|int $computedvalue + */ + public function differentiate($value, $computedvalue): float + { + return 1 - pow($computedvalue, 2); + } } diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php b/src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php index fc7ff628..88212d1a 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php @@ -25,4 +25,13 @@ public function compute($value): float { return $value >= 0 ? $value : $this->beta * $value; } + + /** + * @param float|int $value + * @param float|int $computedvalue + */ + public function differentiate($value, $computedvalue): float + { + return $computedvalue >= 0 ? 1.0 : $this->beta; + } } diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php b/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php index 4ae96031..edad3d6e 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php @@ -25,4 +25,13 @@ public function compute($value): float { return 1 / (1 + exp(-$this->beta * $value)); } + + /** + * @param float|int $value + * @param float|int $computedvalue + */ + public function differentiate($value, $computedvalue): float + { + return $computedvalue * (1 - $computedvalue); + } } diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php b/src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php index 2bb1cc7d..f8f8247c 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php @@ -13,7 +13,7 @@ class ThresholdedReLU implements ActivationFunction */ private $theta; - public function __construct(float $theta = 1.0) + public function __construct(float $theta = 0.0) { $this->theta = $theta; } @@ -25,4 +25,13 @@ public function compute($value): float { return $value > $this->theta ? $value : 0.0; } + + /** + * @param float|int $value + * @param float|int $calculatedvalue + */ + public function differentiate($value, $calculatedvalue): float + { + return $calculatedvalue >= $this->theta ? 1.0 : 0.0; + } } diff --git a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php index 1a997be2..bfec9297 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -9,6 +9,7 @@ use Phpml\Helper\Predictable; use Phpml\IncrementalEstimator; use Phpml\NeuralNetwork\ActivationFunction; +use Phpml\NeuralNetwork\ActivationFunction\Sigmoid; use Phpml\NeuralNetwork\Layer; use Phpml\NeuralNetwork\Node\Bias; use Phpml\NeuralNetwork\Node\Input; @@ -125,7 +126,10 @@ private function initNetwork(): void { $this->addInputLayer($this->inputLayerFeatures); $this->addNeuronLayers($this->hiddenLayers, $this->activationFunction); - $this->addNeuronLayers([count($this->classes)], $this->activationFunction); + + // Sigmoid function for the output layer as we want a value from 0 to 1. + $sigmoid = new Sigmoid(); + $this->addNeuronLayers([count($this->classes)], $sigmoid); $this->addBiasNodes(); $this->generateSynapses(); diff --git a/src/Phpml/NeuralNetwork/Node/Neuron.php b/src/Phpml/NeuralNetwork/Node/Neuron.php index 2dff6009..47d606da 100644 --- a/src/Phpml/NeuralNetwork/Node/Neuron.php +++ b/src/Phpml/NeuralNetwork/Node/Neuron.php @@ -26,6 +26,11 @@ class Neuron implements Node */ protected $output = 0.0; + /** + * @var float + */ + protected $z = 0.0; + public function __construct(?ActivationFunction $activationFunction = null) { $this->activationFunction = $activationFunction ?: new Sigmoid(); @@ -47,19 +52,25 @@ public function getSynapses() public function getOutput(): float { if ($this->output === 0.0) { - $sum = 0.0; + $this->z = 0; foreach ($this->synapses as $synapse) { - $sum += $synapse->getOutput(); + $this->z += $synapse->getOutput(); } - $this->output = $this->activationFunction->compute($sum); + $this->output = $this->activationFunction->compute($this->z); } return $this->output; } + public function getDerivative(): float + { + return $this->activationFunction->differentiate($this->z, $this->output); + } + public function reset(): void { $this->output = 0.0; + $this->z = 0.0; } } diff --git a/src/Phpml/NeuralNetwork/Training/Backpropagation.php b/src/Phpml/NeuralNetwork/Training/Backpropagation.php index fd09d95c..6c9af981 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -64,7 +64,7 @@ public function backpropagate(array $layers, $targetClass): void private function getSigma(Neuron $neuron, int $targetClass, int $key, bool $lastLayer): float { $neuronOutput = $neuron->getOutput(); - $sigma = $neuronOutput * (1 - $neuronOutput); + $sigma = $neuron->getDerivative(); if ($lastLayer) { $value = 0; diff --git a/tests/Phpml/Classification/MLPClassifierTest.php b/tests/Phpml/Classification/MLPClassifierTest.php index ef40618d..c46e297c 100644 --- a/tests/Phpml/Classification/MLPClassifierTest.php +++ b/tests/Phpml/Classification/MLPClassifierTest.php @@ -7,6 +7,11 @@ use Phpml\Classification\MLPClassifier; use Phpml\Exception\InvalidArgumentException; use Phpml\ModelManager; +use Phpml\NeuralNetwork\ActivationFunction; +use Phpml\NeuralNetwork\ActivationFunction\HyperbolicTangent; +use Phpml\NeuralNetwork\ActivationFunction\PReLU; +use Phpml\NeuralNetwork\ActivationFunction\Sigmoid; +use Phpml\NeuralNetwork\ActivationFunction\ThresholdedReLU; use Phpml\NeuralNetwork\Node\Neuron; use PHPUnit\Framework\TestCase; @@ -141,6 +146,33 @@ public function testBackpropagationLearningMulticlass(): void $this->assertEquals(4, $network->predict([0, 0, 0, 0, 0])); } + /** + * @dataProvider activationFunctionsProvider + */ + public function testBackpropagationActivationFunctions(ActivationFunction $activationFunction): void + { + $network = new MLPClassifier(5, [3], ['a', 'b'], 10000, $activationFunction); + $network->train( + [[1, 0, 0, 0, 0], [0, 1, 0, 0, 0], [0, 0, 1, 1, 0], [1, 1, 1, 1, 1]], + ['a', 'b', 'a', 'a'] + ); + + $this->assertEquals('a', $network->predict([1, 0, 0, 0, 0])); + $this->assertEquals('b', $network->predict([0, 1, 0, 0, 0])); + $this->assertEquals('a', $network->predict([0, 0, 1, 1, 0])); + $this->assertEquals('a', $network->predict([1, 1, 1, 1, 1])); + } + + public function activationFunctionsProvider(): array + { + return [ + [new Sigmoid()], + [new HyperbolicTangent()], + [new PReLU()], + [new ThresholdedReLU()], + ]; + } + public function testSaveAndRestore(): void { // Instantinate new Percetron trained for OR problem diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php index acc7977c..4e854786 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php @@ -27,4 +27,23 @@ public function binaryStepProvider(): array [0, -0.1], ]; } + + /** + * @dataProvider binaryStepDerivativeProvider + */ + public function testBinaryStepDerivative($expected, $value): void + { + $binaryStep = new BinaryStep(); + $activatedValue = $binaryStep->compute($value); + $this->assertEquals($expected, $binaryStep->differentiate($value, $activatedValue)); + } + + public function binaryStepDerivativeProvider(): array + { + return [ + [0, -1], + [1, 0], + [0, 1], + ]; + } } diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php index 2b087937..aace8bcb 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php @@ -29,4 +29,27 @@ public function gaussianProvider(): array [0, -3], ]; } + + /** + * @dataProvider gaussianDerivativeProvider + */ + public function testGaussianDerivative($expected, $value): void + { + $gaussian = new Gaussian(); + $activatedValue = $gaussian->compute($value); + $this->assertEquals($expected, $gaussian->differentiate($value, $activatedValue), '', 0.001); + } + + public function gaussianDerivativeProvider(): array + { + return [ + [0, -5], + [0.735, -1], + [0.779, -0.5], + [0, 0], + [-0.779, 0.5], + [-0.735, 1], + [0, 5], + ]; + } } diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php index 91e7eba1..629200e9 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php @@ -30,4 +30,28 @@ public function tanhProvider(): array [0.3, 0, 0], ]; } + + /** + * @dataProvider tanhDerivativeProvider + */ + public function testHyperbolicTangentDerivative($beta, $expected, $value): void + { + $tanh = new HyperbolicTangent($beta); + $activatedValue = $tanh->compute($value); + $this->assertEquals($expected, $tanh->differentiate($value, $activatedValue), '', 0.001); + } + + public function tanhDerivativeProvider(): array + { + return [ + [1.0, 0, -6], + [1.0, 0.419, -1], + [1.0, 1, 0], + [1.0, 0.419, 1], + [1.0, 0, 6], + [0.5, 0.786, 1], + [0.5, 0.786, -1], + [0.3, 1, 0], + ]; + } } diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php index f4070605..c9f565d3 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php @@ -29,4 +29,27 @@ public function preluProvider(): array [0.02, -0.06, -3], ]; } + + /** + * @dataProvider preluDerivativeProvider + */ + public function testPReLUDerivative($beta, $expected, $value): void + { + $prelu = new PReLU($beta); + $activatedValue = $prelu->compute($value); + $this->assertEquals($expected, $prelu->differentiate($value, $activatedValue)); + } + + public function preluDerivativeProvider(): array + { + return [ + [0.5, 0.5, -3], + [0.5, 1, 0], + [0.5, 1, 1], + [0.01, 1, 1], + [1, 1, 1], + [0.3, 1, 0.1], + [0.1, 0.1, -0.1], + ]; + } } diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php index 30b50f86..1028fb3b 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php @@ -30,4 +30,28 @@ public function sigmoidProvider(): array [2.0, 0, -3.75], ]; } + + /** + * @dataProvider sigmoidDerivativeProvider + */ + public function testSigmoidDerivative($beta, $expected, $value): void + { + $sigmoid = new Sigmoid($beta); + $activatedValue = $sigmoid->compute($value); + $this->assertEquals($expected, $sigmoid->differentiate($value, $activatedValue), '', 0.001); + } + + public function sigmoidDerivativeProvider(): array + { + return [ + [1.0, 0, -10], + [1, 0.006, -5], + [1.0, 0.25, 0], + [1, 0.006, 5], + [1.0, 0, 10], + [2.0, 0.25, 0], + [0.5, 0.246, 0.5], + [0.5, 0.241, 0.75], + ]; + } } diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php index f46ff024..4db0418e 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php @@ -28,4 +28,26 @@ public function thresholdProvider(): array [0.9, 0, 0.1], ]; } + + /** + * @dataProvider thresholdDerivativeProvider + */ + public function testThresholdedReLUDerivative($theta, $expected, $value): void + { + $thresholdedReLU = new ThresholdedReLU($theta); + $activatedValue = $thresholdedReLU->compute($value); + $this->assertEquals($expected, $thresholdedReLU->differentiate($value, $activatedValue)); + } + + public function thresholdDerivativeProvider(): array + { + return [ + [0, 1, 1], + [0, 1, 0], + [0.5, 1, 1], + [0.5, 1, 1], + [0.5, 0, 0], + [2, 0, -1], + ]; + } } From d953ef6bfc8ea1054e66c3b052fe7e6ce8dc24e8 Mon Sep 17 00:00:00 2001 From: Yuji Uchiyama Date: Fri, 12 Jan 2018 18:53:43 +0900 Subject: [PATCH 221/328] Fix the implementation of conjugate gradient method (#184) * Add unit tests for optimizers * Fix ConjugateGradient * Fix coding style * Fix namespace --- .../Helper/Optimizer/ConjugateGradient.php | 75 +++++++++++-------- .../Optimizer/ConjugateGradientTest.php | 65 ++++++++++++++++ tests/Phpml/Helper/Optimizer/GDTest.php | 65 ++++++++++++++++ .../Helper/Optimizer/StochasticGDTest.php | 65 ++++++++++++++++ 4 files changed, 240 insertions(+), 30 deletions(-) create mode 100644 tests/Phpml/Helper/Optimizer/ConjugateGradientTest.php create mode 100644 tests/Phpml/Helper/Optimizer/GDTest.php create mode 100644 tests/Phpml/Helper/Optimizer/StochasticGDTest.php diff --git a/src/Phpml/Helper/Optimizer/ConjugateGradient.php b/src/Phpml/Helper/Optimizer/ConjugateGradient.php index a034af00..67210abd 100644 --- a/src/Phpml/Helper/Optimizer/ConjugateGradient.php +++ b/src/Phpml/Helper/Optimizer/ConjugateGradient.php @@ -31,7 +31,7 @@ public function runOptimization(array $samples, array $targets, Closure $gradien for ($i = 0; $i < $this->maxIterations; ++$i) { // Obtain α that minimizes f(θ + α.d) - $alpha = $this->getAlpha(array_sum($d)); + $alpha = $this->getAlpha($d); // θ(k+1) = θ(k) + α.d $thetaNew = $this->getNewTheta($alpha, $d); @@ -63,7 +63,23 @@ public function runOptimization(array $samples, array $targets, Closure $gradien */ protected function gradient(array $theta): array { - [, $gradient] = parent::gradient($theta); + [, $updates, $penalty] = parent::gradient($theta); + + // Calculate gradient for each dimension + $gradient = []; + for ($i = 0; $i <= $this->dimensions; ++$i) { + if ($i === 0) { + $gradient[$i] = array_sum($updates); + } else { + $col = array_column($this->samples, $i - 1); + $error = 0; + foreach ($col as $index => $val) { + $error += $val * $updates[$index]; + } + + $gradient[$i] = $error + $penalty * $theta[$i]; + } + } return $gradient; } @@ -92,14 +108,14 @@ protected function cost(array $theta): float * b-1) If cost function decreases, continue enlarging alpha * b-2) If cost function increases, take the midpoint and try again */ - protected function getAlpha(float $d): float + protected function getAlpha(array $d): float { - $small = 0.0001 * $d; - $large = 0.01 * $d; + $small = MP::muls($d, 0.0001); + $large = MP::muls($d, 0.01); // Obtain θ + α.d for two initial values, x0 and x1 - $x0 = MP::adds($this->theta, $small); - $x1 = MP::adds($this->theta, $large); + $x0 = MP::add($this->theta, $small); + $x1 = MP::add($this->theta, $large); $epsilon = 0.0001; $iteration = 0; @@ -123,12 +139,20 @@ protected function getAlpha(float $d): float $error = $fx1 / $this->dimensions; } while ($error <= $epsilon || $iteration++ < 10); - // Return α = θ / d - if ($d == 0) { - return $x1[0] - $this->theta[0]; + // Return α = θ / d + // For accuracy, choose a dimension which maximize |d[i]| + $imax = 0; + for ($i = 1; $i <= $this->dimensions; ++$i) { + if (abs($d[$i]) > abs($d[$imax])) { + $imax = $i; + } + } + + if ($d[$imax] == 0) { + return $x1[$imax] - $this->theta[$imax]; } - return ($x1[0] - $this->theta[0]) / $d; + return ($x1[$imax] - $this->theta[$imax]) / $d[$imax]; } /** @@ -139,22 +163,7 @@ protected function getAlpha(float $d): float */ protected function getNewTheta(float $alpha, array $d): array { - $theta = $this->theta; - - for ($i = 0; $i < $this->dimensions + 1; ++$i) { - if ($i === 0) { - $theta[$i] += $alpha * array_sum($d); - } else { - $sum = 0.0; - foreach ($this->samples as $si => $sample) { - $sum += $sample[$i - 1] * $d[$si] * $alpha; - } - - $theta[$i] += $sum; - } - } - - return $theta; + return MP::add($this->theta, MP::muls($d, $alpha)); } /** @@ -168,10 +177,16 @@ protected function getNewTheta(float $alpha, array $d): array */ protected function getBeta(array $newTheta): float { - $dNew = array_sum($this->gradient($newTheta)); - $dOld = array_sum($this->gradient($this->theta)) + 1e-100; + $gNew = $this->gradient($newTheta); + $gOld = $this->gradient($this->theta); + $dNew = 0; + $dOld = 1e-100; + for ($i = 0; $i <= $this->dimensions; ++$i) { + $dNew += $gNew[$i] ** 2; + $dOld += $gOld[$i] ** 2; + } - return $dNew ** 2 / $dOld ** 2; + return $dNew / $dOld; } /** diff --git a/tests/Phpml/Helper/Optimizer/ConjugateGradientTest.php b/tests/Phpml/Helper/Optimizer/ConjugateGradientTest.php new file mode 100644 index 00000000..b05f998f --- /dev/null +++ b/tests/Phpml/Helper/Optimizer/ConjugateGradientTest.php @@ -0,0 +1,65 @@ +runOptimization($samples, $targets, $callback); + + $this->assertEquals([-1, 2], $theta, '', 0.1); + } + + public function testRunOptimization2Dim(): void + { + // 100 samples from y = -1 + 2x0 - 3x1 (i.e. theta = [-1, 2, -3]) + $samples = []; + $targets = []; + for ($i = 0; $i < 100; ++$i) { + $x0 = intval($i / 10) / 10; + $x1 = ($i % 10) / 10; + $samples[] = [$x0, $x1]; + $targets[] = -1 + 2 * $x0 - 3 * $x1; + } + + $callback = function ($theta, $sample, $target) { + $y = $theta[0] + $theta[1] * $sample[0] + $theta[2] * $sample[1]; + $cost = ($y - $target) ** 2 / 2; + $grad = $y - $target; + + return [$cost, $grad]; + }; + + $optimizer = new ConjugateGradient(2); + $optimizer->setChangeThreshold(1e-6); + + $theta = $optimizer->runOptimization($samples, $targets, $callback); + + $this->assertEquals([-1, 2, -3], $theta, '', 0.1); + } +} diff --git a/tests/Phpml/Helper/Optimizer/GDTest.php b/tests/Phpml/Helper/Optimizer/GDTest.php new file mode 100644 index 00000000..c68e3185 --- /dev/null +++ b/tests/Phpml/Helper/Optimizer/GDTest.php @@ -0,0 +1,65 @@ +runOptimization($samples, $targets, $callback); + + $this->assertEquals([-1, 2], $theta, '', 0.1); + } + + public function testRunOptimization2Dim(): void + { + // 100 samples from y = -1 + 2x0 - 3x1 (i.e. theta = [-1, 2, -3]) + $samples = []; + $targets = []; + for ($i = 0; $i < 100; ++$i) { + $x0 = intval($i / 10) / 10; + $x1 = ($i % 10) / 10; + $samples[] = [$x0, $x1]; + $targets[] = -1 + 2 * $x0 - 3 * $x1; + } + + $callback = function ($theta, $sample, $target) { + $y = $theta[0] + $theta[1] * $sample[0] + $theta[2] * $sample[1]; + $cost = ($y - $target) ** 2 / 2; + $grad = $y - $target; + + return [$cost, $grad]; + }; + + $optimizer = new GD(2); + $optimizer->setChangeThreshold(1e-6); + + $theta = $optimizer->runOptimization($samples, $targets, $callback); + + $this->assertEquals([-1, 2, -3], $theta, '', 0.1); + } +} diff --git a/tests/Phpml/Helper/Optimizer/StochasticGDTest.php b/tests/Phpml/Helper/Optimizer/StochasticGDTest.php new file mode 100644 index 00000000..6f6e469b --- /dev/null +++ b/tests/Phpml/Helper/Optimizer/StochasticGDTest.php @@ -0,0 +1,65 @@ +runOptimization($samples, $targets, $callback); + + $this->assertEquals([-1, 2], $theta, '', 0.1); + } + + public function testRunOptimization2Dim(): void + { + // 100 samples from y = -1 + 2x0 - 3x1 (i.e. theta = [-1, 2, -3]) + $samples = []; + $targets = []; + for ($i = 0; $i < 100; ++$i) { + $x0 = intval($i / 10) / 10; + $x1 = ($i % 10) / 10; + $samples[] = [$x0, $x1]; + $targets[] = -1 + 2 * $x0 - 3 * $x1; + } + + $callback = function ($theta, $sample, $target) { + $y = $theta[0] + $theta[1] * $sample[0] + $theta[2] * $sample[1]; + $cost = ($y - $target) ** 2 / 2; + $grad = $y - $target; + + return [$cost, $grad]; + }; + + $optimizer = new StochasticGD(2); + $optimizer->setChangeThreshold(1e-6); + + $theta = $optimizer->runOptimization($samples, $targets, $callback); + + $this->assertEquals([-1, 2, -3], $theta, '', 0.1); + } +} From 7435bece34e29429adae5a09de10354511602d05 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 12 Jan 2018 10:54:20 +0100 Subject: [PATCH 222/328] Add test for Pipeline save and restore with ModelManager (#191) --- tests/Phpml/PipelineTest.php | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/Phpml/PipelineTest.php b/tests/Phpml/PipelineTest.php index e45675d4..86ff2a9f 100644 --- a/tests/Phpml/PipelineTest.php +++ b/tests/Phpml/PipelineTest.php @@ -7,6 +7,7 @@ use Phpml\Classification\SVC; use Phpml\FeatureExtraction\TfIdfTransformer; use Phpml\FeatureExtraction\TokenCountVectorizer; +use Phpml\ModelManager; use Phpml\Pipeline; use Phpml\Preprocessing\Imputer; use Phpml\Preprocessing\Imputer\Strategy\MostFrequentStrategy; @@ -104,4 +105,40 @@ public function testPipelineTransformers(): void $this->assertEquals($expected, $predicted); } + + public function testSaveAndRestore(): void + { + $pipeline = new Pipeline([ + new TokenCountVectorizer(new WordTokenizer()), + new TfIdfTransformer(), + ], new SVC()); + + $pipeline->train([ + 'Hello Paul', + 'Hello Martin', + 'Goodbye Tom', + 'Hello John', + 'Goodbye Alex', + 'Bye Tony', + ], [ + 'greetings', + 'greetings', + 'farewell', + 'greetings', + 'farewell', + 'farewell', + ]); + + $testSamples = ['Hello Max', 'Goodbye Mark']; + $predicted = $pipeline->predict($testSamples); + + $filepath = tempnam(sys_get_temp_dir(), uniqid('pipeline-test', true)); + $modelManager = new ModelManager(); + $modelManager->saveToFile($pipeline, $filepath); + + $restoredClassifier = $modelManager->restoreFromFile($filepath); + $this->assertEquals($pipeline, $restoredClassifier); + $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + unlink($filepath); + } } From 89268ecb1afd171943932254c8e4f92077fadac0 Mon Sep 17 00:00:00 2001 From: Jeroen van den Enden Date: Thu, 25 Jan 2018 16:12:13 +0100 Subject: [PATCH 223/328] Throw exception when libsvm command fails to run (#200) * Throw exception when libsvm command fails to run * Update CS --- src/Phpml/Exception/LibsvmCommandException.php | 15 +++++++++++++++ .../SupportVectorMachine/SupportVectorMachine.php | 15 +++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 src/Phpml/Exception/LibsvmCommandException.php diff --git a/src/Phpml/Exception/LibsvmCommandException.php b/src/Phpml/Exception/LibsvmCommandException.php new file mode 100644 index 00000000..01d80797 --- /dev/null +++ b/src/Phpml/Exception/LibsvmCommandException.php @@ -0,0 +1,15 @@ +buildTrainCommand($trainingSetFileName, $modelFileName); $output = ''; - exec(escapeshellcmd($command), $output); + exec(escapeshellcmd($command), $output, $return); + + if ($return !== 0) { + throw LibsvmCommandException::failedToRun($command); + } $this->model = file_get_contents($modelFileName); @@ -168,6 +173,8 @@ public function getModel(): string /** * @return array|string + * + * @throws LibsvmCommandException */ public function predict(array $samples) { @@ -178,7 +185,11 @@ public function predict(array $samples) $command = sprintf('%ssvm-predict%s %s %s %s', $this->binPath, $this->getOSExtension(), $testSetFileName, $modelFileName, $outputFileName); $output = ''; - exec(escapeshellcmd($command), $output); + exec(escapeshellcmd($command), $output, $return); + + if ($return !== 0) { + throw LibsvmCommandException::failedToRun($command); + } $predictions = file_get_contents($outputFileName); From ba7114a3f7880071226c8870826456ab62497741 Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Fri, 26 Jan 2018 22:07:22 +0100 Subject: [PATCH 224/328] Add libsvm exception tests (#202) --- .../Exception/LibsvmCommandException.php | 4 +-- .../SupportVectorMachine.php | 25 ++++++++++--------- .../SupportVectorMachineTest.php | 19 ++++++++++++++ 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/Phpml/Exception/LibsvmCommandException.php b/src/Phpml/Exception/LibsvmCommandException.php index 01d80797..a9d11e3a 100644 --- a/src/Phpml/Exception/LibsvmCommandException.php +++ b/src/Phpml/Exception/LibsvmCommandException.php @@ -8,8 +8,8 @@ class LibsvmCommandException extends Exception { - public static function failedToRun(string $command): self + public static function failedToRun(string $command, string $reason): self { - return new self(sprintf('Failed running libsvm command: "%s"', $command)); + return new self(sprintf('Failed running libsvm command: "%s" with reason: "%s"', $command, $reason)); } } diff --git a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php index f66d5d75..ce7a7bab 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -153,16 +153,17 @@ public function train(array $samples, array $targets): void $modelFileName = $trainingSetFileName.'-model'; $command = $this->buildTrainCommand($trainingSetFileName, $modelFileName); - $output = ''; - exec(escapeshellcmd($command), $output, $return); + $output = []; + exec(escapeshellcmd($command).' 2>&1', $output, $return); + + unlink($trainingSetFileName); if ($return !== 0) { - throw LibsvmCommandException::failedToRun($command); + throw LibsvmCommandException::failedToRun($command, array_pop($output)); } $this->model = file_get_contents($modelFileName); - unlink($trainingSetFileName); unlink($modelFileName); } @@ -184,19 +185,19 @@ public function predict(array $samples) $outputFileName = $testSetFileName.'-output'; $command = sprintf('%ssvm-predict%s %s %s %s', $this->binPath, $this->getOSExtension(), $testSetFileName, $modelFileName, $outputFileName); - $output = ''; - exec(escapeshellcmd($command), $output, $return); - - if ($return !== 0) { - throw LibsvmCommandException::failedToRun($command); - } - - $predictions = file_get_contents($outputFileName); + $output = []; + exec(escapeshellcmd($command).' 2>&1', $output, $return); unlink($testSetFileName); unlink($modelFileName); + $predictions = file_get_contents($outputFileName); + unlink($outputFileName); + if ($return !== 0) { + throw LibsvmCommandException::failedToRun($command, array_pop($output)); + } + if (in_array($this->type, [Type::C_SVC, Type::NU_SVC])) { $predictions = DataTransformer::predictions($predictions, $this->targets); } else { diff --git a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php index 180b8d33..466c962a 100644 --- a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php @@ -5,6 +5,7 @@ namespace Phpml\Tests\SupportVectorMachine; use Phpml\Exception\InvalidArgumentException; +use Phpml\Exception\LibsvmCommandException; use Phpml\SupportVectorMachine\Kernel; use Phpml\SupportVectorMachine\SupportVectorMachine; use Phpml\SupportVectorMachine\Type; @@ -105,4 +106,22 @@ public function testThrowExceptionWhenFileIsNotFoundInBinPath(): void $svm = new SupportVectorMachine(Type::C_SVC, Kernel::RBF); $svm->setBinPath('var'); } + + public function testThrowExceptionWhenLibsvmFailsDuringTrain(): void + { + $this->expectException(LibsvmCommandException::class); + $this->expectExceptionMessage('ERROR: unknown svm type'); + + $svm = new SupportVectorMachine(99, Kernel::RBF); + $svm->train([], []); + } + + public function testThrowExceptionWhenLibsvmFailsDuringPredict(): void + { + $this->expectException(LibsvmCommandException::class); + $this->expectExceptionMessage('can\'t open model file'); + + $svm = new SupportVectorMachine(Type::C_SVC, Kernel::RBF); + $svm->predict([1]); + } } From 554c86af6833e224017c0ef53fea32c36220b76e Mon Sep 17 00:00:00 2001 From: Yuji Uchiyama Date: Tue, 30 Jan 2018 02:06:21 +0900 Subject: [PATCH 225/328] Choose averaging method in classification report (#205) * Fix testcases of ClassificationReport * Fix averaging method in ClassificationReport * Fix divided by zero if labels are empty * Fix calculation of f1score * Add averaging methods (not completed) * Implement weighted average method * Extract counts to properties * Fix default to macro average * Implement micro average method * Fix style * Update docs * Fix styles --- .../metric/classification-report.md | 7 + src/Phpml/Metric/ClassificationReport.php | 137 ++++++++++++++---- .../Phpml/Metric/ClassificationReportTest.php | 86 ++++++++++- 3 files changed, 199 insertions(+), 31 deletions(-) diff --git a/docs/machine-learning/metric/classification-report.md b/docs/machine-learning/metric/classification-report.md index 53490b2c..a0a6accd 100644 --- a/docs/machine-learning/metric/classification-report.md +++ b/docs/machine-learning/metric/classification-report.md @@ -18,6 +18,13 @@ $predictedLabels = ['cat', 'cat', 'bird', 'bird', 'ant']; $report = new ClassificationReport($actualLabels, $predictedLabels); ``` +Optionally you can provide the following parameter: + +* $average - (int) averaging method for multi-class classification + * `ClassificationReport::MICRO_AVERAGE` = 1 + * `ClassificationReport::MACRO_AVERAGE` = 2 (default) + * `ClassificationReport::WEIGHTED_AVERAGE` = 3 + ### Metrics After creating the report you can draw its individual metrics: diff --git a/src/Phpml/Metric/ClassificationReport.php b/src/Phpml/Metric/ClassificationReport.php index 0c3198fd..755d78b5 100644 --- a/src/Phpml/Metric/ClassificationReport.php +++ b/src/Phpml/Metric/ClassificationReport.php @@ -4,22 +4,30 @@ namespace Phpml\Metric; +use Phpml\Exception\InvalidArgumentException; + class ClassificationReport { + public const MICRO_AVERAGE = 1; + + public const MACRO_AVERAGE = 2; + + public const WEIGHTED_AVERAGE = 3; + /** * @var array */ - private $precision = []; + private $truePositive = []; /** * @var array */ - private $recall = []; + private $falsePositive = []; /** * @var array */ - private $f1score = []; + private $falseNegative = []; /** * @var array @@ -29,26 +37,33 @@ class ClassificationReport /** * @var array */ - private $average = []; + private $precision = []; - public function __construct(array $actualLabels, array $predictedLabels) - { - $truePositive = $falsePositive = $falseNegative = $this->support = self::getLabelIndexedArray($actualLabels, $predictedLabels); + /** + * @var array + */ + private $recall = []; - foreach ($actualLabels as $index => $actual) { - $predicted = $predictedLabels[$index]; - ++$this->support[$actual]; + /** + * @var array + */ + private $f1score = []; - if ($actual === $predicted) { - ++$truePositive[$actual]; - } else { - ++$falsePositive[$predicted]; - ++$falseNegative[$actual]; - } + /** + * @var array + */ + private $average = []; + + public function __construct(array $actualLabels, array $predictedLabels, int $average = self::MACRO_AVERAGE) + { + $averagingMethods = range(self::MICRO_AVERAGE, self::WEIGHTED_AVERAGE); + if (!in_array($average, $averagingMethods)) { + throw new InvalidArgumentException('Averaging method must be MICRO_AVERAGE, MACRO_AVERAGE or WEIGHTED_AVERAGE'); } - $this->computeMetrics($truePositive, $falsePositive, $falseNegative); - $this->computeAverage(); + $this->aggregateClassificationResults($actualLabels, $predictedLabels); + $this->computeMetrics(); + $this->computeAverage($average); } public function getPrecision(): array @@ -76,20 +91,73 @@ public function getAverage(): array return $this->average; } - private function computeMetrics(array $truePositive, array $falsePositive, array $falseNegative): void + private function aggregateClassificationResults(array $actualLabels, array $predictedLabels): void { - foreach ($truePositive as $label => $tp) { - $this->precision[$label] = $this->computePrecision($tp, $falsePositive[$label]); - $this->recall[$label] = $this->computeRecall($tp, $falseNegative[$label]); + $truePositive = $falsePositive = $falseNegative = $support = self::getLabelIndexedArray($actualLabels, $predictedLabels); + + foreach ($actualLabels as $index => $actual) { + $predicted = $predictedLabels[$index]; + ++$support[$actual]; + + if ($actual === $predicted) { + ++$truePositive[$actual]; + } else { + ++$falsePositive[$predicted]; + ++$falseNegative[$actual]; + } + } + + $this->truePositive = $truePositive; + $this->falsePositive = $falsePositive; + $this->falseNegative = $falseNegative; + $this->support = $support; + } + + private function computeMetrics(): void + { + foreach ($this->truePositive as $label => $tp) { + $this->precision[$label] = $this->computePrecision($tp, $this->falsePositive[$label]); + $this->recall[$label] = $this->computeRecall($tp, $this->falseNegative[$label]); $this->f1score[$label] = $this->computeF1Score((float) $this->precision[$label], (float) $this->recall[$label]); } } - private function computeAverage(): void + private function computeAverage(int $average): void + { + switch ($average) { + case self::MICRO_AVERAGE: + $this->computeMicroAverage(); + + return; + case self::MACRO_AVERAGE: + $this->computeMacroAverage(); + + return; + case self::WEIGHTED_AVERAGE: + $this->computeWeightedAverage(); + + return; + } + } + + private function computeMicroAverage(): void + { + $truePositive = array_sum($this->truePositive); + $falsePositive = array_sum($this->falsePositive); + $falseNegative = array_sum($this->falseNegative); + + $precision = $this->computePrecision($truePositive, $falsePositive); + $recall = $this->computeRecall($truePositive, $falseNegative); + $f1score = $this->computeF1Score((float) $precision, (float) $recall); + + $this->average = compact('precision', 'recall', 'f1score'); + } + + private function computeMacroAverage(): void { foreach (['precision', 'recall', 'f1score'] as $metric) { - $values = array_filter($this->{$metric}); - if (empty($values)) { + $values = $this->{$metric}; + if (count($values) == 0) { $this->average[$metric] = 0.0; continue; @@ -99,6 +167,25 @@ private function computeAverage(): void } } + private function computeWeightedAverage(): void + { + foreach (['precision', 'recall', 'f1score'] as $metric) { + $values = $this->{$metric}; + if (count($values) == 0) { + $this->average[$metric] = 0.0; + + continue; + } + + $sum = 0; + foreach ($values as $i => $value) { + $sum += $value * $this->support[$i]; + } + + $this->average[$metric] = $sum / array_sum($this->support); + } + } + /** * @return float|string */ diff --git a/tests/Phpml/Metric/ClassificationReportTest.php b/tests/Phpml/Metric/ClassificationReportTest.php index 483f7696..4c4f01fa 100644 --- a/tests/Phpml/Metric/ClassificationReportTest.php +++ b/tests/Phpml/Metric/ClassificationReportTest.php @@ -4,6 +4,7 @@ namespace Phpml\Tests\Metric; +use Phpml\Exception\InvalidArgumentException; use Phpml\Metric\ClassificationReport; use PHPUnit\Framework\TestCase; @@ -36,10 +37,12 @@ public function testClassificationReportGenerateWithStringLabels(): void 'ant' => 1, 'bird' => 3, ]; + + // ClassificationReport uses macro-averaging as default $average = [ - 'precision' => 0.75, - 'recall' => 0.83, - 'f1score' => 0.73, + 'precision' => 0.5, // (1/2 + 0 + 1) / 3 = 1/2 + 'recall' => 0.56, // (1 + 0 + 2/3) / 3 = 5/9 + 'f1score' => 0.49, // (2/3 + 0 + 4/5) / 3 = 22/45 ]; $this->assertEquals($precision, $report->getPrecision(), '', 0.01); @@ -77,9 +80,9 @@ public function testClassificationReportGenerateWithNumericLabels(): void 2 => 3, ]; $average = [ - 'precision' => 0.75, - 'recall' => 0.83, - 'f1score' => 0.73, + 'precision' => 0.5, + 'recall' => 0.56, + 'f1score' => 0.49, ]; $this->assertEquals($precision, $report->getPrecision(), '', 0.01); @@ -89,6 +92,63 @@ public function testClassificationReportGenerateWithNumericLabels(): void $this->assertEquals($average, $report->getAverage(), '', 0.01); } + public function testClassificationReportAverageOutOfRange(): void + { + $labels = ['cat', 'ant', 'bird', 'bird', 'bird']; + $predicted = ['cat', 'cat', 'bird', 'bird', 'ant']; + + $this->expectException(InvalidArgumentException::class); + $report = new ClassificationReport($labels, $predicted, 0); + } + + public function testClassificationReportMicroAverage(): void + { + $labels = ['cat', 'ant', 'bird', 'bird', 'bird']; + $predicted = ['cat', 'cat', 'bird', 'bird', 'ant']; + + $report = new ClassificationReport($labels, $predicted, ClassificationReport::MICRO_AVERAGE); + + $average = [ + 'precision' => 0.6, // TP / (TP + FP) = (1 + 0 + 2) / (2 + 1 + 2) = 3/5 + 'recall' => 0.6, // TP / (TP + FN) = (1 + 0 + 2) / (1 + 1 + 3) = 3/5 + 'f1score' => 0.6, // Harmonic mean of precision and recall + ]; + + $this->assertEquals($average, $report->getAverage(), '', 0.01); + } + + public function testClassificationReportMacroAverage(): void + { + $labels = ['cat', 'ant', 'bird', 'bird', 'bird']; + $predicted = ['cat', 'cat', 'bird', 'bird', 'ant']; + + $report = new ClassificationReport($labels, $predicted, ClassificationReport::MACRO_AVERAGE); + + $average = [ + 'precision' => 0.5, // (1/2 + 0 + 1) / 3 = 1/2 + 'recall' => 0.56, // (1 + 0 + 2/3) / 3 = 5/9 + 'f1score' => 0.49, // (2/3 + 0 + 4/5) / 3 = 22/45 + ]; + + $this->assertEquals($average, $report->getAverage(), '', 0.01); + } + + public function testClassificationReportWeightedAverage(): void + { + $labels = ['cat', 'ant', 'bird', 'bird', 'bird']; + $predicted = ['cat', 'cat', 'bird', 'bird', 'ant']; + + $report = new ClassificationReport($labels, $predicted, ClassificationReport::WEIGHTED_AVERAGE); + + $average = [ + 'precision' => 0.7, // (1/2 * 1 + 0 * 1 + 1 * 3) / 5 = 7/10 + 'recall' => 0.6, // (1 * 1 + 0 * 1 + 2/3 * 3) / 5 = 3/5 + 'f1score' => 0.61, // (2/3 * 1 + 0 * 1 + 4/5 * 3) / 5 = 46/75 + ]; + + $this->assertEquals($average, $report->getAverage(), '', 0.01); + } + public function testPreventDivideByZeroWhenTruePositiveAndFalsePositiveSumEqualsZero(): void { $labels = [1, 2]; @@ -129,4 +189,18 @@ public function testPreventDividedByZeroWhenPredictedLabelsAllNotMatch(): void 'f1score' => 0, ], $report->getAverage(), '', 0.01); } + + public function testPreventDividedByZeroWhenLabelsAreEmpty(): void + { + $labels = []; + $predicted = []; + + $report = new ClassificationReport($labels, $predicted); + + $this->assertEquals([ + 'precision' => 0, + 'recall' => 0, + 'f1score' => 0, + ], $report->getAverage(), '', 0.01); + } } From 4ab73eec5bd40d013a226658c3e77b5ccf9b6687 Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Tue, 30 Jan 2018 22:05:47 +0100 Subject: [PATCH 226/328] Force all errors when running tests (#203) --- phpunit.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/phpunit.xml b/phpunit.xml index 455f8bbc..fd86a5ce 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -16,4 +16,8 @@ + + + + From 9f0723f7d0dfc4b076f720873fcb070213d88ad8 Mon Sep 17 00:00:00 2001 From: Yuji Uchiyama Date: Thu, 1 Feb 2018 03:20:50 +0900 Subject: [PATCH 227/328] Fix documentation of ClassificationReport (#209) * Fix values in example code * Remove inconsistent empty lines --- docs/machine-learning/metric/classification-report.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/machine-learning/metric/classification-report.md b/docs/machine-learning/metric/classification-report.md index a0a6accd..53f125b8 100644 --- a/docs/machine-learning/metric/classification-report.md +++ b/docs/machine-learning/metric/classification-report.md @@ -36,7 +36,6 @@ After creating the report you can draw its individual metrics: ``` $precision = $report->getPrecision(); - // $precision = ['cat' => 0.5, 'ant' => 0.0, 'bird' => 1.0]; ``` @@ -63,6 +62,5 @@ $report->getSupport(); // ['cat' => 1, 'ant' => 1, 'bird' => 3] $report->getAverage(); -// ['precision' => 0.75, 'recall' => 0.83, 'f1score' => 0.73] - +// ['precision' => 0.5, 'recall' => 0.56, 'f1score' => 0.49] ``` From 4954f4d40e4a1c7045b069ed40ab172f4e787ddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Wed, 31 Jan 2018 19:25:22 +0100 Subject: [PATCH 228/328] Enhancement: Keep packages sorted in composer.json (#210) --- composer.json | 7 +++++-- composer.lock | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 149ce27d..0e97bba4 100644 --- a/composer.json +++ b/composer.json @@ -15,10 +15,13 @@ "php": "^7.1" }, "require-dev": { + "phpstan/phpstan-shim": "^0.9", "phpunit/phpunit": "^6.5", - "symplify/easy-coding-standard": "^3.1", "symplify/coding-standard": "^3.1", - "phpstan/phpstan-shim": "^0.9" + "symplify/easy-coding-standard": "^3.1" + }, + "config": { + "sort-packages": true }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 93187863..bab8a711 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "1efc0df70ee999e80ff6a3770fd3b6c0", + "content-hash": "7f8e0516fc20861caade713f6fe241dc", "packages": [], "packages-dev": [ { From 498937cf641cb8ba59e76266ec7aa00f160e79be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Wed, 31 Jan 2018 19:29:19 +0100 Subject: [PATCH 229/328] Enhancement: Reference phpunit schema (#211) --- phpunit.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/phpunit.xml b/phpunit.xml index fd86a5ce..75520cc9 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,5 +1,7 @@ Date: Wed, 31 Jan 2018 19:32:18 +0100 Subject: [PATCH 230/328] Fix: Use enforceTimeLimit instead of beStrictAboutTestSize (#212) --- phpunit.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit.xml b/phpunit.xml index 75520cc9..4a74eb65 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -5,8 +5,8 @@ colors="true" beStrictAboutTestsThatDoNotTestAnything="true" beStrictAboutOutputDuringTests="true" - beStrictAboutTestSize="true" beStrictAboutChangesToGlobalState="true" + enforceTimeLimit="true" > tests/* From 695a62d75f12b71496947e7b2a90b02a4a542deb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Wed, 31 Jan 2018 19:33:57 +0100 Subject: [PATCH 231/328] Fix: Option --dev has been deprecated (#213) --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f415daf7..7a0f3af9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ before_install: install: - if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then /usr/bin/env bash bin/handle_brew_pkg.sh "${_PHP}" ; fi - curl -s http://getcomposer.org/installer | php - - php composer.phar install --dev --no-interaction --ignore-platform-reqs + - php composer.phar install --no-interaction --ignore-platform-reqs script: - vendor/bin/phpunit $PHPUNIT_FLAGS From 10070d97fd147099ae023b19f81f9bf514c5ed88 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 31 Jan 2018 20:06:51 +0100 Subject: [PATCH 232/328] Normalize composer.json with localheinz/json-normalizer (#214) --- composer.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 0e97bba4..acb34dd5 100644 --- a/composer.json +++ b/composer.json @@ -2,9 +2,17 @@ "name": "php-ai/php-ml", "type": "library", "description": "PHP-ML - Machine Learning library for PHP", - "license": "MIT", - "keywords": ["machine learning","pattern recognition","neural network","computational learning theory","artificial intelligence","data science","feature extraction"], + "keywords": [ + "machine learning", + "pattern recognition", + "neural network", + "computational learning theory", + "artificial intelligence", + "data science", + "feature extraction" + ], "homepage": "https://github.com/php-ai/php-ml", + "license": "MIT", "authors": [ { "name": "Arkadiusz Kondas", From e3189210761121e20b38a356457b7d61c8e6fe0c Mon Sep 17 00:00:00 2001 From: Jonathan Baldie Date: Wed, 31 Jan 2018 20:44:44 +0000 Subject: [PATCH 233/328] Fix string representation of integer labels issue in NaiveBayes (#206) * Update NaiveBayes.php This fixes an issue using string labels that are string representations of integers, e.g. "1998" getting cast to (int)1998. * Update NaiveBayes.php fixes superfluous whitespace error * added tests for naive bayes with numeric labels * added array_unique * nested array_flips for speed * nested the array flips inside the array map * to appear style CI test --- src/Phpml/Classification/NaiveBayes.php | 3 +- tests/Phpml/Classification/NaiveBayesTest.php | 59 +++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/Phpml/Classification/NaiveBayes.php b/src/Phpml/Classification/NaiveBayes.php index 38c857db..8f092575 100644 --- a/src/Phpml/Classification/NaiveBayes.php +++ b/src/Phpml/Classification/NaiveBayes.php @@ -66,8 +66,7 @@ public function train(array $samples, array $targets): void $this->sampleCount = count($this->samples); $this->featureCount = count($this->samples[0]); - $labelCounts = array_count_values($this->targets); - $this->labels = array_keys($labelCounts); + $this->labels = array_map('strval', array_flip(array_flip($this->targets))); foreach ($this->labels as $label) { $samples = $this->getSamplesByLabel($label); $this->p[$label] = count($samples) / $this->sampleCount; diff --git a/tests/Phpml/Classification/NaiveBayesTest.php b/tests/Phpml/Classification/NaiveBayesTest.php index 8312e9ca..7db8645d 100644 --- a/tests/Phpml/Classification/NaiveBayesTest.php +++ b/tests/Phpml/Classification/NaiveBayesTest.php @@ -68,4 +68,63 @@ public function testSaveAndRestore(): void $this->assertEquals($classifier, $restoredClassifier); $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); } + + public function testPredictSimpleNumericLabels(): void + { + $samples = [[5, 1, 1], [1, 5, 1], [1, 1, 5]]; + $labels = ['1996', '1997', '1998']; + + $classifier = new NaiveBayes(); + $classifier->train($samples, $labels); + + $this->assertEquals('1996', $classifier->predict([3, 1, 1])); + $this->assertEquals('1997', $classifier->predict([1, 4, 1])); + $this->assertEquals('1998', $classifier->predict([1, 1, 6])); + } + + public function testPredictArrayOfSamplesNumericalLabels(): void + { + $trainSamples = [[5, 1, 1], [1, 5, 1], [1, 1, 5]]; + $trainLabels = ['1996', '1997', '1998']; + + $testSamples = [[3, 1, 1], [5, 1, 1], [4, 3, 8], [1, 1, 2], [2, 3, 2], [1, 2, 1], [9, 5, 1], [3, 1, 2]]; + $testLabels = ['1996', '1996', '1998', '1998', '1997', '1997', '1996', '1996']; + + $classifier = new NaiveBayes(); + $classifier->train($trainSamples, $trainLabels); + $predicted = $classifier->predict($testSamples); + + $this->assertEquals($testLabels, $predicted); + + // Feed an extra set of training data. + $samples = [[1, 1, 6]]; + $labels = ['1999']; + $classifier->train($samples, $labels); + + $testSamples = [[1, 1, 6], [5, 1, 1]]; + $testLabels = ['1999', '1996']; + $this->assertEquals($testLabels, $classifier->predict($testSamples)); + } + + public function testSaveAndRestoreNumericLabels(): void + { + $trainSamples = [[5, 1, 1], [1, 5, 1], [1, 1, 5]]; + $trainLabels = ['1996', '1997', '1998']; + + $testSamples = [[3, 1, 1], [5, 1, 1], [4, 3, 8]]; + $testLabels = ['1996', '1996', '1998']; + + $classifier = new NaiveBayes(); + $classifier->train($trainSamples, $trainLabels); + $predicted = $classifier->predict($testSamples); + + $filename = 'naive-bayes-test-'.random_int(100, 999).'-'.uniqid(); + $filepath = tempnam(sys_get_temp_dir(), $filename); + $modelManager = new ModelManager(); + $modelManager->saveToFile($classifier, $filepath); + + $restoredClassifier = $modelManager->restoreFromFile($filepath); + $this->assertEquals($classifier, $restoredClassifier); + $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + } } From 8daed2484db95ba38f41135fc2a67f01a5db981b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Wed, 31 Jan 2018 21:50:26 +0100 Subject: [PATCH 234/328] Enhancement: Cache dependencies installed with composer on Travis (#215) --- .travis.yml | 4 ++++ composer.json | 1 + 2 files changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 7a0f3af9..fd841637 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,10 @@ matrix: - _OSX=10.11 - _PHP: php71 +cache: + directories: + - $HOME/.composer/cache + before_install: - if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then /usr/bin/env bash bin/prepare_osx_env.sh ; fi - if [[ $DISABLE_XDEBUG == "true" ]]; then phpenv config-rm xdebug.ini; fi diff --git a/composer.json b/composer.json index acb34dd5..865d4b51 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,7 @@ "symplify/easy-coding-standard": "^3.1" }, "config": { + "preferred-install": "dist", "sort-packages": true }, "autoload": { From c32bf3fe2b4eeb7b29b40b059f2fe0a99e40eeae Mon Sep 17 00:00:00 2001 From: Jonathan Baldie Date: Thu, 1 Feb 2018 22:15:36 +0000 Subject: [PATCH 235/328] Configure an Activation Function per hidden layer (#208) * ability to specify per-layer activation function * some tests for new addition to layer * appease style CI whitespace issue * more flexible addition of layers, and developer can pass Layer object in manually * new test for layer object in mlp constructor * documentation for added MLP functionality --- .../multilayer-perceptron-classifier.md | 18 +++++++ .../Network/MultilayerPerceptron.php | 13 +++-- .../Network/LayeredNetworkTest.php | 18 +++++++ .../Network/MultilayerPerceptronTest.php | 52 +++++++++++++++++++ 4 files changed, 98 insertions(+), 3 deletions(-) diff --git a/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md b/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md index 5acf0933..7365a715 100644 --- a/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md +++ b/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md @@ -19,6 +19,24 @@ $mlp = new MLPClassifier(4, [2], ['a', 'b', 'c']); ``` +An Activation Function may also be passed in with each individual hidden layer. Example: + +``` +use Phpml\NeuralNetwork\ActivationFunction\PReLU; +use Phpml\NeuralNetwork\ActivationFunction\Sigmoid; +$mlp = new MLPClassifier(4, [[2, new PReLU], [2, new Sigmoid]], ['a', 'b', 'c']); +``` + +Instead of configuring each hidden layer as an array, they may also be configured with Layer objects. Example: + +``` +use Phpml\NeuralNetwork\Layer; +use Phpml\NeuralNetwork\Node\Neuron; +$layer1 = new Layer(2, Neuron::class, new PReLU); +$layer2 = new Layer(2, Neuron::class, new Sigmoid); +$mlp = new MLPClassifier(4, [$layer1, $layer2], ['a', 'b', 'c']); +``` + ## Train To train a MLP simply provide train samples and labels (as array). Example: diff --git a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php index bfec9297..a6d3be0d 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -142,10 +142,17 @@ private function addInputLayer(int $nodes): void $this->addLayer(new Layer($nodes, Input::class)); } - private function addNeuronLayers(array $layers, ?ActivationFunction $activationFunction = null): void + private function addNeuronLayers(array $layers, ?ActivationFunction $defaultActivationFunction = null): void { - foreach ($layers as $neurons) { - $this->addLayer(new Layer($neurons, Neuron::class, $activationFunction)); + foreach ($layers as $layer) { + if (is_array($layer)) { + $function = $layer[1] instanceof ActivationFunction ? $layer[1] : $defaultActivationFunction; + $this->addLayer(new Layer($layer[0], Neuron::class, $function)); + } elseif ($layer instanceof Layer) { + $this->addLayer($layer); + } else { + $this->addLayer(new Layer($layer, Neuron::class, $defaultActivationFunction)); + } } } diff --git a/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php b/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php index 21f5e9cf..2a7e6966 100644 --- a/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php +++ b/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php @@ -4,6 +4,7 @@ namespace Phpml\Tests\NeuralNetwork\Network; +use Phpml\NeuralNetwork\ActivationFunction; use Phpml\NeuralNetwork\Layer; use Phpml\NeuralNetwork\Network\LayeredNetwork; use Phpml\NeuralNetwork\Node\Input; @@ -45,6 +46,15 @@ public function testSetInputAndGetOutput(): void $this->assertEquals([0.5], $network->getOutput()); } + public function testSetInputAndGetOutputWithCustomActivationFunctions(): void + { + $network = $this->getLayeredNetworkMock(); + $network->addLayer(new Layer(2, Input::class, $this->getActivationFunctionMock())); + + $network->setInput($input = [34, 43]); + $this->assertEquals($input, $network->getOutput()); + } + /** * @return LayeredNetwork|PHPUnit_Framework_MockObject_MockObject */ @@ -52,4 +62,12 @@ private function getLayeredNetworkMock() { return $this->getMockForAbstractClass(LayeredNetwork::class); } + + /** + * @return ActivationFunction|PHPUnit_Framework_MockObject_MockObject + */ + private function getActivationFunctionMock() + { + return $this->getMockForAbstractClass(ActivationFunction::class); + } } diff --git a/tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php b/tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php index 885c1e14..006733f5 100644 --- a/tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php +++ b/tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php @@ -4,8 +4,12 @@ namespace Phpml\Tests\NeuralNetwork\Network; +use Phpml\NeuralNetwork\ActivationFunction; +use Phpml\NeuralNetwork\Layer; use Phpml\NeuralNetwork\Network\MultilayerPerceptron; +use Phpml\NeuralNetwork\Node\Neuron; use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject; class MultilayerPerceptronTest extends TestCase { @@ -26,4 +30,52 @@ public function testLearningRateSetter(): void $backprop = $this->readAttribute($mlp, 'backpropagation'); $this->assertEquals(0.24, $this->readAttribute($backprop, 'learningRate')); } + + public function testLearningRateSetterWithCustomActivationFunctions(): void + { + $activation_function = $this->getActivationFunctionMock(); + + /** @var MultilayerPerceptron $mlp */ + $mlp = $this->getMockForAbstractClass( + MultilayerPerceptron::class, + [5, [[3, $activation_function], [5, $activation_function]], [0, 1], 1000, null, 0.42] + ); + + $this->assertEquals(0.42, $this->readAttribute($mlp, 'learningRate')); + $backprop = $this->readAttribute($mlp, 'backpropagation'); + $this->assertEquals(0.42, $this->readAttribute($backprop, 'learningRate')); + + $mlp->setLearningRate(0.24); + $this->assertEquals(0.24, $this->readAttribute($mlp, 'learningRate')); + $backprop = $this->readAttribute($mlp, 'backpropagation'); + $this->assertEquals(0.24, $this->readAttribute($backprop, 'learningRate')); + } + + public function testLearningRateSetterWithLayerObject(): void + { + $activation_function = $this->getActivationFunctionMock(); + + /** @var MultilayerPerceptron $mlp */ + $mlp = $this->getMockForAbstractClass( + MultilayerPerceptron::class, + [5, [new Layer(3, Neuron::class, $activation_function), new Layer(5, Neuron::class, $activation_function)], [0, 1], 1000, null, 0.42] + ); + + $this->assertEquals(0.42, $this->readAttribute($mlp, 'learningRate')); + $backprop = $this->readAttribute($mlp, 'backpropagation'); + $this->assertEquals(0.42, $this->readAttribute($backprop, 'learningRate')); + + $mlp->setLearningRate(0.24); + $this->assertEquals(0.24, $this->readAttribute($mlp, 'learningRate')); + $backprop = $this->readAttribute($mlp, 'backpropagation'); + $this->assertEquals(0.24, $this->readAttribute($backprop, 'learningRate')); + } + + /** + * @return ActivationFunction|PHPUnit_Framework_MockObject_MockObject + */ + private function getActivationFunctionMock() + { + return $this->getMockForAbstractClass(ActivationFunction::class); + } } From 84a49dbffe9823cbbb222b013c7a806d10fdaa14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Sat, 3 Feb 2018 14:11:48 +0100 Subject: [PATCH 236/328] Enhancement: Update phpunit/phpunit (#219) --- composer.json | 2 +- composer.lock | 147 +++++++++++++++++++++++++------------------------- 2 files changed, 73 insertions(+), 76 deletions(-) diff --git a/composer.json b/composer.json index 865d4b51..3571788c 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ }, "require-dev": { "phpstan/phpstan-shim": "^0.9", - "phpunit/phpunit": "^6.5", + "phpunit/phpunit": "^7.0.0", "symplify/coding-standard": "^3.1", "symplify/easy-coding-standard": "^3.1" }, diff --git a/composer.lock b/composer.lock index bab8a711..1545cc2a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "7f8e0516fc20861caade713f6fe241dc", + "content-hash": "3e327a50a76dd6df905cef56cbc37e02", "packages": [], "packages-dev": [ { @@ -1326,40 +1326,40 @@ }, { "name": "phpunit/php-code-coverage", - "version": "5.3.0", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "661f34d0bd3f1a7225ef491a70a020ad23a057a1" + "reference": "f8ca4b604baf23dab89d87773c28cc07405189ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/661f34d0bd3f1a7225ef491a70a020ad23a057a1", - "reference": "661f34d0bd3f1a7225ef491a70a020ad23a057a1", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f8ca4b604baf23dab89d87773c28cc07405189ba", + "reference": "f8ca4b604baf23dab89d87773c28cc07405189ba", "shasum": "" }, "require": { "ext-dom": "*", "ext-xmlwriter": "*", - "php": "^7.0", + "php": "^7.1", "phpunit/php-file-iterator": "^1.4.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^2.0.1", + "phpunit/php-token-stream": "^3.0", "sebastian/code-unit-reverse-lookup": "^1.0.1", "sebastian/environment": "^3.0", "sebastian/version": "^2.0.1", "theseer/tokenizer": "^1.1" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^7.0" }, "suggest": { - "ext-xdebug": "^2.5.5" + "ext-xdebug": "^2.6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.3.x-dev" + "dev-master": "6.0-dev" } }, "autoload": { @@ -1385,7 +1385,7 @@ "testing", "xunit" ], - "time": "2017-12-06T09:29:45+00:00" + "time": "2018-02-02T07:01:41+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1477,28 +1477,28 @@ }, { "name": "phpunit/php-timer", - "version": "1.0.9", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b8454ea6958c3dee38453d3bd571e023108c91f", + "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -1513,7 +1513,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -1522,33 +1522,33 @@ "keywords": [ "timer" ], - "time": "2017-02-26T11:10:40+00:00" + "time": "2018-02-01T13:07:23+00:00" }, { "name": "phpunit/php-token-stream", - "version": "2.0.2", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "791198a2c6254db10131eecfe8c06670700904db" + "reference": "21ad88bbba7c3d93530d93994e0a33cd45f02ace" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", - "reference": "791198a2c6254db10131eecfe8c06670700904db", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/21ad88bbba7c3d93530d93994e0a33cd45f02ace", + "reference": "21ad88bbba7c3d93530d93994e0a33cd45f02ace", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": "^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^6.2.4" + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -1571,20 +1571,20 @@ "keywords": [ "tokenizer" ], - "time": "2017-11-27T05:48:46+00:00" + "time": "2018-02-01T13:16:43+00:00" }, { "name": "phpunit/phpunit", - "version": "6.5.5", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "83d27937a310f2984fd575686138597147bdc7df" + "reference": "9b3373439fdf2f3e9d1578f5e408a3a0d161c3bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/83d27937a310f2984fd575686138597147bdc7df", - "reference": "83d27937a310f2984fd575686138597147bdc7df", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9b3373439fdf2f3e9d1578f5e408a3a0d161c3bc", + "reference": "9b3373439fdf2f3e9d1578f5e408a3a0d161c3bc", "shasum": "" }, "require": { @@ -1596,15 +1596,15 @@ "myclabs/deep-copy": "^1.6.1", "phar-io/manifest": "^1.0.1", "phar-io/version": "^1.0", - "php": "^7.0", + "php": "^7.1", "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^5.3", + "phpunit/php-code-coverage": "^6.0", "phpunit/php-file-iterator": "^1.4.3", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^1.0.9", - "phpunit/phpunit-mock-objects": "^5.0.5", + "phpunit/php-timer": "^2.0", + "phpunit/phpunit-mock-objects": "^6.0", "sebastian/comparator": "^2.1", - "sebastian/diff": "^2.0", + "sebastian/diff": "^3.0", "sebastian/environment": "^3.1", "sebastian/exporter": "^3.1", "sebastian/global-state": "^2.0", @@ -1612,16 +1612,12 @@ "sebastian/resource-operations": "^1.0", "sebastian/version": "^2.0.1" }, - "conflict": { - "phpdocumentor/reflection-docblock": "3.0.2", - "phpunit/dbunit": "<3.0" - }, "require-dev": { "ext-pdo": "*" }, "suggest": { "ext-xdebug": "*", - "phpunit/php-invoker": "^1.1" + "phpunit/php-invoker": "^2.0" }, "bin": [ "phpunit" @@ -1629,7 +1625,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.5.x-dev" + "dev-master": "7.0-dev" } }, "autoload": { @@ -1655,33 +1651,30 @@ "testing", "xunit" ], - "time": "2017-12-17T06:31:19+00:00" + "time": "2018-02-02T05:04:08+00:00" }, { "name": "phpunit/phpunit-mock-objects", - "version": "5.0.6", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf" + "reference": "e495e5d3660321b62c294d8c0e954d02d6ce2573" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/33fd41a76e746b8fa96d00b49a23dadfa8334cdf", - "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/e495e5d3660321b62c294d8c0e954d02d6ce2573", + "reference": "e495e5d3660321b62c294d8c0e954d02d6ce2573", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.5", - "php": "^7.0", + "php": "^7.1", "phpunit/php-text-template": "^1.2.1", "sebastian/exporter": "^3.1" }, - "conflict": { - "phpunit/phpunit": "<6.0" - }, "require-dev": { - "phpunit/phpunit": "^6.5" + "phpunit/phpunit": "^7.0" }, "suggest": { "ext-soap": "*" @@ -1689,7 +1682,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0.x-dev" + "dev-master": "6.0.x-dev" } }, "autoload": { @@ -1714,7 +1707,7 @@ "mock", "xunit" ], - "time": "2018-01-06T05:45:45+00:00" + "time": "2018-02-01T13:11:13+00:00" }, { "name": "psr/container", @@ -1859,21 +1852,21 @@ }, { "name": "sebastian/comparator", - "version": "2.1.1", + "version": "2.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "b11c729f95109b56a0fe9650c6a63a0fcd8c439f" + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/b11c729f95109b56a0fe9650c6a63a0fcd8c439f", - "reference": "b11c729f95109b56a0fe9650c6a63a0fcd8c439f", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", "shasum": "" }, "require": { "php": "^7.0", - "sebastian/diff": "^2.0", + "sebastian/diff": "^2.0 || ^3.0", "sebastian/exporter": "^3.1" }, "require-dev": { @@ -1919,32 +1912,33 @@ "compare", "equality" ], - "time": "2017-12-22T14:50:35+00:00" + "time": "2018-02-01T13:46:46+00:00" }, { "name": "sebastian/diff", - "version": "2.0.1", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" + "reference": "e09160918c66281713f1c324c1f4c4c3037ba1e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", - "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/e09160918c66281713f1c324c1f4c4c3037ba1e8", + "reference": "e09160918c66281713f1c324c1f4c4c3037ba1e8", "shasum": "" }, "require": { - "php": "^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^6.2" + "phpunit/phpunit": "^7.0", + "symfony/process": "^2 || ^3.3 || ^4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -1969,9 +1963,12 @@ "description": "Diff implementation", "homepage": "https://github.com/sebastianbergmann/diff", "keywords": [ - "diff" + "diff", + "udiff", + "unidiff", + "unified diff" ], - "time": "2017-08-03T08:09:46+00:00" + "time": "2018-02-01T13:45:15+00:00" }, { "name": "sebastian/environment", @@ -3668,16 +3665,16 @@ }, { "name": "webmozart/assert", - "version": "1.2.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f" + "reference": "0df1908962e7a3071564e857d86874dad1ef204a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f", - "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f", + "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", + "reference": "0df1908962e7a3071564e857d86874dad1ef204a", "shasum": "" }, "require": { @@ -3714,7 +3711,7 @@ "check", "validate" ], - "time": "2016-11-23T20:04:58+00:00" + "time": "2018-01-29T19:49:41+00:00" } ], "aliases": [], From ed775fb232065307909ede1cb99b7ef393068b86 Mon Sep 17 00:00:00 2001 From: Yuji Uchiyama Date: Tue, 6 Feb 2018 02:50:45 +0900 Subject: [PATCH 237/328] Fix documentation of apriori (#221) * Fix the return value of the single sample prediction * Fix typo --- docs/machine-learning/association/apriori.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/machine-learning/association/apriori.md b/docs/machine-learning/association/apriori.md index e6685af8..6f597bea 100644 --- a/docs/machine-learning/association/apriori.md +++ b/docs/machine-learning/association/apriori.md @@ -35,7 +35,7 @@ To predict sample label use `predict` method. You can provide one sample or arra ``` $associator->predict(['alpha','theta']); -// return [[['beta']]] +// return [['beta']] $associator->predict([['alpha','epsilon'],['beta','theta']]); // return [[['beta']], [['alpha']]] @@ -47,7 +47,7 @@ Get generated association rules simply use `rules` method. ``` $associator->getRules(); -// return [['antecedent' => ['alpha', 'theta'], 'consequent' => ['beta], 'support' => 1.0, 'confidence' => 1.0], ... ] +// return [['antecedent' => ['alpha', 'theta'], 'consequent' => ['beta'], 'support' => 1.0, 'confidence' => 1.0], ... ] ``` ### Frequent item sets From ec091b5ea3d3bfa8f3168c476cb0c71725b3d344 Mon Sep 17 00:00:00 2001 From: Yuji Uchiyama Date: Wed, 7 Feb 2018 04:39:25 +0900 Subject: [PATCH 238/328] Support probability estimation in SVC (#218) * Add test for svm model with probability estimation * Extract buildPredictCommand method * Fix test to use PHP_EOL * Add predictProbability method (not completed) * Add test for DataTransformer::predictions * Fix SVM to use PHP_EOL * Support probability estimation in SVM * Add documentation * Add InvalidOperationException class * Throw InvalidOperationException before executing libsvm if probability estimation is not supported --- docs/machine-learning/classification/svc.md | 39 +++++++++ .../Exception/InvalidOperationException.php | 11 +++ .../SupportVectorMachine/DataTransformer.php | 31 ++++++++ .../SupportVectorMachine.php | 78 +++++++++++++++--- .../DataTransformerTest.php | 41 ++++++++++ .../SupportVectorMachineTest.php | 79 +++++++++++++++++++ 6 files changed, 268 insertions(+), 11 deletions(-) create mode 100644 src/Phpml/Exception/InvalidOperationException.php diff --git a/docs/machine-learning/classification/svc.md b/docs/machine-learning/classification/svc.md index 62da5095..da0511c8 100644 --- a/docs/machine-learning/classification/svc.md +++ b/docs/machine-learning/classification/svc.md @@ -47,3 +47,42 @@ $classifier->predict([3, 2]); $classifier->predict([[3, 2], [1, 5]]); // return ['b', 'a'] ``` + +### Probability estimation + +To predict probabilities you must build a classifier with `$probabilityEstimates` set to true. Example: + +``` +use Phpml\Classification\SVC; +use Phpml\SupportVectorMachine\Kernel; + +$samples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; +$labels = ['a', 'a', 'a', 'b', 'b', 'b']; + +$classifier = new SVC( + Kernel::LINEAR, // $kernel + 1.0, // $cost + 3, // $degree + null, // $gamma + 0.0, // $coef0 + 0.001, // $tolerance + 100, // $cacheSize + true, // $shrinking + true // $probabilityEstimates, set to true +); + +$classifier->train($samples, $labels); +``` + +Then use `predictProbability` method instead of `predict`: + +``` +$classifier->predictProbability([3, 2]); +// return ['a' => 0.349833, 'b' => 0.650167] + +$classifier->predictProbability([[3, 2], [1, 5]]); +// return [ +// ['a' => 0.349833, 'b' => 0.650167], +// ['a' => 0.922664, 'b' => 0.0773364], +// ] +``` diff --git a/src/Phpml/Exception/InvalidOperationException.php b/src/Phpml/Exception/InvalidOperationException.php new file mode 100644 index 00000000..0eba9734 --- /dev/null +++ b/src/Phpml/Exception/InvalidOperationException.php @@ -0,0 +1,11 @@ + $prob) { + $result[$columnLabels[$i]] = (float) $prob; + } + + $results[] = $result; + } + + return $results; + } + public static function numericLabels(array $labels): array { $numericLabels = []; diff --git a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php index ce7a7bab..ddd843fb 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -5,6 +5,7 @@ namespace Phpml\SupportVectorMachine; use Phpml\Exception\InvalidArgumentException; +use Phpml\Exception\InvalidOperationException; use Phpml\Exception\LibsvmCommandException; use Phpml\Helper\Trainable; @@ -178,13 +179,61 @@ public function getModel(): string * @throws LibsvmCommandException */ public function predict(array $samples) + { + $predictions = $this->runSvmPredict($samples, false); + + if (in_array($this->type, [Type::C_SVC, Type::NU_SVC])) { + $predictions = DataTransformer::predictions($predictions, $this->targets); + } else { + $predictions = explode(PHP_EOL, trim($predictions)); + } + + if (!is_array($samples[0])) { + return $predictions[0]; + } + + return $predictions; + } + + /** + * @return array|string + * + * @throws LibsvmCommandException + */ + public function predictProbability(array $samples) + { + if (!$this->probabilityEstimates) { + throw new InvalidOperationException('Model does not support probabiliy estimates'); + } + + $predictions = $this->runSvmPredict($samples, true); + + if (in_array($this->type, [Type::C_SVC, Type::NU_SVC])) { + $predictions = DataTransformer::probabilities($predictions, $this->targets); + } else { + $predictions = explode(PHP_EOL, trim($predictions)); + } + + if (!is_array($samples[0])) { + return $predictions[0]; + } + + return $predictions; + } + + private function runSvmPredict(array $samples, bool $probabilityEstimates): string { $testSet = DataTransformer::testSet($samples); file_put_contents($testSetFileName = $this->varPath.uniqid('phpml', true), $testSet); file_put_contents($modelFileName = $testSetFileName.'-model', $this->model); $outputFileName = $testSetFileName.'-output'; - $command = sprintf('%ssvm-predict%s %s %s %s', $this->binPath, $this->getOSExtension(), $testSetFileName, $modelFileName, $outputFileName); + $command = $this->buildPredictCommand( + $testSetFileName, + $modelFileName, + $outputFileName, + $probabilityEstimates + ); $output = []; exec(escapeshellcmd($command).' 2>&1', $output, $return); @@ -198,16 +247,6 @@ public function predict(array $samples) throw LibsvmCommandException::failedToRun($command, array_pop($output)); } - if (in_array($this->type, [Type::C_SVC, Type::NU_SVC])) { - $predictions = DataTransformer::predictions($predictions, $this->targets); - } else { - $predictions = explode(PHP_EOL, trim($predictions)); - } - - if (!is_array($samples[0])) { - return $predictions[0]; - } - return $predictions; } @@ -246,6 +285,23 @@ private function buildTrainCommand(string $trainingSetFileName, string $modelFil ); } + private function buildPredictCommand( + string $testSetFileName, + string $modelFileName, + string $outputFileName, + bool $probabilityEstimates + ): string { + return sprintf( + '%ssvm-predict%s -b %d %s %s %s', + $this->binPath, + $this->getOSExtension(), + $probabilityEstimates ? 1 : 0, + escapeshellarg($testSetFileName), + escapeshellarg($modelFileName), + escapeshellarg($outputFileName) + ); + } + private function ensureDirectorySeparator(string &$path): void { if (substr($path, -1) !== DIRECTORY_SEPARATOR) { diff --git a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php b/tests/Phpml/SupportVectorMachine/DataTransformerTest.php index 1db1fdf8..79dcb494 100644 --- a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php +++ b/tests/Phpml/SupportVectorMachine/DataTransformerTest.php @@ -37,4 +37,45 @@ public function testTransformSamplesToTestSet(): void $this->assertEquals($testSet, DataTransformer::testSet($samples)); } + + public function testPredictions(): void + { + $labels = ['a', 'a', 'b', 'b']; + $rawPredictions = implode(PHP_EOL, [0, 1, 0, 0]); + + $predictions = ['a', 'b', 'a', 'a']; + + $this->assertEquals($predictions, DataTransformer::predictions($rawPredictions, $labels)); + } + + public function testProbabilities(): void + { + $labels = ['a', 'b', 'c']; + $rawPredictions = implode(PHP_EOL, [ + 'labels 0 1 2', + '1 0.1 0.7 0.2', + '2 0.2 0.3 0.5', + '0 0.6 0.1 0.3', + ]); + + $probabilities = [ + [ + 'a' => 0.1, + 'b' => 0.7, + 'c' => 0.2, + ], + [ + 'a' => 0.2, + 'b' => 0.3, + 'c' => 0.5, + ], + [ + 'a' => 0.6, + 'b' => 0.1, + 'c' => 0.3, + ], + ]; + + $this->assertEquals($probabilities, DataTransformer::probabilities($rawPredictions, $labels)); + } } diff --git a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php index 466c962a..899fa40c 100644 --- a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php @@ -5,6 +5,7 @@ namespace Phpml\Tests\SupportVectorMachine; use Phpml\Exception\InvalidArgumentException; +use Phpml\Exception\InvalidOperationException; use Phpml\Exception\LibsvmCommandException; use Phpml\SupportVectorMachine\Kernel; use Phpml\SupportVectorMachine\SupportVectorMachine; @@ -37,6 +38,31 @@ public function testTrainCSVCModelWithLinearKernel(): void $this->assertEquals($model, $svm->getModel()); } + public function testTrainCSVCModelWithProbabilityEstimate(): void + { + $samples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; + $labels = ['a', 'a', 'a', 'b', 'b', 'b']; + + $svm = new SupportVectorMachine( + Type::C_SVC, + Kernel::LINEAR, + 100.0, + 0.5, + 3, + null, + 0.0, + 0.1, + 0.01, + 100, + true, + true + ); + $svm->train($samples, $labels); + + $this->assertContains(PHP_EOL.'probA ', $svm->getModel()); + $this->assertContains(PHP_EOL.'probB ', $svm->getModel()); + } + public function testPredictSampleWithLinearKernel(): void { $samples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; @@ -83,6 +109,41 @@ public function testPredictSampleFromMultipleClassWithRbfKernel(): void $this->assertEquals('c', $predictions[2]); } + public function testPredictProbability(): void + { + $samples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; + $labels = ['a', 'a', 'a', 'b', 'b', 'b']; + + $svm = new SupportVectorMachine( + Type::C_SVC, + Kernel::LINEAR, + 100.0, + 0.5, + 3, + null, + 0.0, + 0.1, + 0.01, + 100, + true, + true + ); + $svm->train($samples, $labels); + + $predictions = $svm->predictProbability([ + [3, 2], + [2, 3], + [4, -5], + ]); + + $this->assertTrue($predictions[0]['a'] < $predictions[0]['b']); + $this->assertTrue($predictions[1]['a'] > $predictions[1]['b']); + $this->assertTrue($predictions[2]['a'] < $predictions[2]['b']); + + // Should be true because the latter is farther from the decision boundary + $this->assertTrue($predictions[0]['b'] < $predictions[2]['b']); + } + public function testThrowExceptionWhenVarPathIsNotWritable(): void { $this->expectException(InvalidArgumentException::class); @@ -124,4 +185,22 @@ public function testThrowExceptionWhenLibsvmFailsDuringPredict(): void $svm = new SupportVectorMachine(Type::C_SVC, Kernel::RBF); $svm->predict([1]); } + + public function testThrowExceptionWhenPredictProbabilityCalledWithoutProperModel(): void + { + $this->expectException(InvalidOperationException::class); + $this->expectExceptionMessage('Model does not support probabiliy estimates'); + + $samples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; + $labels = ['a', 'a', 'a', 'b', 'b', 'b']; + + $svm = new SupportVectorMachine(Type::C_SVC, Kernel::LINEAR, 100.0); + $svm->train($samples, $labels); + + $predictions = $svm->predictProbability([ + [3, 2], + [2, 3], + [4, -5], + ]); + } } From 71cc633c8ee1e645df1d813d91e700c9d3ff015a Mon Sep 17 00:00:00 2001 From: Yuji Uchiyama Date: Wed, 7 Feb 2018 18:02:38 +0900 Subject: [PATCH 239/328] Fix apriori generates an empty array as a part of the frequent item sets (#224) --- src/Phpml/Association/Apriori.php | 9 ++++----- tests/Phpml/Association/AprioriTest.php | 26 ++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/Phpml/Association/Apriori.php b/src/Phpml/Association/Apriori.php index 76d86245..b6079cc1 100644 --- a/src/Phpml/Association/Apriori.php +++ b/src/Phpml/Association/Apriori.php @@ -86,12 +86,11 @@ public function getRules(): array public function apriori(): array { $L = []; - $L[1] = $this->items(); - $L[1] = $this->frequent($L[1]); - for ($k = 2; !empty($L[$k - 1]); ++$k) { - $L[$k] = $this->candidates($L[$k - 1]); - $L[$k] = $this->frequent($L[$k]); + $items = $this->frequent($this->items()); + for ($k = 1; !empty($items); ++$k) { + $L[$k] = $items; + $items = $this->frequent($this->candidates($items)); } return $L; diff --git a/tests/Phpml/Association/AprioriTest.php b/tests/Phpml/Association/AprioriTest.php index 8b95237f..3ba69016 100644 --- a/tests/Phpml/Association/AprioriTest.php +++ b/tests/Phpml/Association/AprioriTest.php @@ -64,7 +64,6 @@ public function testApriori(): void $L = $apriori->apriori(); - $this->assertCount(0, $L[3]); $this->assertCount(4, $L[2]); $this->assertTrue($this->invoke($apriori, 'contains', [$L[2], [1, 2]])); $this->assertFalse($this->invoke($apriori, 'contains', [$L[2], [1, 3]])); @@ -204,4 +203,29 @@ public function testSaveAndRestore(): void $this->assertEquals($classifier, $restoredClassifier); $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); } + + public function testAprioriEmpty(): void + { + $sample = []; + + $apriori = new Apriori(0, 0); + $apriori->train($sample, []); + + $L = $apriori->apriori(); + + $this->assertEmpty($L); + } + + public function testAprioriSingleItem(): void + { + $sample = [['a']]; + + $apriori = new Apriori(0, 0); + $apriori->train($sample, []); + + $L = $apriori->apriori(); + + $this->assertEquals([1], array_keys($L)); + $this->assertEquals([['a']], $L[1]); + } } From 4b5d57fd6fadeea5b32a15870a2d649b94b941a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Sat, 10 Feb 2018 12:08:58 +0100 Subject: [PATCH 240/328] Enhancement: Flatten directory structure (#220) --- composer.json | 4 ++-- easy-coding-standard.neon | 10 +++++----- src/{Phpml => }/Association/Apriori.php | 0 src/{Phpml => }/Association/Associator.php | 0 src/{Phpml => }/Classification/Classifier.php | 0 src/{Phpml => }/Classification/DecisionTree.php | 0 .../Classification/DecisionTree/DecisionTreeLeaf.php | 0 src/{Phpml => }/Classification/Ensemble/AdaBoost.php | 0 src/{Phpml => }/Classification/Ensemble/Bagging.php | 0 .../Classification/Ensemble/RandomForest.php | 0 src/{Phpml => }/Classification/KNearestNeighbors.php | 0 src/{Phpml => }/Classification/Linear/Adaline.php | 0 .../Classification/Linear/DecisionStump.php | 0 .../Classification/Linear/LogisticRegression.php | 0 src/{Phpml => }/Classification/Linear/Perceptron.php | 0 src/{Phpml => }/Classification/MLPClassifier.php | 0 src/{Phpml => }/Classification/NaiveBayes.php | 0 src/{Phpml => }/Classification/SVC.php | 0 src/{Phpml => }/Classification/WeightedClassifier.php | 0 src/{Phpml => }/Clustering/Clusterer.php | 0 src/{Phpml => }/Clustering/DBSCAN.php | 0 src/{Phpml => }/Clustering/FuzzyCMeans.php | 0 src/{Phpml => }/Clustering/KMeans.php | 0 src/{Phpml => }/Clustering/KMeans/Cluster.php | 0 src/{Phpml => }/Clustering/KMeans/Point.php | 0 src/{Phpml => }/Clustering/KMeans/Space.php | 0 src/{Phpml => }/CrossValidation/RandomSplit.php | 0 src/{Phpml => }/CrossValidation/Split.php | 0 .../CrossValidation/StratifiedRandomSplit.php | 0 src/{Phpml => }/Dataset/ArrayDataset.php | 0 src/{Phpml => }/Dataset/CsvDataset.php | 0 src/{Phpml => }/Dataset/Dataset.php | 0 src/{Phpml => }/Dataset/Demo/GlassDataset.php | 2 +- src/{Phpml => }/Dataset/Demo/IrisDataset.php | 2 +- src/{Phpml => }/Dataset/Demo/WineDataset.php | 2 +- src/{Phpml => }/Dataset/FilesDataset.php | 0 .../DimensionReduction/EigenTransformerBase.php | 0 src/{Phpml => }/DimensionReduction/KernelPCA.php | 0 src/{Phpml => }/DimensionReduction/LDA.php | 0 src/{Phpml => }/DimensionReduction/PCA.php | 0 src/{Phpml => }/Estimator.php | 0 src/{Phpml => }/Exception/DatasetException.php | 0 src/{Phpml => }/Exception/FileException.php | 0 src/{Phpml => }/Exception/InvalidArgumentException.php | 0 .../Exception/InvalidOperationException.php | 0 src/{Phpml => }/Exception/LibsvmCommandException.php | 0 src/{Phpml => }/Exception/MatrixException.php | 0 src/{Phpml => }/Exception/NormalizerException.php | 0 src/{Phpml => }/Exception/SerializeException.php | 0 src/{Phpml => }/FeatureExtraction/StopWords.php | 0 .../FeatureExtraction/StopWords/English.php | 0 src/{Phpml => }/FeatureExtraction/StopWords/French.php | 0 src/{Phpml => }/FeatureExtraction/StopWords/Polish.php | 0 src/{Phpml => }/FeatureExtraction/TfIdfTransformer.php | 0 .../FeatureExtraction/TokenCountVectorizer.php | 0 src/{Phpml => }/Helper/OneVsRest.php | 0 src/{Phpml => }/Helper/Optimizer/ConjugateGradient.php | 0 src/{Phpml => }/Helper/Optimizer/GD.php | 0 src/{Phpml => }/Helper/Optimizer/Optimizer.php | 0 src/{Phpml => }/Helper/Optimizer/StochasticGD.php | 0 src/{Phpml => }/Helper/Predictable.php | 0 src/{Phpml => }/Helper/Trainable.php | 0 src/{Phpml => }/IncrementalEstimator.php | 0 src/{Phpml => }/Math/Comparison.php | 0 src/{Phpml => }/Math/Distance.php | 0 src/{Phpml => }/Math/Distance/Chebyshev.php | 0 src/{Phpml => }/Math/Distance/Euclidean.php | 0 src/{Phpml => }/Math/Distance/Manhattan.php | 0 src/{Phpml => }/Math/Distance/Minkowski.php | 0 src/{Phpml => }/Math/Kernel.php | 0 src/{Phpml => }/Math/Kernel/RBF.php | 0 .../Math/LinearAlgebra/EigenvalueDecomposition.php | 0 src/{Phpml => }/Math/LinearAlgebra/LUDecomposition.php | 0 src/{Phpml => }/Math/Matrix.php | 0 src/{Phpml => }/Math/Product.php | 0 src/{Phpml => }/Math/Set.php | 0 src/{Phpml => }/Math/Statistic/Correlation.php | 0 src/{Phpml => }/Math/Statistic/Covariance.php | 0 src/{Phpml => }/Math/Statistic/Gaussian.php | 0 src/{Phpml => }/Math/Statistic/Mean.php | 0 src/{Phpml => }/Math/Statistic/StandardDeviation.php | 0 src/{Phpml => }/Metric/Accuracy.php | 0 src/{Phpml => }/Metric/ClassificationReport.php | 0 src/{Phpml => }/Metric/ConfusionMatrix.php | 0 src/{Phpml => }/ModelManager.php | 0 src/{Phpml => }/NeuralNetwork/ActivationFunction.php | 0 .../NeuralNetwork/ActivationFunction/BinaryStep.php | 0 .../NeuralNetwork/ActivationFunction/Gaussian.php | 0 .../ActivationFunction/HyperbolicTangent.php | 0 .../NeuralNetwork/ActivationFunction/PReLU.php | 0 .../NeuralNetwork/ActivationFunction/Sigmoid.php | 0 .../ActivationFunction/ThresholdedReLU.php | 0 src/{Phpml => }/NeuralNetwork/Layer.php | 0 src/{Phpml => }/NeuralNetwork/Network.php | 0 .../NeuralNetwork/Network/LayeredNetwork.php | 0 .../NeuralNetwork/Network/MultilayerPerceptron.php | 0 src/{Phpml => }/NeuralNetwork/Node.php | 0 src/{Phpml => }/NeuralNetwork/Node/Bias.php | 0 src/{Phpml => }/NeuralNetwork/Node/Input.php | 0 src/{Phpml => }/NeuralNetwork/Node/Neuron.php | 0 src/{Phpml => }/NeuralNetwork/Node/Neuron/Synapse.php | 0 src/{Phpml => }/NeuralNetwork/Training.php | 0 .../NeuralNetwork/Training/Backpropagation.php | 0 .../NeuralNetwork/Training/Backpropagation/Sigma.php | 0 src/{Phpml => }/Pipeline.php | 0 src/{Phpml => }/Preprocessing/Imputer.php | 0 src/{Phpml => }/Preprocessing/Imputer/Strategy.php | 0 .../Preprocessing/Imputer/Strategy/MeanStrategy.php | 0 .../Preprocessing/Imputer/Strategy/MedianStrategy.php | 0 .../Imputer/Strategy/MostFrequentStrategy.php | 0 src/{Phpml => }/Preprocessing/Normalizer.php | 0 src/{Phpml => }/Preprocessing/Preprocessor.php | 0 src/{Phpml => }/Regression/LeastSquares.php | 0 src/{Phpml => }/Regression/Regression.php | 0 src/{Phpml => }/Regression/SVR.php | 0 .../SupportVectorMachine/DataTransformer.php | 0 src/{Phpml => }/SupportVectorMachine/Kernel.php | 0 .../SupportVectorMachine/SupportVectorMachine.php | 2 +- src/{Phpml => }/SupportVectorMachine/Type.php | 0 src/{Phpml => }/Tokenization/Tokenizer.php | 0 src/{Phpml => }/Tokenization/WhitespaceTokenizer.php | 0 src/{Phpml => }/Tokenization/WordTokenizer.php | 0 src/{Phpml => }/Transformer.php | 0 tests/{Phpml => }/Association/AprioriTest.php | 0 .../DecisionTree/DecisionTreeLeafTest.php | 0 tests/{Phpml => }/Classification/DecisionTreeTest.php | 0 .../Classification/Ensemble/AdaBoostTest.php | 0 .../Classification/Ensemble/BaggingTest.php | 0 .../Classification/Ensemble/RandomForestTest.php | 0 .../Classification/KNearestNeighborsTest.php | 0 .../{Phpml => }/Classification/Linear/AdalineTest.php | 0 .../Classification/Linear/DecisionStumpTest.php | 0 .../Classification/Linear/LogisticRegressionTest.php | 0 .../Classification/Linear/PerceptronTest.php | 0 tests/{Phpml => }/Classification/MLPClassifierTest.php | 0 tests/{Phpml => }/Classification/NaiveBayesTest.php | 0 tests/{Phpml => }/Classification/SVCTest.php | 0 tests/{Phpml => }/Clustering/DBSCANTest.php | 0 tests/{Phpml => }/Clustering/FuzzyCMeansTest.php | 0 tests/{Phpml => }/Clustering/KMeansTest.php | 0 tests/{Phpml => }/CrossValidation/RandomSplitTest.php | 0 .../CrossValidation/StratifiedRandomSplitTest.php | 0 tests/{Phpml => }/Dataset/ArrayDatasetTest.php | 0 tests/{Phpml => }/Dataset/CsvDatasetTest.php | 0 tests/{Phpml => }/Dataset/Demo/GlassDatasetTest.php | 0 tests/{Phpml => }/Dataset/Demo/IrisDatasetTest.php | 0 tests/{Phpml => }/Dataset/Demo/WineDatasetTest.php | 0 tests/{Phpml => }/Dataset/FilesDatasetTest.php | 0 .../{Phpml => }/Dataset/Resources/bbc/business/001.txt | 0 .../{Phpml => }/Dataset/Resources/bbc/business/002.txt | 0 .../{Phpml => }/Dataset/Resources/bbc/business/003.txt | 0 .../{Phpml => }/Dataset/Resources/bbc/business/004.txt | 0 .../{Phpml => }/Dataset/Resources/bbc/business/005.txt | 0 .../{Phpml => }/Dataset/Resources/bbc/business/006.txt | 0 .../{Phpml => }/Dataset/Resources/bbc/business/007.txt | 0 .../{Phpml => }/Dataset/Resources/bbc/business/008.txt | 0 .../{Phpml => }/Dataset/Resources/bbc/business/009.txt | 0 .../{Phpml => }/Dataset/Resources/bbc/business/010.txt | 0 .../Dataset/Resources/bbc/entertainment/001.txt | 0 .../Dataset/Resources/bbc/entertainment/002.txt | 0 .../Dataset/Resources/bbc/entertainment/003.txt | 0 .../Dataset/Resources/bbc/entertainment/004.txt | 0 .../Dataset/Resources/bbc/entertainment/005.txt | 0 .../Dataset/Resources/bbc/entertainment/006.txt | 0 .../Dataset/Resources/bbc/entertainment/007.txt | 0 .../Dataset/Resources/bbc/entertainment/008.txt | 0 .../Dataset/Resources/bbc/entertainment/009.txt | 0 .../Dataset/Resources/bbc/entertainment/010.txt | 0 .../{Phpml => }/Dataset/Resources/bbc/politics/001.txt | 0 .../{Phpml => }/Dataset/Resources/bbc/politics/002.txt | 0 .../{Phpml => }/Dataset/Resources/bbc/politics/003.txt | 0 .../{Phpml => }/Dataset/Resources/bbc/politics/004.txt | 0 .../{Phpml => }/Dataset/Resources/bbc/politics/005.txt | 0 .../{Phpml => }/Dataset/Resources/bbc/politics/006.txt | 0 .../{Phpml => }/Dataset/Resources/bbc/politics/007.txt | 0 .../{Phpml => }/Dataset/Resources/bbc/politics/008.txt | 0 .../{Phpml => }/Dataset/Resources/bbc/politics/009.txt | 0 .../{Phpml => }/Dataset/Resources/bbc/politics/010.txt | 0 tests/{Phpml => }/Dataset/Resources/bbc/sport/001.txt | 0 tests/{Phpml => }/Dataset/Resources/bbc/sport/002.txt | 0 tests/{Phpml => }/Dataset/Resources/bbc/sport/003.txt | 0 tests/{Phpml => }/Dataset/Resources/bbc/sport/004.txt | 0 tests/{Phpml => }/Dataset/Resources/bbc/sport/005.txt | 0 tests/{Phpml => }/Dataset/Resources/bbc/sport/006.txt | 0 tests/{Phpml => }/Dataset/Resources/bbc/sport/007.txt | 0 tests/{Phpml => }/Dataset/Resources/bbc/sport/008.txt | 0 tests/{Phpml => }/Dataset/Resources/bbc/sport/009.txt | 0 tests/{Phpml => }/Dataset/Resources/bbc/sport/010.txt | 0 tests/{Phpml => }/Dataset/Resources/bbc/tech/001.txt | 0 tests/{Phpml => }/Dataset/Resources/bbc/tech/002.txt | 0 tests/{Phpml => }/Dataset/Resources/bbc/tech/003.txt | 0 tests/{Phpml => }/Dataset/Resources/bbc/tech/004.txt | 0 tests/{Phpml => }/Dataset/Resources/bbc/tech/005.txt | 0 tests/{Phpml => }/Dataset/Resources/bbc/tech/006.txt | 0 tests/{Phpml => }/Dataset/Resources/bbc/tech/007.txt | 0 tests/{Phpml => }/Dataset/Resources/bbc/tech/008.txt | 0 tests/{Phpml => }/Dataset/Resources/bbc/tech/009.txt | 0 tests/{Phpml => }/Dataset/Resources/bbc/tech/010.txt | 0 tests/{Phpml => }/Dataset/Resources/dataset.csv | 0 tests/{Phpml => }/Dataset/Resources/longdataset.csv | 0 tests/{Phpml => }/DimensionReduction/KernelPCATest.php | 0 tests/{Phpml => }/DimensionReduction/LDATest.php | 0 tests/{Phpml => }/DimensionReduction/PCATest.php | 0 tests/{Phpml => }/FeatureExtraction/StopWordsTest.php | 0 .../FeatureExtraction/TfIdfTransformerTest.php | 0 .../FeatureExtraction/TokenCountVectorizerTest.php | 0 .../Helper/Optimizer/ConjugateGradientTest.php | 0 tests/{Phpml => }/Helper/Optimizer/GDTest.php | 0 .../{Phpml => }/Helper/Optimizer/StochasticGDTest.php | 0 tests/{Phpml => }/Math/ComparisonTest.php | 0 tests/{Phpml => }/Math/Distance/ChebyshevTest.php | 0 tests/{Phpml => }/Math/Distance/EuclideanTest.php | 0 tests/{Phpml => }/Math/Distance/ManhattanTest.php | 0 tests/{Phpml => }/Math/Distance/MinkowskiTest.php | 0 tests/{Phpml => }/Math/Kernel/RBFTest.php | 0 .../Math/LinearAlgebra/EigenDecompositionTest.php | 0 tests/{Phpml => }/Math/MatrixTest.php | 0 tests/{Phpml => }/Math/ProductTest.php | 0 tests/{Phpml => }/Math/SetTest.php | 0 tests/{Phpml => }/Math/Statistic/CorrelationTest.php | 0 tests/{Phpml => }/Math/Statistic/CovarianceTest.php | 0 tests/{Phpml => }/Math/Statistic/GaussianTest.php | 0 tests/{Phpml => }/Math/Statistic/MeanTest.php | 0 .../Math/Statistic/StandardDeviationTest.php | 0 tests/{Phpml => }/Metric/AccuracyTest.php | 0 tests/{Phpml => }/Metric/ClassificationReportTest.php | 0 tests/{Phpml => }/Metric/ConfusionMatrixTest.php | 0 tests/{Phpml => }/ModelManagerTest.php | 0 .../ActivationFunction/BinaryStepTest.php | 0 .../NeuralNetwork/ActivationFunction/GaussianTest.php | 0 .../ActivationFunction/HyperboliTangentTest.php | 0 .../NeuralNetwork/ActivationFunction/PReLUTest.php | 0 .../NeuralNetwork/ActivationFunction/SigmoidTest.php | 0 .../ActivationFunction/ThresholdedReLUTest.php | 0 tests/{Phpml => }/NeuralNetwork/LayerTest.php | 0 .../NeuralNetwork/Network/LayeredNetworkTest.php | 0 .../NeuralNetwork/Network/MultilayerPerceptronTest.php | 0 tests/{Phpml => }/NeuralNetwork/Node/BiasTest.php | 0 tests/{Phpml => }/NeuralNetwork/Node/InputTest.php | 0 .../NeuralNetwork/Node/Neuron/SynapseTest.php | 0 tests/{Phpml => }/NeuralNetwork/Node/NeuronTest.php | 0 tests/{Phpml => }/PipelineTest.php | 0 tests/{Phpml => }/Preprocessing/ImputerTest.php | 0 tests/{Phpml => }/Preprocessing/NormalizerTest.php | 0 tests/{Phpml => }/Regression/LeastSquaresTest.php | 0 tests/{Phpml => }/Regression/SVRTest.php | 0 .../SupportVectorMachine/DataTransformerTest.php | 0 .../SupportVectorMachine/SupportVectorMachineTest.php | 0 .../Tokenization/WhitespaceTokenizerTest.php | 0 tests/{Phpml => }/Tokenization/WordTokenizerTest.php | 0 250 files changed, 11 insertions(+), 11 deletions(-) rename src/{Phpml => }/Association/Apriori.php (100%) rename src/{Phpml => }/Association/Associator.php (100%) rename src/{Phpml => }/Classification/Classifier.php (100%) rename src/{Phpml => }/Classification/DecisionTree.php (100%) rename src/{Phpml => }/Classification/DecisionTree/DecisionTreeLeaf.php (100%) rename src/{Phpml => }/Classification/Ensemble/AdaBoost.php (100%) rename src/{Phpml => }/Classification/Ensemble/Bagging.php (100%) rename src/{Phpml => }/Classification/Ensemble/RandomForest.php (100%) rename src/{Phpml => }/Classification/KNearestNeighbors.php (100%) rename src/{Phpml => }/Classification/Linear/Adaline.php (100%) rename src/{Phpml => }/Classification/Linear/DecisionStump.php (100%) rename src/{Phpml => }/Classification/Linear/LogisticRegression.php (100%) rename src/{Phpml => }/Classification/Linear/Perceptron.php (100%) rename src/{Phpml => }/Classification/MLPClassifier.php (100%) rename src/{Phpml => }/Classification/NaiveBayes.php (100%) rename src/{Phpml => }/Classification/SVC.php (100%) rename src/{Phpml => }/Classification/WeightedClassifier.php (100%) rename src/{Phpml => }/Clustering/Clusterer.php (100%) rename src/{Phpml => }/Clustering/DBSCAN.php (100%) rename src/{Phpml => }/Clustering/FuzzyCMeans.php (100%) rename src/{Phpml => }/Clustering/KMeans.php (100%) rename src/{Phpml => }/Clustering/KMeans/Cluster.php (100%) rename src/{Phpml => }/Clustering/KMeans/Point.php (100%) rename src/{Phpml => }/Clustering/KMeans/Space.php (100%) rename src/{Phpml => }/CrossValidation/RandomSplit.php (100%) rename src/{Phpml => }/CrossValidation/Split.php (100%) rename src/{Phpml => }/CrossValidation/StratifiedRandomSplit.php (100%) rename src/{Phpml => }/Dataset/ArrayDataset.php (100%) rename src/{Phpml => }/Dataset/CsvDataset.php (100%) rename src/{Phpml => }/Dataset/Dataset.php (100%) rename src/{Phpml => }/Dataset/Demo/GlassDataset.php (89%) rename src/{Phpml => }/Dataset/Demo/IrisDataset.php (84%) rename src/{Phpml => }/Dataset/Demo/WineDataset.php (86%) rename src/{Phpml => }/Dataset/FilesDataset.php (100%) rename src/{Phpml => }/DimensionReduction/EigenTransformerBase.php (100%) rename src/{Phpml => }/DimensionReduction/KernelPCA.php (100%) rename src/{Phpml => }/DimensionReduction/LDA.php (100%) rename src/{Phpml => }/DimensionReduction/PCA.php (100%) rename src/{Phpml => }/Estimator.php (100%) rename src/{Phpml => }/Exception/DatasetException.php (100%) rename src/{Phpml => }/Exception/FileException.php (100%) rename src/{Phpml => }/Exception/InvalidArgumentException.php (100%) rename src/{Phpml => }/Exception/InvalidOperationException.php (100%) rename src/{Phpml => }/Exception/LibsvmCommandException.php (100%) rename src/{Phpml => }/Exception/MatrixException.php (100%) rename src/{Phpml => }/Exception/NormalizerException.php (100%) rename src/{Phpml => }/Exception/SerializeException.php (100%) rename src/{Phpml => }/FeatureExtraction/StopWords.php (100%) rename src/{Phpml => }/FeatureExtraction/StopWords/English.php (100%) rename src/{Phpml => }/FeatureExtraction/StopWords/French.php (100%) rename src/{Phpml => }/FeatureExtraction/StopWords/Polish.php (100%) rename src/{Phpml => }/FeatureExtraction/TfIdfTransformer.php (100%) rename src/{Phpml => }/FeatureExtraction/TokenCountVectorizer.php (100%) rename src/{Phpml => }/Helper/OneVsRest.php (100%) rename src/{Phpml => }/Helper/Optimizer/ConjugateGradient.php (100%) rename src/{Phpml => }/Helper/Optimizer/GD.php (100%) rename src/{Phpml => }/Helper/Optimizer/Optimizer.php (100%) rename src/{Phpml => }/Helper/Optimizer/StochasticGD.php (100%) rename src/{Phpml => }/Helper/Predictable.php (100%) rename src/{Phpml => }/Helper/Trainable.php (100%) rename src/{Phpml => }/IncrementalEstimator.php (100%) rename src/{Phpml => }/Math/Comparison.php (100%) rename src/{Phpml => }/Math/Distance.php (100%) rename src/{Phpml => }/Math/Distance/Chebyshev.php (100%) rename src/{Phpml => }/Math/Distance/Euclidean.php (100%) rename src/{Phpml => }/Math/Distance/Manhattan.php (100%) rename src/{Phpml => }/Math/Distance/Minkowski.php (100%) rename src/{Phpml => }/Math/Kernel.php (100%) rename src/{Phpml => }/Math/Kernel/RBF.php (100%) rename src/{Phpml => }/Math/LinearAlgebra/EigenvalueDecomposition.php (100%) rename src/{Phpml => }/Math/LinearAlgebra/LUDecomposition.php (100%) rename src/{Phpml => }/Math/Matrix.php (100%) rename src/{Phpml => }/Math/Product.php (100%) rename src/{Phpml => }/Math/Set.php (100%) rename src/{Phpml => }/Math/Statistic/Correlation.php (100%) rename src/{Phpml => }/Math/Statistic/Covariance.php (100%) rename src/{Phpml => }/Math/Statistic/Gaussian.php (100%) rename src/{Phpml => }/Math/Statistic/Mean.php (100%) rename src/{Phpml => }/Math/Statistic/StandardDeviation.php (100%) rename src/{Phpml => }/Metric/Accuracy.php (100%) rename src/{Phpml => }/Metric/ClassificationReport.php (100%) rename src/{Phpml => }/Metric/ConfusionMatrix.php (100%) rename src/{Phpml => }/ModelManager.php (100%) rename src/{Phpml => }/NeuralNetwork/ActivationFunction.php (100%) rename src/{Phpml => }/NeuralNetwork/ActivationFunction/BinaryStep.php (100%) rename src/{Phpml => }/NeuralNetwork/ActivationFunction/Gaussian.php (100%) rename src/{Phpml => }/NeuralNetwork/ActivationFunction/HyperbolicTangent.php (100%) rename src/{Phpml => }/NeuralNetwork/ActivationFunction/PReLU.php (100%) rename src/{Phpml => }/NeuralNetwork/ActivationFunction/Sigmoid.php (100%) rename src/{Phpml => }/NeuralNetwork/ActivationFunction/ThresholdedReLU.php (100%) rename src/{Phpml => }/NeuralNetwork/Layer.php (100%) rename src/{Phpml => }/NeuralNetwork/Network.php (100%) rename src/{Phpml => }/NeuralNetwork/Network/LayeredNetwork.php (100%) rename src/{Phpml => }/NeuralNetwork/Network/MultilayerPerceptron.php (100%) rename src/{Phpml => }/NeuralNetwork/Node.php (100%) rename src/{Phpml => }/NeuralNetwork/Node/Bias.php (100%) rename src/{Phpml => }/NeuralNetwork/Node/Input.php (100%) rename src/{Phpml => }/NeuralNetwork/Node/Neuron.php (100%) rename src/{Phpml => }/NeuralNetwork/Node/Neuron/Synapse.php (100%) rename src/{Phpml => }/NeuralNetwork/Training.php (100%) rename src/{Phpml => }/NeuralNetwork/Training/Backpropagation.php (100%) rename src/{Phpml => }/NeuralNetwork/Training/Backpropagation/Sigma.php (100%) rename src/{Phpml => }/Pipeline.php (100%) rename src/{Phpml => }/Preprocessing/Imputer.php (100%) rename src/{Phpml => }/Preprocessing/Imputer/Strategy.php (100%) rename src/{Phpml => }/Preprocessing/Imputer/Strategy/MeanStrategy.php (100%) rename src/{Phpml => }/Preprocessing/Imputer/Strategy/MedianStrategy.php (100%) rename src/{Phpml => }/Preprocessing/Imputer/Strategy/MostFrequentStrategy.php (100%) rename src/{Phpml => }/Preprocessing/Normalizer.php (100%) rename src/{Phpml => }/Preprocessing/Preprocessor.php (100%) rename src/{Phpml => }/Regression/LeastSquares.php (100%) rename src/{Phpml => }/Regression/Regression.php (100%) rename src/{Phpml => }/Regression/SVR.php (100%) rename src/{Phpml => }/SupportVectorMachine/DataTransformer.php (100%) rename src/{Phpml => }/SupportVectorMachine/Kernel.php (100%) rename src/{Phpml => }/SupportVectorMachine/SupportVectorMachine.php (99%) rename src/{Phpml => }/SupportVectorMachine/Type.php (100%) rename src/{Phpml => }/Tokenization/Tokenizer.php (100%) rename src/{Phpml => }/Tokenization/WhitespaceTokenizer.php (100%) rename src/{Phpml => }/Tokenization/WordTokenizer.php (100%) rename src/{Phpml => }/Transformer.php (100%) rename tests/{Phpml => }/Association/AprioriTest.php (100%) rename tests/{Phpml => }/Classification/DecisionTree/DecisionTreeLeafTest.php (100%) rename tests/{Phpml => }/Classification/DecisionTreeTest.php (100%) rename tests/{Phpml => }/Classification/Ensemble/AdaBoostTest.php (100%) rename tests/{Phpml => }/Classification/Ensemble/BaggingTest.php (100%) rename tests/{Phpml => }/Classification/Ensemble/RandomForestTest.php (100%) rename tests/{Phpml => }/Classification/KNearestNeighborsTest.php (100%) rename tests/{Phpml => }/Classification/Linear/AdalineTest.php (100%) rename tests/{Phpml => }/Classification/Linear/DecisionStumpTest.php (100%) rename tests/{Phpml => }/Classification/Linear/LogisticRegressionTest.php (100%) rename tests/{Phpml => }/Classification/Linear/PerceptronTest.php (100%) rename tests/{Phpml => }/Classification/MLPClassifierTest.php (100%) rename tests/{Phpml => }/Classification/NaiveBayesTest.php (100%) rename tests/{Phpml => }/Classification/SVCTest.php (100%) rename tests/{Phpml => }/Clustering/DBSCANTest.php (100%) rename tests/{Phpml => }/Clustering/FuzzyCMeansTest.php (100%) rename tests/{Phpml => }/Clustering/KMeansTest.php (100%) rename tests/{Phpml => }/CrossValidation/RandomSplitTest.php (100%) rename tests/{Phpml => }/CrossValidation/StratifiedRandomSplitTest.php (100%) rename tests/{Phpml => }/Dataset/ArrayDatasetTest.php (100%) rename tests/{Phpml => }/Dataset/CsvDatasetTest.php (100%) rename tests/{Phpml => }/Dataset/Demo/GlassDatasetTest.php (100%) rename tests/{Phpml => }/Dataset/Demo/IrisDatasetTest.php (100%) rename tests/{Phpml => }/Dataset/Demo/WineDatasetTest.php (100%) rename tests/{Phpml => }/Dataset/FilesDatasetTest.php (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/business/001.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/business/002.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/business/003.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/business/004.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/business/005.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/business/006.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/business/007.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/business/008.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/business/009.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/business/010.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/entertainment/001.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/entertainment/002.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/entertainment/003.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/entertainment/004.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/entertainment/005.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/entertainment/006.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/entertainment/007.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/entertainment/008.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/entertainment/009.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/entertainment/010.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/politics/001.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/politics/002.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/politics/003.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/politics/004.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/politics/005.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/politics/006.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/politics/007.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/politics/008.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/politics/009.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/politics/010.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/sport/001.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/sport/002.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/sport/003.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/sport/004.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/sport/005.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/sport/006.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/sport/007.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/sport/008.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/sport/009.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/sport/010.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/tech/001.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/tech/002.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/tech/003.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/tech/004.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/tech/005.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/tech/006.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/tech/007.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/tech/008.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/tech/009.txt (100%) rename tests/{Phpml => }/Dataset/Resources/bbc/tech/010.txt (100%) rename tests/{Phpml => }/Dataset/Resources/dataset.csv (100%) rename tests/{Phpml => }/Dataset/Resources/longdataset.csv (100%) rename tests/{Phpml => }/DimensionReduction/KernelPCATest.php (100%) rename tests/{Phpml => }/DimensionReduction/LDATest.php (100%) rename tests/{Phpml => }/DimensionReduction/PCATest.php (100%) rename tests/{Phpml => }/FeatureExtraction/StopWordsTest.php (100%) rename tests/{Phpml => }/FeatureExtraction/TfIdfTransformerTest.php (100%) rename tests/{Phpml => }/FeatureExtraction/TokenCountVectorizerTest.php (100%) rename tests/{Phpml => }/Helper/Optimizer/ConjugateGradientTest.php (100%) rename tests/{Phpml => }/Helper/Optimizer/GDTest.php (100%) rename tests/{Phpml => }/Helper/Optimizer/StochasticGDTest.php (100%) rename tests/{Phpml => }/Math/ComparisonTest.php (100%) rename tests/{Phpml => }/Math/Distance/ChebyshevTest.php (100%) rename tests/{Phpml => }/Math/Distance/EuclideanTest.php (100%) rename tests/{Phpml => }/Math/Distance/ManhattanTest.php (100%) rename tests/{Phpml => }/Math/Distance/MinkowskiTest.php (100%) rename tests/{Phpml => }/Math/Kernel/RBFTest.php (100%) rename tests/{Phpml => }/Math/LinearAlgebra/EigenDecompositionTest.php (100%) rename tests/{Phpml => }/Math/MatrixTest.php (100%) rename tests/{Phpml => }/Math/ProductTest.php (100%) rename tests/{Phpml => }/Math/SetTest.php (100%) rename tests/{Phpml => }/Math/Statistic/CorrelationTest.php (100%) rename tests/{Phpml => }/Math/Statistic/CovarianceTest.php (100%) rename tests/{Phpml => }/Math/Statistic/GaussianTest.php (100%) rename tests/{Phpml => }/Math/Statistic/MeanTest.php (100%) rename tests/{Phpml => }/Math/Statistic/StandardDeviationTest.php (100%) rename tests/{Phpml => }/Metric/AccuracyTest.php (100%) rename tests/{Phpml => }/Metric/ClassificationReportTest.php (100%) rename tests/{Phpml => }/Metric/ConfusionMatrixTest.php (100%) rename tests/{Phpml => }/ModelManagerTest.php (100%) rename tests/{Phpml => }/NeuralNetwork/ActivationFunction/BinaryStepTest.php (100%) rename tests/{Phpml => }/NeuralNetwork/ActivationFunction/GaussianTest.php (100%) rename tests/{Phpml => }/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php (100%) rename tests/{Phpml => }/NeuralNetwork/ActivationFunction/PReLUTest.php (100%) rename tests/{Phpml => }/NeuralNetwork/ActivationFunction/SigmoidTest.php (100%) rename tests/{Phpml => }/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php (100%) rename tests/{Phpml => }/NeuralNetwork/LayerTest.php (100%) rename tests/{Phpml => }/NeuralNetwork/Network/LayeredNetworkTest.php (100%) rename tests/{Phpml => }/NeuralNetwork/Network/MultilayerPerceptronTest.php (100%) rename tests/{Phpml => }/NeuralNetwork/Node/BiasTest.php (100%) rename tests/{Phpml => }/NeuralNetwork/Node/InputTest.php (100%) rename tests/{Phpml => }/NeuralNetwork/Node/Neuron/SynapseTest.php (100%) rename tests/{Phpml => }/NeuralNetwork/Node/NeuronTest.php (100%) rename tests/{Phpml => }/PipelineTest.php (100%) rename tests/{Phpml => }/Preprocessing/ImputerTest.php (100%) rename tests/{Phpml => }/Preprocessing/NormalizerTest.php (100%) rename tests/{Phpml => }/Regression/LeastSquaresTest.php (100%) rename tests/{Phpml => }/Regression/SVRTest.php (100%) rename tests/{Phpml => }/SupportVectorMachine/DataTransformerTest.php (100%) rename tests/{Phpml => }/SupportVectorMachine/SupportVectorMachineTest.php (100%) rename tests/{Phpml => }/Tokenization/WhitespaceTokenizerTest.php (100%) rename tests/{Phpml => }/Tokenization/WordTokenizerTest.php (100%) diff --git a/composer.json b/composer.json index 3571788c..b39a39e6 100644 --- a/composer.json +++ b/composer.json @@ -34,12 +34,12 @@ }, "autoload": { "psr-4": { - "Phpml\\": "src/Phpml" + "Phpml\\": "src/" } }, "autoload-dev": { "psr-4": { - "Phpml\\Tests\\": "tests/Phpml" + "Phpml\\Tests\\": "tests/" } }, "scripts": { diff --git a/easy-coding-standard.neon b/easy-coding-standard.neon index abf30aff..028fe9e7 100644 --- a/easy-coding-standard.neon +++ b/easy-coding-standard.neon @@ -40,19 +40,19 @@ parameters: skip: PhpCsFixer\Fixer\Alias\RandomApiMigrationFixer: # random_int() breaks code - - src/Phpml/CrossValidation/RandomSplit.php + - src/CrossValidation/RandomSplit.php SlevomatCodingStandard\Sniffs\Classes\UnusedPrivateElementsSniff: # magic calls - - src/Phpml/Preprocessing/Normalizer.php + - src/Preprocessing/Normalizer.php PhpCsFixer\Fixer\StringNotation\ExplicitStringVariableFixer: # bugged - - src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php + - src/Classification/DecisionTree/DecisionTreeLeaf.php Symplify\CodingStandard\Fixer\Commenting\RemoveUselessDocBlockFixer: # bug in fixer - - src/Phpml/Math/LinearAlgebra/LUDecomposition.php + - src/Math/LinearAlgebra/LUDecomposition.php PhpCsFixer\Fixer\FunctionNotation\VoidReturnFixer: # covariant return types - - src/Phpml/Classification/Linear/Perceptron.php + - src/Classification/Linear/Perceptron.php skip_codes: # missing typehints diff --git a/src/Phpml/Association/Apriori.php b/src/Association/Apriori.php similarity index 100% rename from src/Phpml/Association/Apriori.php rename to src/Association/Apriori.php diff --git a/src/Phpml/Association/Associator.php b/src/Association/Associator.php similarity index 100% rename from src/Phpml/Association/Associator.php rename to src/Association/Associator.php diff --git a/src/Phpml/Classification/Classifier.php b/src/Classification/Classifier.php similarity index 100% rename from src/Phpml/Classification/Classifier.php rename to src/Classification/Classifier.php diff --git a/src/Phpml/Classification/DecisionTree.php b/src/Classification/DecisionTree.php similarity index 100% rename from src/Phpml/Classification/DecisionTree.php rename to src/Classification/DecisionTree.php diff --git a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php b/src/Classification/DecisionTree/DecisionTreeLeaf.php similarity index 100% rename from src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php rename to src/Classification/DecisionTree/DecisionTreeLeaf.php diff --git a/src/Phpml/Classification/Ensemble/AdaBoost.php b/src/Classification/Ensemble/AdaBoost.php similarity index 100% rename from src/Phpml/Classification/Ensemble/AdaBoost.php rename to src/Classification/Ensemble/AdaBoost.php diff --git a/src/Phpml/Classification/Ensemble/Bagging.php b/src/Classification/Ensemble/Bagging.php similarity index 100% rename from src/Phpml/Classification/Ensemble/Bagging.php rename to src/Classification/Ensemble/Bagging.php diff --git a/src/Phpml/Classification/Ensemble/RandomForest.php b/src/Classification/Ensemble/RandomForest.php similarity index 100% rename from src/Phpml/Classification/Ensemble/RandomForest.php rename to src/Classification/Ensemble/RandomForest.php diff --git a/src/Phpml/Classification/KNearestNeighbors.php b/src/Classification/KNearestNeighbors.php similarity index 100% rename from src/Phpml/Classification/KNearestNeighbors.php rename to src/Classification/KNearestNeighbors.php diff --git a/src/Phpml/Classification/Linear/Adaline.php b/src/Classification/Linear/Adaline.php similarity index 100% rename from src/Phpml/Classification/Linear/Adaline.php rename to src/Classification/Linear/Adaline.php diff --git a/src/Phpml/Classification/Linear/DecisionStump.php b/src/Classification/Linear/DecisionStump.php similarity index 100% rename from src/Phpml/Classification/Linear/DecisionStump.php rename to src/Classification/Linear/DecisionStump.php diff --git a/src/Phpml/Classification/Linear/LogisticRegression.php b/src/Classification/Linear/LogisticRegression.php similarity index 100% rename from src/Phpml/Classification/Linear/LogisticRegression.php rename to src/Classification/Linear/LogisticRegression.php diff --git a/src/Phpml/Classification/Linear/Perceptron.php b/src/Classification/Linear/Perceptron.php similarity index 100% rename from src/Phpml/Classification/Linear/Perceptron.php rename to src/Classification/Linear/Perceptron.php diff --git a/src/Phpml/Classification/MLPClassifier.php b/src/Classification/MLPClassifier.php similarity index 100% rename from src/Phpml/Classification/MLPClassifier.php rename to src/Classification/MLPClassifier.php diff --git a/src/Phpml/Classification/NaiveBayes.php b/src/Classification/NaiveBayes.php similarity index 100% rename from src/Phpml/Classification/NaiveBayes.php rename to src/Classification/NaiveBayes.php diff --git a/src/Phpml/Classification/SVC.php b/src/Classification/SVC.php similarity index 100% rename from src/Phpml/Classification/SVC.php rename to src/Classification/SVC.php diff --git a/src/Phpml/Classification/WeightedClassifier.php b/src/Classification/WeightedClassifier.php similarity index 100% rename from src/Phpml/Classification/WeightedClassifier.php rename to src/Classification/WeightedClassifier.php diff --git a/src/Phpml/Clustering/Clusterer.php b/src/Clustering/Clusterer.php similarity index 100% rename from src/Phpml/Clustering/Clusterer.php rename to src/Clustering/Clusterer.php diff --git a/src/Phpml/Clustering/DBSCAN.php b/src/Clustering/DBSCAN.php similarity index 100% rename from src/Phpml/Clustering/DBSCAN.php rename to src/Clustering/DBSCAN.php diff --git a/src/Phpml/Clustering/FuzzyCMeans.php b/src/Clustering/FuzzyCMeans.php similarity index 100% rename from src/Phpml/Clustering/FuzzyCMeans.php rename to src/Clustering/FuzzyCMeans.php diff --git a/src/Phpml/Clustering/KMeans.php b/src/Clustering/KMeans.php similarity index 100% rename from src/Phpml/Clustering/KMeans.php rename to src/Clustering/KMeans.php diff --git a/src/Phpml/Clustering/KMeans/Cluster.php b/src/Clustering/KMeans/Cluster.php similarity index 100% rename from src/Phpml/Clustering/KMeans/Cluster.php rename to src/Clustering/KMeans/Cluster.php diff --git a/src/Phpml/Clustering/KMeans/Point.php b/src/Clustering/KMeans/Point.php similarity index 100% rename from src/Phpml/Clustering/KMeans/Point.php rename to src/Clustering/KMeans/Point.php diff --git a/src/Phpml/Clustering/KMeans/Space.php b/src/Clustering/KMeans/Space.php similarity index 100% rename from src/Phpml/Clustering/KMeans/Space.php rename to src/Clustering/KMeans/Space.php diff --git a/src/Phpml/CrossValidation/RandomSplit.php b/src/CrossValidation/RandomSplit.php similarity index 100% rename from src/Phpml/CrossValidation/RandomSplit.php rename to src/CrossValidation/RandomSplit.php diff --git a/src/Phpml/CrossValidation/Split.php b/src/CrossValidation/Split.php similarity index 100% rename from src/Phpml/CrossValidation/Split.php rename to src/CrossValidation/Split.php diff --git a/src/Phpml/CrossValidation/StratifiedRandomSplit.php b/src/CrossValidation/StratifiedRandomSplit.php similarity index 100% rename from src/Phpml/CrossValidation/StratifiedRandomSplit.php rename to src/CrossValidation/StratifiedRandomSplit.php diff --git a/src/Phpml/Dataset/ArrayDataset.php b/src/Dataset/ArrayDataset.php similarity index 100% rename from src/Phpml/Dataset/ArrayDataset.php rename to src/Dataset/ArrayDataset.php diff --git a/src/Phpml/Dataset/CsvDataset.php b/src/Dataset/CsvDataset.php similarity index 100% rename from src/Phpml/Dataset/CsvDataset.php rename to src/Dataset/CsvDataset.php diff --git a/src/Phpml/Dataset/Dataset.php b/src/Dataset/Dataset.php similarity index 100% rename from src/Phpml/Dataset/Dataset.php rename to src/Dataset/Dataset.php diff --git a/src/Phpml/Dataset/Demo/GlassDataset.php b/src/Dataset/Demo/GlassDataset.php similarity index 89% rename from src/Phpml/Dataset/Demo/GlassDataset.php rename to src/Dataset/Demo/GlassDataset.php index 8f7d56da..a8a43795 100644 --- a/src/Phpml/Dataset/Demo/GlassDataset.php +++ b/src/Dataset/Demo/GlassDataset.php @@ -22,7 +22,7 @@ class GlassDataset extends CsvDataset { public function __construct() { - $filepath = __DIR__.'/../../../../data/glass.csv'; + $filepath = __DIR__.'/../../../data/glass.csv'; parent::__construct($filepath, 9, true); } } diff --git a/src/Phpml/Dataset/Demo/IrisDataset.php b/src/Dataset/Demo/IrisDataset.php similarity index 84% rename from src/Phpml/Dataset/Demo/IrisDataset.php rename to src/Dataset/Demo/IrisDataset.php index 0bc96d86..d70dffb7 100644 --- a/src/Phpml/Dataset/Demo/IrisDataset.php +++ b/src/Dataset/Demo/IrisDataset.php @@ -16,7 +16,7 @@ class IrisDataset extends CsvDataset { public function __construct() { - $filepath = __DIR__.'/../../../../data/iris.csv'; + $filepath = __DIR__.'/../../../data/iris.csv'; parent::__construct($filepath, 4, true); } } diff --git a/src/Phpml/Dataset/Demo/WineDataset.php b/src/Dataset/Demo/WineDataset.php similarity index 86% rename from src/Phpml/Dataset/Demo/WineDataset.php rename to src/Dataset/Demo/WineDataset.php index c65b08cf..e7666aa2 100644 --- a/src/Phpml/Dataset/Demo/WineDataset.php +++ b/src/Dataset/Demo/WineDataset.php @@ -16,7 +16,7 @@ class WineDataset extends CsvDataset { public function __construct() { - $filepath = __DIR__.'/../../../../data/wine.csv'; + $filepath = __DIR__.'/../../../data/wine.csv'; parent::__construct($filepath, 13, true); } } diff --git a/src/Phpml/Dataset/FilesDataset.php b/src/Dataset/FilesDataset.php similarity index 100% rename from src/Phpml/Dataset/FilesDataset.php rename to src/Dataset/FilesDataset.php diff --git a/src/Phpml/DimensionReduction/EigenTransformerBase.php b/src/DimensionReduction/EigenTransformerBase.php similarity index 100% rename from src/Phpml/DimensionReduction/EigenTransformerBase.php rename to src/DimensionReduction/EigenTransformerBase.php diff --git a/src/Phpml/DimensionReduction/KernelPCA.php b/src/DimensionReduction/KernelPCA.php similarity index 100% rename from src/Phpml/DimensionReduction/KernelPCA.php rename to src/DimensionReduction/KernelPCA.php diff --git a/src/Phpml/DimensionReduction/LDA.php b/src/DimensionReduction/LDA.php similarity index 100% rename from src/Phpml/DimensionReduction/LDA.php rename to src/DimensionReduction/LDA.php diff --git a/src/Phpml/DimensionReduction/PCA.php b/src/DimensionReduction/PCA.php similarity index 100% rename from src/Phpml/DimensionReduction/PCA.php rename to src/DimensionReduction/PCA.php diff --git a/src/Phpml/Estimator.php b/src/Estimator.php similarity index 100% rename from src/Phpml/Estimator.php rename to src/Estimator.php diff --git a/src/Phpml/Exception/DatasetException.php b/src/Exception/DatasetException.php similarity index 100% rename from src/Phpml/Exception/DatasetException.php rename to src/Exception/DatasetException.php diff --git a/src/Phpml/Exception/FileException.php b/src/Exception/FileException.php similarity index 100% rename from src/Phpml/Exception/FileException.php rename to src/Exception/FileException.php diff --git a/src/Phpml/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php similarity index 100% rename from src/Phpml/Exception/InvalidArgumentException.php rename to src/Exception/InvalidArgumentException.php diff --git a/src/Phpml/Exception/InvalidOperationException.php b/src/Exception/InvalidOperationException.php similarity index 100% rename from src/Phpml/Exception/InvalidOperationException.php rename to src/Exception/InvalidOperationException.php diff --git a/src/Phpml/Exception/LibsvmCommandException.php b/src/Exception/LibsvmCommandException.php similarity index 100% rename from src/Phpml/Exception/LibsvmCommandException.php rename to src/Exception/LibsvmCommandException.php diff --git a/src/Phpml/Exception/MatrixException.php b/src/Exception/MatrixException.php similarity index 100% rename from src/Phpml/Exception/MatrixException.php rename to src/Exception/MatrixException.php diff --git a/src/Phpml/Exception/NormalizerException.php b/src/Exception/NormalizerException.php similarity index 100% rename from src/Phpml/Exception/NormalizerException.php rename to src/Exception/NormalizerException.php diff --git a/src/Phpml/Exception/SerializeException.php b/src/Exception/SerializeException.php similarity index 100% rename from src/Phpml/Exception/SerializeException.php rename to src/Exception/SerializeException.php diff --git a/src/Phpml/FeatureExtraction/StopWords.php b/src/FeatureExtraction/StopWords.php similarity index 100% rename from src/Phpml/FeatureExtraction/StopWords.php rename to src/FeatureExtraction/StopWords.php diff --git a/src/Phpml/FeatureExtraction/StopWords/English.php b/src/FeatureExtraction/StopWords/English.php similarity index 100% rename from src/Phpml/FeatureExtraction/StopWords/English.php rename to src/FeatureExtraction/StopWords/English.php diff --git a/src/Phpml/FeatureExtraction/StopWords/French.php b/src/FeatureExtraction/StopWords/French.php similarity index 100% rename from src/Phpml/FeatureExtraction/StopWords/French.php rename to src/FeatureExtraction/StopWords/French.php diff --git a/src/Phpml/FeatureExtraction/StopWords/Polish.php b/src/FeatureExtraction/StopWords/Polish.php similarity index 100% rename from src/Phpml/FeatureExtraction/StopWords/Polish.php rename to src/FeatureExtraction/StopWords/Polish.php diff --git a/src/Phpml/FeatureExtraction/TfIdfTransformer.php b/src/FeatureExtraction/TfIdfTransformer.php similarity index 100% rename from src/Phpml/FeatureExtraction/TfIdfTransformer.php rename to src/FeatureExtraction/TfIdfTransformer.php diff --git a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php b/src/FeatureExtraction/TokenCountVectorizer.php similarity index 100% rename from src/Phpml/FeatureExtraction/TokenCountVectorizer.php rename to src/FeatureExtraction/TokenCountVectorizer.php diff --git a/src/Phpml/Helper/OneVsRest.php b/src/Helper/OneVsRest.php similarity index 100% rename from src/Phpml/Helper/OneVsRest.php rename to src/Helper/OneVsRest.php diff --git a/src/Phpml/Helper/Optimizer/ConjugateGradient.php b/src/Helper/Optimizer/ConjugateGradient.php similarity index 100% rename from src/Phpml/Helper/Optimizer/ConjugateGradient.php rename to src/Helper/Optimizer/ConjugateGradient.php diff --git a/src/Phpml/Helper/Optimizer/GD.php b/src/Helper/Optimizer/GD.php similarity index 100% rename from src/Phpml/Helper/Optimizer/GD.php rename to src/Helper/Optimizer/GD.php diff --git a/src/Phpml/Helper/Optimizer/Optimizer.php b/src/Helper/Optimizer/Optimizer.php similarity index 100% rename from src/Phpml/Helper/Optimizer/Optimizer.php rename to src/Helper/Optimizer/Optimizer.php diff --git a/src/Phpml/Helper/Optimizer/StochasticGD.php b/src/Helper/Optimizer/StochasticGD.php similarity index 100% rename from src/Phpml/Helper/Optimizer/StochasticGD.php rename to src/Helper/Optimizer/StochasticGD.php diff --git a/src/Phpml/Helper/Predictable.php b/src/Helper/Predictable.php similarity index 100% rename from src/Phpml/Helper/Predictable.php rename to src/Helper/Predictable.php diff --git a/src/Phpml/Helper/Trainable.php b/src/Helper/Trainable.php similarity index 100% rename from src/Phpml/Helper/Trainable.php rename to src/Helper/Trainable.php diff --git a/src/Phpml/IncrementalEstimator.php b/src/IncrementalEstimator.php similarity index 100% rename from src/Phpml/IncrementalEstimator.php rename to src/IncrementalEstimator.php diff --git a/src/Phpml/Math/Comparison.php b/src/Math/Comparison.php similarity index 100% rename from src/Phpml/Math/Comparison.php rename to src/Math/Comparison.php diff --git a/src/Phpml/Math/Distance.php b/src/Math/Distance.php similarity index 100% rename from src/Phpml/Math/Distance.php rename to src/Math/Distance.php diff --git a/src/Phpml/Math/Distance/Chebyshev.php b/src/Math/Distance/Chebyshev.php similarity index 100% rename from src/Phpml/Math/Distance/Chebyshev.php rename to src/Math/Distance/Chebyshev.php diff --git a/src/Phpml/Math/Distance/Euclidean.php b/src/Math/Distance/Euclidean.php similarity index 100% rename from src/Phpml/Math/Distance/Euclidean.php rename to src/Math/Distance/Euclidean.php diff --git a/src/Phpml/Math/Distance/Manhattan.php b/src/Math/Distance/Manhattan.php similarity index 100% rename from src/Phpml/Math/Distance/Manhattan.php rename to src/Math/Distance/Manhattan.php diff --git a/src/Phpml/Math/Distance/Minkowski.php b/src/Math/Distance/Minkowski.php similarity index 100% rename from src/Phpml/Math/Distance/Minkowski.php rename to src/Math/Distance/Minkowski.php diff --git a/src/Phpml/Math/Kernel.php b/src/Math/Kernel.php similarity index 100% rename from src/Phpml/Math/Kernel.php rename to src/Math/Kernel.php diff --git a/src/Phpml/Math/Kernel/RBF.php b/src/Math/Kernel/RBF.php similarity index 100% rename from src/Phpml/Math/Kernel/RBF.php rename to src/Math/Kernel/RBF.php diff --git a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php b/src/Math/LinearAlgebra/EigenvalueDecomposition.php similarity index 100% rename from src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php rename to src/Math/LinearAlgebra/EigenvalueDecomposition.php diff --git a/src/Phpml/Math/LinearAlgebra/LUDecomposition.php b/src/Math/LinearAlgebra/LUDecomposition.php similarity index 100% rename from src/Phpml/Math/LinearAlgebra/LUDecomposition.php rename to src/Math/LinearAlgebra/LUDecomposition.php diff --git a/src/Phpml/Math/Matrix.php b/src/Math/Matrix.php similarity index 100% rename from src/Phpml/Math/Matrix.php rename to src/Math/Matrix.php diff --git a/src/Phpml/Math/Product.php b/src/Math/Product.php similarity index 100% rename from src/Phpml/Math/Product.php rename to src/Math/Product.php diff --git a/src/Phpml/Math/Set.php b/src/Math/Set.php similarity index 100% rename from src/Phpml/Math/Set.php rename to src/Math/Set.php diff --git a/src/Phpml/Math/Statistic/Correlation.php b/src/Math/Statistic/Correlation.php similarity index 100% rename from src/Phpml/Math/Statistic/Correlation.php rename to src/Math/Statistic/Correlation.php diff --git a/src/Phpml/Math/Statistic/Covariance.php b/src/Math/Statistic/Covariance.php similarity index 100% rename from src/Phpml/Math/Statistic/Covariance.php rename to src/Math/Statistic/Covariance.php diff --git a/src/Phpml/Math/Statistic/Gaussian.php b/src/Math/Statistic/Gaussian.php similarity index 100% rename from src/Phpml/Math/Statistic/Gaussian.php rename to src/Math/Statistic/Gaussian.php diff --git a/src/Phpml/Math/Statistic/Mean.php b/src/Math/Statistic/Mean.php similarity index 100% rename from src/Phpml/Math/Statistic/Mean.php rename to src/Math/Statistic/Mean.php diff --git a/src/Phpml/Math/Statistic/StandardDeviation.php b/src/Math/Statistic/StandardDeviation.php similarity index 100% rename from src/Phpml/Math/Statistic/StandardDeviation.php rename to src/Math/Statistic/StandardDeviation.php diff --git a/src/Phpml/Metric/Accuracy.php b/src/Metric/Accuracy.php similarity index 100% rename from src/Phpml/Metric/Accuracy.php rename to src/Metric/Accuracy.php diff --git a/src/Phpml/Metric/ClassificationReport.php b/src/Metric/ClassificationReport.php similarity index 100% rename from src/Phpml/Metric/ClassificationReport.php rename to src/Metric/ClassificationReport.php diff --git a/src/Phpml/Metric/ConfusionMatrix.php b/src/Metric/ConfusionMatrix.php similarity index 100% rename from src/Phpml/Metric/ConfusionMatrix.php rename to src/Metric/ConfusionMatrix.php diff --git a/src/Phpml/ModelManager.php b/src/ModelManager.php similarity index 100% rename from src/Phpml/ModelManager.php rename to src/ModelManager.php diff --git a/src/Phpml/NeuralNetwork/ActivationFunction.php b/src/NeuralNetwork/ActivationFunction.php similarity index 100% rename from src/Phpml/NeuralNetwork/ActivationFunction.php rename to src/NeuralNetwork/ActivationFunction.php diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/BinaryStep.php b/src/NeuralNetwork/ActivationFunction/BinaryStep.php similarity index 100% rename from src/Phpml/NeuralNetwork/ActivationFunction/BinaryStep.php rename to src/NeuralNetwork/ActivationFunction/BinaryStep.php diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php b/src/NeuralNetwork/ActivationFunction/Gaussian.php similarity index 100% rename from src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php rename to src/NeuralNetwork/ActivationFunction/Gaussian.php diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/HyperbolicTangent.php b/src/NeuralNetwork/ActivationFunction/HyperbolicTangent.php similarity index 100% rename from src/Phpml/NeuralNetwork/ActivationFunction/HyperbolicTangent.php rename to src/NeuralNetwork/ActivationFunction/HyperbolicTangent.php diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php b/src/NeuralNetwork/ActivationFunction/PReLU.php similarity index 100% rename from src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php rename to src/NeuralNetwork/ActivationFunction/PReLU.php diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php b/src/NeuralNetwork/ActivationFunction/Sigmoid.php similarity index 100% rename from src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php rename to src/NeuralNetwork/ActivationFunction/Sigmoid.php diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php b/src/NeuralNetwork/ActivationFunction/ThresholdedReLU.php similarity index 100% rename from src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php rename to src/NeuralNetwork/ActivationFunction/ThresholdedReLU.php diff --git a/src/Phpml/NeuralNetwork/Layer.php b/src/NeuralNetwork/Layer.php similarity index 100% rename from src/Phpml/NeuralNetwork/Layer.php rename to src/NeuralNetwork/Layer.php diff --git a/src/Phpml/NeuralNetwork/Network.php b/src/NeuralNetwork/Network.php similarity index 100% rename from src/Phpml/NeuralNetwork/Network.php rename to src/NeuralNetwork/Network.php diff --git a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php b/src/NeuralNetwork/Network/LayeredNetwork.php similarity index 100% rename from src/Phpml/NeuralNetwork/Network/LayeredNetwork.php rename to src/NeuralNetwork/Network/LayeredNetwork.php diff --git a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php b/src/NeuralNetwork/Network/MultilayerPerceptron.php similarity index 100% rename from src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php rename to src/NeuralNetwork/Network/MultilayerPerceptron.php diff --git a/src/Phpml/NeuralNetwork/Node.php b/src/NeuralNetwork/Node.php similarity index 100% rename from src/Phpml/NeuralNetwork/Node.php rename to src/NeuralNetwork/Node.php diff --git a/src/Phpml/NeuralNetwork/Node/Bias.php b/src/NeuralNetwork/Node/Bias.php similarity index 100% rename from src/Phpml/NeuralNetwork/Node/Bias.php rename to src/NeuralNetwork/Node/Bias.php diff --git a/src/Phpml/NeuralNetwork/Node/Input.php b/src/NeuralNetwork/Node/Input.php similarity index 100% rename from src/Phpml/NeuralNetwork/Node/Input.php rename to src/NeuralNetwork/Node/Input.php diff --git a/src/Phpml/NeuralNetwork/Node/Neuron.php b/src/NeuralNetwork/Node/Neuron.php similarity index 100% rename from src/Phpml/NeuralNetwork/Node/Neuron.php rename to src/NeuralNetwork/Node/Neuron.php diff --git a/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php b/src/NeuralNetwork/Node/Neuron/Synapse.php similarity index 100% rename from src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php rename to src/NeuralNetwork/Node/Neuron/Synapse.php diff --git a/src/Phpml/NeuralNetwork/Training.php b/src/NeuralNetwork/Training.php similarity index 100% rename from src/Phpml/NeuralNetwork/Training.php rename to src/NeuralNetwork/Training.php diff --git a/src/Phpml/NeuralNetwork/Training/Backpropagation.php b/src/NeuralNetwork/Training/Backpropagation.php similarity index 100% rename from src/Phpml/NeuralNetwork/Training/Backpropagation.php rename to src/NeuralNetwork/Training/Backpropagation.php diff --git a/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php b/src/NeuralNetwork/Training/Backpropagation/Sigma.php similarity index 100% rename from src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php rename to src/NeuralNetwork/Training/Backpropagation/Sigma.php diff --git a/src/Phpml/Pipeline.php b/src/Pipeline.php similarity index 100% rename from src/Phpml/Pipeline.php rename to src/Pipeline.php diff --git a/src/Phpml/Preprocessing/Imputer.php b/src/Preprocessing/Imputer.php similarity index 100% rename from src/Phpml/Preprocessing/Imputer.php rename to src/Preprocessing/Imputer.php diff --git a/src/Phpml/Preprocessing/Imputer/Strategy.php b/src/Preprocessing/Imputer/Strategy.php similarity index 100% rename from src/Phpml/Preprocessing/Imputer/Strategy.php rename to src/Preprocessing/Imputer/Strategy.php diff --git a/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php b/src/Preprocessing/Imputer/Strategy/MeanStrategy.php similarity index 100% rename from src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php rename to src/Preprocessing/Imputer/Strategy/MeanStrategy.php diff --git a/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php b/src/Preprocessing/Imputer/Strategy/MedianStrategy.php similarity index 100% rename from src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php rename to src/Preprocessing/Imputer/Strategy/MedianStrategy.php diff --git a/src/Phpml/Preprocessing/Imputer/Strategy/MostFrequentStrategy.php b/src/Preprocessing/Imputer/Strategy/MostFrequentStrategy.php similarity index 100% rename from src/Phpml/Preprocessing/Imputer/Strategy/MostFrequentStrategy.php rename to src/Preprocessing/Imputer/Strategy/MostFrequentStrategy.php diff --git a/src/Phpml/Preprocessing/Normalizer.php b/src/Preprocessing/Normalizer.php similarity index 100% rename from src/Phpml/Preprocessing/Normalizer.php rename to src/Preprocessing/Normalizer.php diff --git a/src/Phpml/Preprocessing/Preprocessor.php b/src/Preprocessing/Preprocessor.php similarity index 100% rename from src/Phpml/Preprocessing/Preprocessor.php rename to src/Preprocessing/Preprocessor.php diff --git a/src/Phpml/Regression/LeastSquares.php b/src/Regression/LeastSquares.php similarity index 100% rename from src/Phpml/Regression/LeastSquares.php rename to src/Regression/LeastSquares.php diff --git a/src/Phpml/Regression/Regression.php b/src/Regression/Regression.php similarity index 100% rename from src/Phpml/Regression/Regression.php rename to src/Regression/Regression.php diff --git a/src/Phpml/Regression/SVR.php b/src/Regression/SVR.php similarity index 100% rename from src/Phpml/Regression/SVR.php rename to src/Regression/SVR.php diff --git a/src/Phpml/SupportVectorMachine/DataTransformer.php b/src/SupportVectorMachine/DataTransformer.php similarity index 100% rename from src/Phpml/SupportVectorMachine/DataTransformer.php rename to src/SupportVectorMachine/DataTransformer.php diff --git a/src/Phpml/SupportVectorMachine/Kernel.php b/src/SupportVectorMachine/Kernel.php similarity index 100% rename from src/Phpml/SupportVectorMachine/Kernel.php rename to src/SupportVectorMachine/Kernel.php diff --git a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/src/SupportVectorMachine/SupportVectorMachine.php similarity index 99% rename from src/Phpml/SupportVectorMachine/SupportVectorMachine.php rename to src/SupportVectorMachine/SupportVectorMachine.php index ddd843fb..3ec3ed8c 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/SupportVectorMachine/SupportVectorMachine.php @@ -120,7 +120,7 @@ public function __construct( $this->shrinking = $shrinking; $this->probabilityEstimates = $probabilityEstimates; - $rootPath = realpath(implode(DIRECTORY_SEPARATOR, [__DIR__, '..', '..', '..'])).DIRECTORY_SEPARATOR; + $rootPath = realpath(implode(DIRECTORY_SEPARATOR, [__DIR__, '..', '..'])).DIRECTORY_SEPARATOR; $this->binPath = $rootPath.'bin'.DIRECTORY_SEPARATOR.'libsvm'.DIRECTORY_SEPARATOR; $this->varPath = $rootPath.'var'.DIRECTORY_SEPARATOR; diff --git a/src/Phpml/SupportVectorMachine/Type.php b/src/SupportVectorMachine/Type.php similarity index 100% rename from src/Phpml/SupportVectorMachine/Type.php rename to src/SupportVectorMachine/Type.php diff --git a/src/Phpml/Tokenization/Tokenizer.php b/src/Tokenization/Tokenizer.php similarity index 100% rename from src/Phpml/Tokenization/Tokenizer.php rename to src/Tokenization/Tokenizer.php diff --git a/src/Phpml/Tokenization/WhitespaceTokenizer.php b/src/Tokenization/WhitespaceTokenizer.php similarity index 100% rename from src/Phpml/Tokenization/WhitespaceTokenizer.php rename to src/Tokenization/WhitespaceTokenizer.php diff --git a/src/Phpml/Tokenization/WordTokenizer.php b/src/Tokenization/WordTokenizer.php similarity index 100% rename from src/Phpml/Tokenization/WordTokenizer.php rename to src/Tokenization/WordTokenizer.php diff --git a/src/Phpml/Transformer.php b/src/Transformer.php similarity index 100% rename from src/Phpml/Transformer.php rename to src/Transformer.php diff --git a/tests/Phpml/Association/AprioriTest.php b/tests/Association/AprioriTest.php similarity index 100% rename from tests/Phpml/Association/AprioriTest.php rename to tests/Association/AprioriTest.php diff --git a/tests/Phpml/Classification/DecisionTree/DecisionTreeLeafTest.php b/tests/Classification/DecisionTree/DecisionTreeLeafTest.php similarity index 100% rename from tests/Phpml/Classification/DecisionTree/DecisionTreeLeafTest.php rename to tests/Classification/DecisionTree/DecisionTreeLeafTest.php diff --git a/tests/Phpml/Classification/DecisionTreeTest.php b/tests/Classification/DecisionTreeTest.php similarity index 100% rename from tests/Phpml/Classification/DecisionTreeTest.php rename to tests/Classification/DecisionTreeTest.php diff --git a/tests/Phpml/Classification/Ensemble/AdaBoostTest.php b/tests/Classification/Ensemble/AdaBoostTest.php similarity index 100% rename from tests/Phpml/Classification/Ensemble/AdaBoostTest.php rename to tests/Classification/Ensemble/AdaBoostTest.php diff --git a/tests/Phpml/Classification/Ensemble/BaggingTest.php b/tests/Classification/Ensemble/BaggingTest.php similarity index 100% rename from tests/Phpml/Classification/Ensemble/BaggingTest.php rename to tests/Classification/Ensemble/BaggingTest.php diff --git a/tests/Phpml/Classification/Ensemble/RandomForestTest.php b/tests/Classification/Ensemble/RandomForestTest.php similarity index 100% rename from tests/Phpml/Classification/Ensemble/RandomForestTest.php rename to tests/Classification/Ensemble/RandomForestTest.php diff --git a/tests/Phpml/Classification/KNearestNeighborsTest.php b/tests/Classification/KNearestNeighborsTest.php similarity index 100% rename from tests/Phpml/Classification/KNearestNeighborsTest.php rename to tests/Classification/KNearestNeighborsTest.php diff --git a/tests/Phpml/Classification/Linear/AdalineTest.php b/tests/Classification/Linear/AdalineTest.php similarity index 100% rename from tests/Phpml/Classification/Linear/AdalineTest.php rename to tests/Classification/Linear/AdalineTest.php diff --git a/tests/Phpml/Classification/Linear/DecisionStumpTest.php b/tests/Classification/Linear/DecisionStumpTest.php similarity index 100% rename from tests/Phpml/Classification/Linear/DecisionStumpTest.php rename to tests/Classification/Linear/DecisionStumpTest.php diff --git a/tests/Phpml/Classification/Linear/LogisticRegressionTest.php b/tests/Classification/Linear/LogisticRegressionTest.php similarity index 100% rename from tests/Phpml/Classification/Linear/LogisticRegressionTest.php rename to tests/Classification/Linear/LogisticRegressionTest.php diff --git a/tests/Phpml/Classification/Linear/PerceptronTest.php b/tests/Classification/Linear/PerceptronTest.php similarity index 100% rename from tests/Phpml/Classification/Linear/PerceptronTest.php rename to tests/Classification/Linear/PerceptronTest.php diff --git a/tests/Phpml/Classification/MLPClassifierTest.php b/tests/Classification/MLPClassifierTest.php similarity index 100% rename from tests/Phpml/Classification/MLPClassifierTest.php rename to tests/Classification/MLPClassifierTest.php diff --git a/tests/Phpml/Classification/NaiveBayesTest.php b/tests/Classification/NaiveBayesTest.php similarity index 100% rename from tests/Phpml/Classification/NaiveBayesTest.php rename to tests/Classification/NaiveBayesTest.php diff --git a/tests/Phpml/Classification/SVCTest.php b/tests/Classification/SVCTest.php similarity index 100% rename from tests/Phpml/Classification/SVCTest.php rename to tests/Classification/SVCTest.php diff --git a/tests/Phpml/Clustering/DBSCANTest.php b/tests/Clustering/DBSCANTest.php similarity index 100% rename from tests/Phpml/Clustering/DBSCANTest.php rename to tests/Clustering/DBSCANTest.php diff --git a/tests/Phpml/Clustering/FuzzyCMeansTest.php b/tests/Clustering/FuzzyCMeansTest.php similarity index 100% rename from tests/Phpml/Clustering/FuzzyCMeansTest.php rename to tests/Clustering/FuzzyCMeansTest.php diff --git a/tests/Phpml/Clustering/KMeansTest.php b/tests/Clustering/KMeansTest.php similarity index 100% rename from tests/Phpml/Clustering/KMeansTest.php rename to tests/Clustering/KMeansTest.php diff --git a/tests/Phpml/CrossValidation/RandomSplitTest.php b/tests/CrossValidation/RandomSplitTest.php similarity index 100% rename from tests/Phpml/CrossValidation/RandomSplitTest.php rename to tests/CrossValidation/RandomSplitTest.php diff --git a/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php b/tests/CrossValidation/StratifiedRandomSplitTest.php similarity index 100% rename from tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php rename to tests/CrossValidation/StratifiedRandomSplitTest.php diff --git a/tests/Phpml/Dataset/ArrayDatasetTest.php b/tests/Dataset/ArrayDatasetTest.php similarity index 100% rename from tests/Phpml/Dataset/ArrayDatasetTest.php rename to tests/Dataset/ArrayDatasetTest.php diff --git a/tests/Phpml/Dataset/CsvDatasetTest.php b/tests/Dataset/CsvDatasetTest.php similarity index 100% rename from tests/Phpml/Dataset/CsvDatasetTest.php rename to tests/Dataset/CsvDatasetTest.php diff --git a/tests/Phpml/Dataset/Demo/GlassDatasetTest.php b/tests/Dataset/Demo/GlassDatasetTest.php similarity index 100% rename from tests/Phpml/Dataset/Demo/GlassDatasetTest.php rename to tests/Dataset/Demo/GlassDatasetTest.php diff --git a/tests/Phpml/Dataset/Demo/IrisDatasetTest.php b/tests/Dataset/Demo/IrisDatasetTest.php similarity index 100% rename from tests/Phpml/Dataset/Demo/IrisDatasetTest.php rename to tests/Dataset/Demo/IrisDatasetTest.php diff --git a/tests/Phpml/Dataset/Demo/WineDatasetTest.php b/tests/Dataset/Demo/WineDatasetTest.php similarity index 100% rename from tests/Phpml/Dataset/Demo/WineDatasetTest.php rename to tests/Dataset/Demo/WineDatasetTest.php diff --git a/tests/Phpml/Dataset/FilesDatasetTest.php b/tests/Dataset/FilesDatasetTest.php similarity index 100% rename from tests/Phpml/Dataset/FilesDatasetTest.php rename to tests/Dataset/FilesDatasetTest.php diff --git a/tests/Phpml/Dataset/Resources/bbc/business/001.txt b/tests/Dataset/Resources/bbc/business/001.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/business/001.txt rename to tests/Dataset/Resources/bbc/business/001.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/business/002.txt b/tests/Dataset/Resources/bbc/business/002.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/business/002.txt rename to tests/Dataset/Resources/bbc/business/002.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/business/003.txt b/tests/Dataset/Resources/bbc/business/003.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/business/003.txt rename to tests/Dataset/Resources/bbc/business/003.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/business/004.txt b/tests/Dataset/Resources/bbc/business/004.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/business/004.txt rename to tests/Dataset/Resources/bbc/business/004.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/business/005.txt b/tests/Dataset/Resources/bbc/business/005.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/business/005.txt rename to tests/Dataset/Resources/bbc/business/005.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/business/006.txt b/tests/Dataset/Resources/bbc/business/006.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/business/006.txt rename to tests/Dataset/Resources/bbc/business/006.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/business/007.txt b/tests/Dataset/Resources/bbc/business/007.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/business/007.txt rename to tests/Dataset/Resources/bbc/business/007.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/business/008.txt b/tests/Dataset/Resources/bbc/business/008.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/business/008.txt rename to tests/Dataset/Resources/bbc/business/008.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/business/009.txt b/tests/Dataset/Resources/bbc/business/009.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/business/009.txt rename to tests/Dataset/Resources/bbc/business/009.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/business/010.txt b/tests/Dataset/Resources/bbc/business/010.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/business/010.txt rename to tests/Dataset/Resources/bbc/business/010.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/entertainment/001.txt b/tests/Dataset/Resources/bbc/entertainment/001.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/entertainment/001.txt rename to tests/Dataset/Resources/bbc/entertainment/001.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/entertainment/002.txt b/tests/Dataset/Resources/bbc/entertainment/002.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/entertainment/002.txt rename to tests/Dataset/Resources/bbc/entertainment/002.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/entertainment/003.txt b/tests/Dataset/Resources/bbc/entertainment/003.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/entertainment/003.txt rename to tests/Dataset/Resources/bbc/entertainment/003.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/entertainment/004.txt b/tests/Dataset/Resources/bbc/entertainment/004.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/entertainment/004.txt rename to tests/Dataset/Resources/bbc/entertainment/004.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/entertainment/005.txt b/tests/Dataset/Resources/bbc/entertainment/005.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/entertainment/005.txt rename to tests/Dataset/Resources/bbc/entertainment/005.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/entertainment/006.txt b/tests/Dataset/Resources/bbc/entertainment/006.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/entertainment/006.txt rename to tests/Dataset/Resources/bbc/entertainment/006.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/entertainment/007.txt b/tests/Dataset/Resources/bbc/entertainment/007.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/entertainment/007.txt rename to tests/Dataset/Resources/bbc/entertainment/007.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/entertainment/008.txt b/tests/Dataset/Resources/bbc/entertainment/008.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/entertainment/008.txt rename to tests/Dataset/Resources/bbc/entertainment/008.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/entertainment/009.txt b/tests/Dataset/Resources/bbc/entertainment/009.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/entertainment/009.txt rename to tests/Dataset/Resources/bbc/entertainment/009.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/entertainment/010.txt b/tests/Dataset/Resources/bbc/entertainment/010.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/entertainment/010.txt rename to tests/Dataset/Resources/bbc/entertainment/010.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/politics/001.txt b/tests/Dataset/Resources/bbc/politics/001.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/politics/001.txt rename to tests/Dataset/Resources/bbc/politics/001.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/politics/002.txt b/tests/Dataset/Resources/bbc/politics/002.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/politics/002.txt rename to tests/Dataset/Resources/bbc/politics/002.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/politics/003.txt b/tests/Dataset/Resources/bbc/politics/003.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/politics/003.txt rename to tests/Dataset/Resources/bbc/politics/003.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/politics/004.txt b/tests/Dataset/Resources/bbc/politics/004.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/politics/004.txt rename to tests/Dataset/Resources/bbc/politics/004.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/politics/005.txt b/tests/Dataset/Resources/bbc/politics/005.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/politics/005.txt rename to tests/Dataset/Resources/bbc/politics/005.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/politics/006.txt b/tests/Dataset/Resources/bbc/politics/006.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/politics/006.txt rename to tests/Dataset/Resources/bbc/politics/006.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/politics/007.txt b/tests/Dataset/Resources/bbc/politics/007.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/politics/007.txt rename to tests/Dataset/Resources/bbc/politics/007.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/politics/008.txt b/tests/Dataset/Resources/bbc/politics/008.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/politics/008.txt rename to tests/Dataset/Resources/bbc/politics/008.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/politics/009.txt b/tests/Dataset/Resources/bbc/politics/009.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/politics/009.txt rename to tests/Dataset/Resources/bbc/politics/009.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/politics/010.txt b/tests/Dataset/Resources/bbc/politics/010.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/politics/010.txt rename to tests/Dataset/Resources/bbc/politics/010.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/sport/001.txt b/tests/Dataset/Resources/bbc/sport/001.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/sport/001.txt rename to tests/Dataset/Resources/bbc/sport/001.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/sport/002.txt b/tests/Dataset/Resources/bbc/sport/002.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/sport/002.txt rename to tests/Dataset/Resources/bbc/sport/002.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/sport/003.txt b/tests/Dataset/Resources/bbc/sport/003.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/sport/003.txt rename to tests/Dataset/Resources/bbc/sport/003.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/sport/004.txt b/tests/Dataset/Resources/bbc/sport/004.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/sport/004.txt rename to tests/Dataset/Resources/bbc/sport/004.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/sport/005.txt b/tests/Dataset/Resources/bbc/sport/005.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/sport/005.txt rename to tests/Dataset/Resources/bbc/sport/005.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/sport/006.txt b/tests/Dataset/Resources/bbc/sport/006.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/sport/006.txt rename to tests/Dataset/Resources/bbc/sport/006.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/sport/007.txt b/tests/Dataset/Resources/bbc/sport/007.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/sport/007.txt rename to tests/Dataset/Resources/bbc/sport/007.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/sport/008.txt b/tests/Dataset/Resources/bbc/sport/008.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/sport/008.txt rename to tests/Dataset/Resources/bbc/sport/008.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/sport/009.txt b/tests/Dataset/Resources/bbc/sport/009.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/sport/009.txt rename to tests/Dataset/Resources/bbc/sport/009.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/sport/010.txt b/tests/Dataset/Resources/bbc/sport/010.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/sport/010.txt rename to tests/Dataset/Resources/bbc/sport/010.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/tech/001.txt b/tests/Dataset/Resources/bbc/tech/001.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/tech/001.txt rename to tests/Dataset/Resources/bbc/tech/001.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/tech/002.txt b/tests/Dataset/Resources/bbc/tech/002.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/tech/002.txt rename to tests/Dataset/Resources/bbc/tech/002.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/tech/003.txt b/tests/Dataset/Resources/bbc/tech/003.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/tech/003.txt rename to tests/Dataset/Resources/bbc/tech/003.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/tech/004.txt b/tests/Dataset/Resources/bbc/tech/004.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/tech/004.txt rename to tests/Dataset/Resources/bbc/tech/004.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/tech/005.txt b/tests/Dataset/Resources/bbc/tech/005.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/tech/005.txt rename to tests/Dataset/Resources/bbc/tech/005.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/tech/006.txt b/tests/Dataset/Resources/bbc/tech/006.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/tech/006.txt rename to tests/Dataset/Resources/bbc/tech/006.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/tech/007.txt b/tests/Dataset/Resources/bbc/tech/007.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/tech/007.txt rename to tests/Dataset/Resources/bbc/tech/007.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/tech/008.txt b/tests/Dataset/Resources/bbc/tech/008.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/tech/008.txt rename to tests/Dataset/Resources/bbc/tech/008.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/tech/009.txt b/tests/Dataset/Resources/bbc/tech/009.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/tech/009.txt rename to tests/Dataset/Resources/bbc/tech/009.txt diff --git a/tests/Phpml/Dataset/Resources/bbc/tech/010.txt b/tests/Dataset/Resources/bbc/tech/010.txt similarity index 100% rename from tests/Phpml/Dataset/Resources/bbc/tech/010.txt rename to tests/Dataset/Resources/bbc/tech/010.txt diff --git a/tests/Phpml/Dataset/Resources/dataset.csv b/tests/Dataset/Resources/dataset.csv similarity index 100% rename from tests/Phpml/Dataset/Resources/dataset.csv rename to tests/Dataset/Resources/dataset.csv diff --git a/tests/Phpml/Dataset/Resources/longdataset.csv b/tests/Dataset/Resources/longdataset.csv similarity index 100% rename from tests/Phpml/Dataset/Resources/longdataset.csv rename to tests/Dataset/Resources/longdataset.csv diff --git a/tests/Phpml/DimensionReduction/KernelPCATest.php b/tests/DimensionReduction/KernelPCATest.php similarity index 100% rename from tests/Phpml/DimensionReduction/KernelPCATest.php rename to tests/DimensionReduction/KernelPCATest.php diff --git a/tests/Phpml/DimensionReduction/LDATest.php b/tests/DimensionReduction/LDATest.php similarity index 100% rename from tests/Phpml/DimensionReduction/LDATest.php rename to tests/DimensionReduction/LDATest.php diff --git a/tests/Phpml/DimensionReduction/PCATest.php b/tests/DimensionReduction/PCATest.php similarity index 100% rename from tests/Phpml/DimensionReduction/PCATest.php rename to tests/DimensionReduction/PCATest.php diff --git a/tests/Phpml/FeatureExtraction/StopWordsTest.php b/tests/FeatureExtraction/StopWordsTest.php similarity index 100% rename from tests/Phpml/FeatureExtraction/StopWordsTest.php rename to tests/FeatureExtraction/StopWordsTest.php diff --git a/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php b/tests/FeatureExtraction/TfIdfTransformerTest.php similarity index 100% rename from tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php rename to tests/FeatureExtraction/TfIdfTransformerTest.php diff --git a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php b/tests/FeatureExtraction/TokenCountVectorizerTest.php similarity index 100% rename from tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php rename to tests/FeatureExtraction/TokenCountVectorizerTest.php diff --git a/tests/Phpml/Helper/Optimizer/ConjugateGradientTest.php b/tests/Helper/Optimizer/ConjugateGradientTest.php similarity index 100% rename from tests/Phpml/Helper/Optimizer/ConjugateGradientTest.php rename to tests/Helper/Optimizer/ConjugateGradientTest.php diff --git a/tests/Phpml/Helper/Optimizer/GDTest.php b/tests/Helper/Optimizer/GDTest.php similarity index 100% rename from tests/Phpml/Helper/Optimizer/GDTest.php rename to tests/Helper/Optimizer/GDTest.php diff --git a/tests/Phpml/Helper/Optimizer/StochasticGDTest.php b/tests/Helper/Optimizer/StochasticGDTest.php similarity index 100% rename from tests/Phpml/Helper/Optimizer/StochasticGDTest.php rename to tests/Helper/Optimizer/StochasticGDTest.php diff --git a/tests/Phpml/Math/ComparisonTest.php b/tests/Math/ComparisonTest.php similarity index 100% rename from tests/Phpml/Math/ComparisonTest.php rename to tests/Math/ComparisonTest.php diff --git a/tests/Phpml/Math/Distance/ChebyshevTest.php b/tests/Math/Distance/ChebyshevTest.php similarity index 100% rename from tests/Phpml/Math/Distance/ChebyshevTest.php rename to tests/Math/Distance/ChebyshevTest.php diff --git a/tests/Phpml/Math/Distance/EuclideanTest.php b/tests/Math/Distance/EuclideanTest.php similarity index 100% rename from tests/Phpml/Math/Distance/EuclideanTest.php rename to tests/Math/Distance/EuclideanTest.php diff --git a/tests/Phpml/Math/Distance/ManhattanTest.php b/tests/Math/Distance/ManhattanTest.php similarity index 100% rename from tests/Phpml/Math/Distance/ManhattanTest.php rename to tests/Math/Distance/ManhattanTest.php diff --git a/tests/Phpml/Math/Distance/MinkowskiTest.php b/tests/Math/Distance/MinkowskiTest.php similarity index 100% rename from tests/Phpml/Math/Distance/MinkowskiTest.php rename to tests/Math/Distance/MinkowskiTest.php diff --git a/tests/Phpml/Math/Kernel/RBFTest.php b/tests/Math/Kernel/RBFTest.php similarity index 100% rename from tests/Phpml/Math/Kernel/RBFTest.php rename to tests/Math/Kernel/RBFTest.php diff --git a/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php b/tests/Math/LinearAlgebra/EigenDecompositionTest.php similarity index 100% rename from tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php rename to tests/Math/LinearAlgebra/EigenDecompositionTest.php diff --git a/tests/Phpml/Math/MatrixTest.php b/tests/Math/MatrixTest.php similarity index 100% rename from tests/Phpml/Math/MatrixTest.php rename to tests/Math/MatrixTest.php diff --git a/tests/Phpml/Math/ProductTest.php b/tests/Math/ProductTest.php similarity index 100% rename from tests/Phpml/Math/ProductTest.php rename to tests/Math/ProductTest.php diff --git a/tests/Phpml/Math/SetTest.php b/tests/Math/SetTest.php similarity index 100% rename from tests/Phpml/Math/SetTest.php rename to tests/Math/SetTest.php diff --git a/tests/Phpml/Math/Statistic/CorrelationTest.php b/tests/Math/Statistic/CorrelationTest.php similarity index 100% rename from tests/Phpml/Math/Statistic/CorrelationTest.php rename to tests/Math/Statistic/CorrelationTest.php diff --git a/tests/Phpml/Math/Statistic/CovarianceTest.php b/tests/Math/Statistic/CovarianceTest.php similarity index 100% rename from tests/Phpml/Math/Statistic/CovarianceTest.php rename to tests/Math/Statistic/CovarianceTest.php diff --git a/tests/Phpml/Math/Statistic/GaussianTest.php b/tests/Math/Statistic/GaussianTest.php similarity index 100% rename from tests/Phpml/Math/Statistic/GaussianTest.php rename to tests/Math/Statistic/GaussianTest.php diff --git a/tests/Phpml/Math/Statistic/MeanTest.php b/tests/Math/Statistic/MeanTest.php similarity index 100% rename from tests/Phpml/Math/Statistic/MeanTest.php rename to tests/Math/Statistic/MeanTest.php diff --git a/tests/Phpml/Math/Statistic/StandardDeviationTest.php b/tests/Math/Statistic/StandardDeviationTest.php similarity index 100% rename from tests/Phpml/Math/Statistic/StandardDeviationTest.php rename to tests/Math/Statistic/StandardDeviationTest.php diff --git a/tests/Phpml/Metric/AccuracyTest.php b/tests/Metric/AccuracyTest.php similarity index 100% rename from tests/Phpml/Metric/AccuracyTest.php rename to tests/Metric/AccuracyTest.php diff --git a/tests/Phpml/Metric/ClassificationReportTest.php b/tests/Metric/ClassificationReportTest.php similarity index 100% rename from tests/Phpml/Metric/ClassificationReportTest.php rename to tests/Metric/ClassificationReportTest.php diff --git a/tests/Phpml/Metric/ConfusionMatrixTest.php b/tests/Metric/ConfusionMatrixTest.php similarity index 100% rename from tests/Phpml/Metric/ConfusionMatrixTest.php rename to tests/Metric/ConfusionMatrixTest.php diff --git a/tests/Phpml/ModelManagerTest.php b/tests/ModelManagerTest.php similarity index 100% rename from tests/Phpml/ModelManagerTest.php rename to tests/ModelManagerTest.php diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php b/tests/NeuralNetwork/ActivationFunction/BinaryStepTest.php similarity index 100% rename from tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php rename to tests/NeuralNetwork/ActivationFunction/BinaryStepTest.php diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php b/tests/NeuralNetwork/ActivationFunction/GaussianTest.php similarity index 100% rename from tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php rename to tests/NeuralNetwork/ActivationFunction/GaussianTest.php diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php b/tests/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php similarity index 100% rename from tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php rename to tests/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php b/tests/NeuralNetwork/ActivationFunction/PReLUTest.php similarity index 100% rename from tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php rename to tests/NeuralNetwork/ActivationFunction/PReLUTest.php diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php b/tests/NeuralNetwork/ActivationFunction/SigmoidTest.php similarity index 100% rename from tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php rename to tests/NeuralNetwork/ActivationFunction/SigmoidTest.php diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php b/tests/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php similarity index 100% rename from tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php rename to tests/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php diff --git a/tests/Phpml/NeuralNetwork/LayerTest.php b/tests/NeuralNetwork/LayerTest.php similarity index 100% rename from tests/Phpml/NeuralNetwork/LayerTest.php rename to tests/NeuralNetwork/LayerTest.php diff --git a/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php b/tests/NeuralNetwork/Network/LayeredNetworkTest.php similarity index 100% rename from tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php rename to tests/NeuralNetwork/Network/LayeredNetworkTest.php diff --git a/tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php b/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php similarity index 100% rename from tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php rename to tests/NeuralNetwork/Network/MultilayerPerceptronTest.php diff --git a/tests/Phpml/NeuralNetwork/Node/BiasTest.php b/tests/NeuralNetwork/Node/BiasTest.php similarity index 100% rename from tests/Phpml/NeuralNetwork/Node/BiasTest.php rename to tests/NeuralNetwork/Node/BiasTest.php diff --git a/tests/Phpml/NeuralNetwork/Node/InputTest.php b/tests/NeuralNetwork/Node/InputTest.php similarity index 100% rename from tests/Phpml/NeuralNetwork/Node/InputTest.php rename to tests/NeuralNetwork/Node/InputTest.php diff --git a/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php b/tests/NeuralNetwork/Node/Neuron/SynapseTest.php similarity index 100% rename from tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php rename to tests/NeuralNetwork/Node/Neuron/SynapseTest.php diff --git a/tests/Phpml/NeuralNetwork/Node/NeuronTest.php b/tests/NeuralNetwork/Node/NeuronTest.php similarity index 100% rename from tests/Phpml/NeuralNetwork/Node/NeuronTest.php rename to tests/NeuralNetwork/Node/NeuronTest.php diff --git a/tests/Phpml/PipelineTest.php b/tests/PipelineTest.php similarity index 100% rename from tests/Phpml/PipelineTest.php rename to tests/PipelineTest.php diff --git a/tests/Phpml/Preprocessing/ImputerTest.php b/tests/Preprocessing/ImputerTest.php similarity index 100% rename from tests/Phpml/Preprocessing/ImputerTest.php rename to tests/Preprocessing/ImputerTest.php diff --git a/tests/Phpml/Preprocessing/NormalizerTest.php b/tests/Preprocessing/NormalizerTest.php similarity index 100% rename from tests/Phpml/Preprocessing/NormalizerTest.php rename to tests/Preprocessing/NormalizerTest.php diff --git a/tests/Phpml/Regression/LeastSquaresTest.php b/tests/Regression/LeastSquaresTest.php similarity index 100% rename from tests/Phpml/Regression/LeastSquaresTest.php rename to tests/Regression/LeastSquaresTest.php diff --git a/tests/Phpml/Regression/SVRTest.php b/tests/Regression/SVRTest.php similarity index 100% rename from tests/Phpml/Regression/SVRTest.php rename to tests/Regression/SVRTest.php diff --git a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php b/tests/SupportVectorMachine/DataTransformerTest.php similarity index 100% rename from tests/Phpml/SupportVectorMachine/DataTransformerTest.php rename to tests/SupportVectorMachine/DataTransformerTest.php diff --git a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php b/tests/SupportVectorMachine/SupportVectorMachineTest.php similarity index 100% rename from tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php rename to tests/SupportVectorMachine/SupportVectorMachineTest.php diff --git a/tests/Phpml/Tokenization/WhitespaceTokenizerTest.php b/tests/Tokenization/WhitespaceTokenizerTest.php similarity index 100% rename from tests/Phpml/Tokenization/WhitespaceTokenizerTest.php rename to tests/Tokenization/WhitespaceTokenizerTest.php diff --git a/tests/Phpml/Tokenization/WordTokenizerTest.php b/tests/Tokenization/WordTokenizerTest.php similarity index 100% rename from tests/Phpml/Tokenization/WordTokenizerTest.php rename to tests/Tokenization/WordTokenizerTest.php From 3ba35918a3c0d85ee8cb54b9999dee23be4737dd Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sat, 10 Feb 2018 18:07:09 +0100 Subject: [PATCH 241/328] Implement VarianceThreshold - simple baseline approach to feature selection. (#228) * Add sum of squares deviations * Calculate population variance * Add VarianceThreshold - feature selection transformer * Add docs about VarianceThreshold * Add missing code for pipeline usage --- README.md | 2 + docs/index.md | 2 + .../feature-selection/variance-threshold.md | 60 +++++++++++++++++++ mkdocs.yml | 2 + src/FeatureSelection/VarianceThreshold.php | 59 ++++++++++++++++++ src/Math/Statistic/StandardDeviation.php | 39 ++++++++---- src/Math/Statistic/Variance.php | 27 +++++++++ .../VarianceThresholdTest.php | 39 ++++++++++++ .../Math/Statistic/StandardDeviationTest.php | 25 ++++++++ tests/Math/Statistic/VarianceTest.php | 34 +++++++++++ 10 files changed, 279 insertions(+), 10 deletions(-) create mode 100644 docs/machine-learning/feature-selection/variance-threshold.md create mode 100644 src/FeatureSelection/VarianceThreshold.php create mode 100644 src/Math/Statistic/Variance.php create mode 100644 tests/FeatureSelection/VarianceThresholdTest.php create mode 100644 tests/Math/Statistic/VarianceTest.php diff --git a/README.md b/README.md index 874d0d25..595714dd 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,8 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * Cross Validation * [Random Split](http://php-ml.readthedocs.io/en/latest/machine-learning/cross-validation/random-split/) * [Stratified Random Split](http://php-ml.readthedocs.io/en/latest/machine-learning/cross-validation/stratified-random-split/) +* Feature Selection + * [Variance Threshold](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-selection/variance-threshold/) * Preprocessing * [Normalization](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/normalization/) * [Imputation missing values](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/imputation-missing-values/) diff --git a/docs/index.md b/docs/index.md index f817b0ac..eb505637 100644 --- a/docs/index.md +++ b/docs/index.md @@ -76,6 +76,8 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * Cross Validation * [Random Split](machine-learning/cross-validation/random-split.md) * [Stratified Random Split](machine-learning/cross-validation/stratified-random-split.md) +* Feature Selection + * [Variance Threshold](machine-learning/feature-selection/variance-threshold.md) * Preprocessing * [Normalization](machine-learning/preprocessing/normalization.md) * [Imputation missing values](machine-learning/preprocessing/imputation-missing-values.md) diff --git a/docs/machine-learning/feature-selection/variance-threshold.md b/docs/machine-learning/feature-selection/variance-threshold.md new file mode 100644 index 00000000..9c942e79 --- /dev/null +++ b/docs/machine-learning/feature-selection/variance-threshold.md @@ -0,0 +1,60 @@ +# Variance Threshold + +`VarianceThreshold` is a simple baseline approach to feature selection. +It removes all features whose variance doesn’t meet some threshold. +By default, it removes all zero-variance features, i.e. features that have the same value in all samples. + +## Constructor Parameters + +* $threshold (float) - features with a variance lower than this threshold will be removed (default 0.0) + +```php +use Phpml\FeatureSelection\VarianceThreshold; + +$transformer = new VarianceThreshold(0.15); +``` + +## Example of use + +As an example, suppose that we have a dataset with boolean features and +we want to remove all features that are either one or zero (on or off) +in more than 80% of the samples. +Boolean features are Bernoulli random variables, and the variance of such +variables is given by +``` +Var[X] = p(1 - p) +``` +so we can select using the threshold .8 * (1 - .8): + +```php +use Phpml\FeatureSelection\VarianceThreshold; + +$samples = [[0, 0, 1], [0, 1, 0], [1, 0, 0], [0, 1, 1], [0, 1, 0], [0, 1, 1]]; +$transformer = new VarianceThreshold(0.8 * (1 - 0.8)); + +$transformer->fit($samples); +$transformer->transform($samples); + +/* +$samples = [[0, 1], [1, 0], [0, 0], [1, 1], [1, 0], [1, 1]]; +*/ +``` + +## Pipeline + +`VarianceThreshold` implements `Transformer` interface so it can be used as part of pipeline: + +```php +use Phpml\FeatureSelection\VarianceThreshold; +use Phpml\Classification\SVC; +use Phpml\FeatureExtraction\TfIdfTransformer; +use Phpml\Pipeline; + +$transformers = [ + new TfIdfTransformer(), + new VarianceThreshold(0.1) +]; +$estimator = new SVC(); + +$pipeline = new Pipeline($transformers, $estimator); +``` diff --git a/mkdocs.yml b/mkdocs.yml index 8c9c10c4..f794320e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -25,6 +25,8 @@ pages: - Cross Validation: - RandomSplit: machine-learning/cross-validation/random-split.md - Stratified Random Split: machine-learning/cross-validation/stratified-random-split.md + - Feature Selection: + - VarianceThreshold: machine-learning/feature-selection/variance-threshold.md - Preprocessing: - Normalization: machine-learning/preprocessing/normalization.md - Imputation missing values: machine-learning/preprocessing/imputation-missing-values.md diff --git a/src/FeatureSelection/VarianceThreshold.php b/src/FeatureSelection/VarianceThreshold.php new file mode 100644 index 00000000..6a3d6395 --- /dev/null +++ b/src/FeatureSelection/VarianceThreshold.php @@ -0,0 +1,59 @@ +threshold = $threshold; + $this->variances = []; + $this->keepColumns = []; + } + + public function fit(array $samples): void + { + $this->variances = array_map(function (array $column) { + return Variance::population($column); + }, Matrix::transposeArray($samples)); + + foreach ($this->variances as $column => $variance) { + if ($variance > $this->threshold) { + $this->keepColumns[$column] = true; + } + } + } + + public function transform(array &$samples): void + { + foreach ($samples as &$sample) { + $sample = array_values(array_intersect_key($sample, $this->keepColumns)); + } + } +} diff --git a/src/Math/Statistic/StandardDeviation.php b/src/Math/Statistic/StandardDeviation.php index 8a0d2411..426e4fd5 100644 --- a/src/Math/Statistic/StandardDeviation.php +++ b/src/Math/Statistic/StandardDeviation.php @@ -9,27 +9,24 @@ class StandardDeviation { /** - * @param array|float[] $a - * - * @throws InvalidArgumentException + * @param array|float[]|int[] $numbers */ - public static function population(array $a, bool $sample = true): float + public static function population(array $numbers, bool $sample = true): float { - if (empty($a)) { + if (empty($numbers)) { throw InvalidArgumentException::arrayCantBeEmpty(); } - $n = count($a); + $n = count($numbers); if ($sample && $n === 1) { throw InvalidArgumentException::arraySizeToSmall(2); } - $mean = Mean::arithmetic($a); + $mean = Mean::arithmetic($numbers); $carry = 0.0; - foreach ($a as $val) { - $d = $val - $mean; - $carry += $d * $d; + foreach ($numbers as $val) { + $carry += ($val - $mean) ** 2; } if ($sample) { @@ -38,4 +35,26 @@ public static function population(array $a, bool $sample = true): float return sqrt((float) ($carry / $n)); } + + /** + * Sum of squares deviations + * ∑⟮xᵢ - μ⟯² + * + * @param array|float[]|int[] $numbers + */ + public static function sumOfSquares(array $numbers): float + { + if (empty($numbers)) { + throw InvalidArgumentException::arrayCantBeEmpty(); + } + + $mean = Mean::arithmetic($numbers); + + return array_sum(array_map( + function ($val) use ($mean) { + return ($val - $mean) ** 2; + }, + $numbers + )); + } } diff --git a/src/Math/Statistic/Variance.php b/src/Math/Statistic/Variance.php new file mode 100644 index 00000000..641cf007 --- /dev/null +++ b/src/Math/Statistic/Variance.php @@ -0,0 +1,27 @@ +fit($samples); + $transformer->transform($samples); + + // expecting to remove first column + self::assertEquals([[0, 1], [1, 0], [0, 0], [1, 1], [1, 0], [1, 1]], $samples); + } + + public function testVarianceThresholdWithZeroThreshold(): void + { + $samples = [[0, 2, 0, 3], [0, 1, 4, 3], [0, 1, 1, 3]]; + $transformer = new VarianceThreshold(); + $transformer->fit($samples); + $transformer->transform($samples); + + self::assertEquals([[2, 0], [1, 4], [1, 1]], $samples); + } + + public function testThrowExceptionWhenThresholdBelowZero(): void + { + $this->expectException(InvalidArgumentException::class); + new VarianceThreshold(-0.1); + } +} diff --git a/tests/Math/Statistic/StandardDeviationTest.php b/tests/Math/Statistic/StandardDeviationTest.php index 8333740c..51c27704 100644 --- a/tests/Math/Statistic/StandardDeviationTest.php +++ b/tests/Math/Statistic/StandardDeviationTest.php @@ -37,4 +37,29 @@ public function testThrowExceptionOnToSmallArray(): void $this->expectException(InvalidArgumentException::class); StandardDeviation::population([1]); } + + /** + * @dataProvider dataProviderForSumOfSquaresDeviations + */ + public function testSumOfSquares(array $numbers, float $sum): void + { + self::assertEquals($sum, StandardDeviation::sumOfSquares($numbers), '', 0.0001); + } + + public function dataProviderForSumOfSquaresDeviations(): array + { + return [ + [[3, 6, 7, 11, 12, 13, 17], 136.8571], + [[6, 11, 12, 14, 15, 20, 21], 162.8571], + [[1, 2, 3, 6, 7, 11, 12], 112], + [[1, 2, 3, 4, 5, 6, 7, 8, 9, 0], 82.5], + [[34, 253, 754, 2342, 75, 23, 876, 4, 1, -34, -345, 754, -377, 3, 0], 6453975.7333], + ]; + } + + public function testThrowExceptionOnEmptyArraySumOfSquares(): void + { + $this->expectException(InvalidArgumentException::class); + StandardDeviation::sumOfSquares([]); + } } diff --git a/tests/Math/Statistic/VarianceTest.php b/tests/Math/Statistic/VarianceTest.php new file mode 100644 index 00000000..19b2cd8a --- /dev/null +++ b/tests/Math/Statistic/VarianceTest.php @@ -0,0 +1,34 @@ + Date: Sun, 11 Feb 2018 20:42:46 +0900 Subject: [PATCH 242/328] Fix support of a rule in Apriori (#229) * Clean up test code * Add test to check support and confidence (failed due to a bug) * Fix support value of rules --- src/Association/Apriori.php | 2 +- tests/Association/AprioriTest.php | 169 +++++++++++++++++------------- 2 files changed, 100 insertions(+), 71 deletions(-) diff --git a/src/Association/Apriori.php b/src/Association/Apriori.php index b6079cc1..1d7f99fe 100644 --- a/src/Association/Apriori.php +++ b/src/Association/Apriori.php @@ -138,7 +138,7 @@ private function generateRules(array $frequent): void $this->rules[] = [ self::ARRAY_KEY_ANTECEDENT => $antecedent, self::ARRAY_KEY_CONSEQUENT => $consequent, - self::ARRAY_KEY_SUPPORT => $this->support($consequent), + self::ARRAY_KEY_SUPPORT => $this->support($frequent), self::ARRAY_KEY_CONFIDENCE => $confidence, ]; } diff --git a/tests/Association/AprioriTest.php b/tests/Association/AprioriTest.php index 3ba69016..5ed4f8c0 100644 --- a/tests/Association/AprioriTest.php +++ b/tests/Association/AprioriTest.php @@ -46,15 +46,18 @@ public function testGreek(): void $apriori = new Apriori(0.5, 0.5); $apriori->train($this->sampleGreek, []); - $this->assertEquals('beta', $apriori->predict([['alpha', 'epsilon'], ['beta', 'theta']])[0][0][0]); - $this->assertEquals('alpha', $apriori->predict([['alpha', 'epsilon'], ['beta', 'theta']])[1][0][0]); + $predicted = $apriori->predict([['alpha', 'epsilon'], ['beta', 'theta']]); + + $this->assertCount(2, $predicted); + $this->assertEquals([['beta']], $predicted[0]); + $this->assertEquals([['alpha']], $predicted[1]); } public function testPowerSet(): void { $apriori = new Apriori(); - $this->assertCount(8, $this->invoke($apriori, 'powerSet', [['a', 'b', 'c']])); + $this->assertCount(8, self::invoke($apriori, 'powerSet', [['a', 'b', 'c']])); } public function testApriori(): void @@ -65,12 +68,37 @@ public function testApriori(): void $L = $apriori->apriori(); $this->assertCount(4, $L[2]); - $this->assertTrue($this->invoke($apriori, 'contains', [$L[2], [1, 2]])); - $this->assertFalse($this->invoke($apriori, 'contains', [$L[2], [1, 3]])); - $this->assertFalse($this->invoke($apriori, 'contains', [$L[2], [1, 4]])); - $this->assertTrue($this->invoke($apriori, 'contains', [$L[2], [2, 3]])); - $this->assertTrue($this->invoke($apriori, 'contains', [$L[2], [2, 4]])); - $this->assertTrue($this->invoke($apriori, 'contains', [$L[2], [3, 4]])); + $this->assertTrue(self::invoke($apriori, 'contains', [$L[2], [1, 2]])); + $this->assertFalse(self::invoke($apriori, 'contains', [$L[2], [1, 3]])); + $this->assertFalse(self::invoke($apriori, 'contains', [$L[2], [1, 4]])); + $this->assertTrue(self::invoke($apriori, 'contains', [$L[2], [2, 3]])); + $this->assertTrue(self::invoke($apriori, 'contains', [$L[2], [2, 4]])); + $this->assertTrue(self::invoke($apriori, 'contains', [$L[2], [3, 4]])); + } + + public function testAprioriEmpty(): void + { + $sample = []; + + $apriori = new Apriori(0, 0); + $apriori->train($sample, []); + + $L = $apriori->apriori(); + + $this->assertEmpty($L); + } + + public function testAprioriSingleItem(): void + { + $sample = [['a']]; + + $apriori = new Apriori(0, 0); + $apriori->train($sample, []); + + $L = $apriori->apriori(); + + $this->assertEquals([1], array_keys($L)); + $this->assertEquals([['a']], $L[1]); } public function testGetRules(): void @@ -81,18 +109,42 @@ public function testGetRules(): void $this->assertCount(19, $apriori->getRules()); } + public function testGetRulesSupportAndConfidence(): void + { + $sample = [['a', 'b'], ['a', 'c']]; + + $apriori = new Apriori(0, 0); + $apriori->train($sample, []); + + $rules = $apriori->getRules(); + + $this->assertCount(4, $rules); + $this->assertContains([ + Apriori::ARRAY_KEY_ANTECEDENT => ['a'], + Apriori::ARRAY_KEY_CONSEQUENT => ['b'], + Apriori::ARRAY_KEY_SUPPORT => 0.5, + Apriori::ARRAY_KEY_CONFIDENCE => 0.5, + ], $rules); + $this->assertContains([ + Apriori::ARRAY_KEY_ANTECEDENT => ['b'], + Apriori::ARRAY_KEY_CONSEQUENT => ['a'], + Apriori::ARRAY_KEY_SUPPORT => 0.5, + Apriori::ARRAY_KEY_CONFIDENCE => 1.0, + ], $rules); + } + public function testAntecedents(): void { $apriori = new Apriori(); - $this->assertCount(6, $this->invoke($apriori, 'antecedents', [['a', 'b', 'c']])); + $this->assertCount(6, self::invoke($apriori, 'antecedents', [['a', 'b', 'c']])); } public function testItems(): void { $apriori = new Apriori(); $apriori->train($this->sampleGreek, []); - $this->assertCount(4, $this->invoke($apriori, 'items', [])); + $this->assertCount(4, self::invoke($apriori, 'items', [])); } public function testFrequent(): void @@ -100,8 +152,8 @@ public function testFrequent(): void $apriori = new Apriori(0.51); $apriori->train($this->sampleGreek, []); - $this->assertCount(0, $this->invoke($apriori, 'frequent', [[['epsilon'], ['theta']]])); - $this->assertCount(2, $this->invoke($apriori, 'frequent', [[['alpha'], ['beta']]])); + $this->assertCount(0, self::invoke($apriori, 'frequent', [[['epsilon'], ['theta']]])); + $this->assertCount(2, self::invoke($apriori, 'frequent', [[['alpha'], ['beta']]])); } public function testCandidates(): void @@ -109,10 +161,12 @@ public function testCandidates(): void $apriori = new Apriori(); $apriori->train($this->sampleGreek, []); - $this->assertArraySubset([0 => ['alpha', 'beta']], $this->invoke($apriori, 'candidates', [[['alpha'], ['beta'], ['theta']]])); - $this->assertArraySubset([1 => ['alpha', 'theta']], $this->invoke($apriori, 'candidates', [[['alpha'], ['beta'], ['theta']]])); - $this->assertArraySubset([2 => ['beta', 'theta']], $this->invoke($apriori, 'candidates', [[['alpha'], ['beta'], ['theta']]])); - $this->assertCount(3, $this->invoke($apriori, 'candidates', [[['alpha'], ['beta'], ['theta']]])); + $candidates = self::invoke($apriori, 'candidates', [[['alpha'], ['beta'], ['theta']]]); + + $this->assertCount(3, $candidates); + $this->assertEquals(['alpha', 'beta'], $candidates[0]); + $this->assertEquals(['alpha', 'theta'], $candidates[1]); + $this->assertEquals(['beta', 'theta'], $candidates[2]); } public function testConfidence(): void @@ -120,8 +174,8 @@ public function testConfidence(): void $apriori = new Apriori(); $apriori->train($this->sampleGreek, []); - $this->assertEquals(0.5, $this->invoke($apriori, 'confidence', [['alpha', 'beta', 'theta'], ['alpha', 'beta']])); - $this->assertEquals(1, $this->invoke($apriori, 'confidence', [['alpha', 'beta'], ['alpha']])); + $this->assertEquals(0.5, self::invoke($apriori, 'confidence', [['alpha', 'beta', 'theta'], ['alpha', 'beta']])); + $this->assertEquals(1, self::invoke($apriori, 'confidence', [['alpha', 'beta'], ['alpha']])); } public function testSupport(): void @@ -129,8 +183,8 @@ public function testSupport(): void $apriori = new Apriori(); $apriori->train($this->sampleGreek, []); - $this->assertEquals(1.0, $this->invoke($apriori, 'support', [['alpha', 'beta']])); - $this->assertEquals(0.5, $this->invoke($apriori, 'support', [['epsilon']])); + $this->assertEquals(1.0, self::invoke($apriori, 'support', [['alpha', 'beta']])); + $this->assertEquals(0.5, self::invoke($apriori, 'support', [['epsilon']])); } public function testFrequency(): void @@ -138,52 +192,35 @@ public function testFrequency(): void $apriori = new Apriori(); $apriori->train($this->sampleGreek, []); - $this->assertEquals(4, $this->invoke($apriori, 'frequency', [['alpha', 'beta']])); - $this->assertEquals(2, $this->invoke($apriori, 'frequency', [['epsilon']])); + $this->assertEquals(4, self::invoke($apriori, 'frequency', [['alpha', 'beta']])); + $this->assertEquals(2, self::invoke($apriori, 'frequency', [['epsilon']])); } public function testContains(): void { $apriori = new Apriori(); - $this->assertTrue($this->invoke($apriori, 'contains', [[['a'], ['b']], ['a']])); - $this->assertTrue($this->invoke($apriori, 'contains', [[[1, 2]], [1, 2]])); - $this->assertFalse($this->invoke($apriori, 'contains', [[['a'], ['b']], ['c']])); + $this->assertTrue(self::invoke($apriori, 'contains', [[['a'], ['b']], ['a']])); + $this->assertTrue(self::invoke($apriori, 'contains', [[[1, 2]], [1, 2]])); + $this->assertFalse(self::invoke($apriori, 'contains', [[['a'], ['b']], ['c']])); } public function testSubset(): void { $apriori = new Apriori(); - $this->assertTrue($this->invoke($apriori, 'subset', [['a', 'b'], ['a']])); - $this->assertTrue($this->invoke($apriori, 'subset', [['a'], ['a']])); - $this->assertFalse($this->invoke($apriori, 'subset', [['a'], ['a', 'b']])); + $this->assertTrue(self::invoke($apriori, 'subset', [['a', 'b'], ['a']])); + $this->assertTrue(self::invoke($apriori, 'subset', [['a'], ['a']])); + $this->assertFalse(self::invoke($apriori, 'subset', [['a'], ['a', 'b']])); } public function testEquals(): void { $apriori = new Apriori(); - $this->assertTrue($this->invoke($apriori, 'equals', [['a'], ['a']])); - $this->assertFalse($this->invoke($apriori, 'equals', [['a'], []])); - $this->assertFalse($this->invoke($apriori, 'equals', [['a'], ['b', 'a']])); - } - - /** - * Invokes objects method. Private/protected will be set accessible. - * - * @param string $method Method name to be called - * @param array $params Array of params to be passed - * - * @return mixed - */ - public function invoke(&$object, string $method, array $params = []) - { - $reflection = new ReflectionClass(get_class($object)); - $method = $reflection->getMethod($method); - $method->setAccessible(true); - - return $method->invokeArgs($object, $params); + $this->assertTrue(self::invoke($apriori, 'equals', [['a'], ['a']])); + $this->assertFalse(self::invoke($apriori, 'equals', [['a'], []])); + $this->assertFalse(self::invoke($apriori, 'equals', [['a'], ['b', 'a']])); } public function testSaveAndRestore(): void @@ -204,28 +241,20 @@ public function testSaveAndRestore(): void $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); } - public function testAprioriEmpty(): void - { - $sample = []; - - $apriori = new Apriori(0, 0); - $apriori->train($sample, []); - - $L = $apriori->apriori(); - - $this->assertEmpty($L); - } - - public function testAprioriSingleItem(): void + /** + * Invokes objects method. Private/protected will be set accessible. + * + * @param string $method Method name to be called + * @param array $params Array of params to be passed + * + * @return mixed + */ + private static function invoke(&$object, string $method, array $params = []) { - $sample = [['a']]; - - $apriori = new Apriori(0, 0); - $apriori->train($sample, []); - - $L = $apriori->apriori(); + $reflection = new ReflectionClass(get_class($object)); + $method = $reflection->getMethod($method); + $method->setAccessible(true); - $this->assertEquals([1], array_keys($L)); - $this->assertEquals([['a']], $L[1]); + return $method->invokeArgs($object, $params); } } From 52c9ba8291cf4ea0a24e217282b28f7c7656cbf1 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 11 Feb 2018 18:17:50 +0100 Subject: [PATCH 243/328] Fix: phpunit include tests path (#230) * Fix phpunit include path * Add tests for Covariance --- phpunit.xml | 2 +- src/Math/Statistic/Covariance.php | 3 +- tests/Math/Statistic/CovarianceTest.php | 43 +++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/phpunit.xml b/phpunit.xml index 4a74eb65..e0a91daa 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -9,7 +9,7 @@ enforceTimeLimit="true" > - tests/* + tests diff --git a/src/Math/Statistic/Covariance.php b/src/Math/Statistic/Covariance.php index a669a7f3..ac24a53b 100644 --- a/src/Math/Statistic/Covariance.php +++ b/src/Math/Statistic/Covariance.php @@ -4,7 +4,6 @@ namespace Phpml\Math\Statistic; -use Exception; use Phpml\Exception\InvalidArgumentException; class Covariance @@ -64,7 +63,7 @@ public static function fromDataset(array $data, int $i, int $k, bool $sample = t } if ($i < 0 || $k < 0 || $i >= $n || $k >= $n) { - throw new Exception('Given indices i and k do not match with the dimensionality of data'); + throw new InvalidArgumentException('Given indices i and k do not match with the dimensionality of data'); } if ($meanX === null || $meanY === null) { diff --git a/tests/Math/Statistic/CovarianceTest.php b/tests/Math/Statistic/CovarianceTest.php index 2b648545..43b775a9 100644 --- a/tests/Math/Statistic/CovarianceTest.php +++ b/tests/Math/Statistic/CovarianceTest.php @@ -4,6 +4,7 @@ namespace Phpml\Tests\Math\Statistic; +use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Statistic\Covariance; use Phpml\Math\Statistic\Mean; use PHPUnit\Framework\TestCase; @@ -59,4 +60,46 @@ public function testSimpleCovariance(): void $covariance = Covariance::covarianceMatrix($matrix, [$meanX, $meanY]); $this->assertEquals($knownCovariance, $covariance, '', $epsilon); } + + public function testThrowExceptionOnEmptyX(): void + { + $this->expectException(InvalidArgumentException::class); + Covariance::fromXYArrays([], [1, 2, 3]); + } + + public function testThrowExceptionOnEmptyY(): void + { + $this->expectException(InvalidArgumentException::class); + Covariance::fromXYArrays([1, 2, 3], []); + } + + public function testThrowExceptionOnToSmallArrayIfSample(): void + { + $this->expectException(InvalidArgumentException::class); + Covariance::fromXYArrays([1], [2], true); + } + + public function testThrowExceptionIfEmptyDataset(): void + { + $this->expectException(InvalidArgumentException::class); + Covariance::fromDataset([], 0, 1); + } + + public function testThrowExceptionOnToSmallDatasetIfSample(): void + { + $this->expectException(InvalidArgumentException::class); + Covariance::fromDataset([1], 0, 1); + } + + public function testThrowExceptionWhenKIndexIsOutOfBound(): void + { + $this->expectException(InvalidArgumentException::class); + Covariance::fromDataset([1, 2, 3], 2, 5); + } + + public function testThrowExceptionWhenIIndexIsOutOfBound(): void + { + $this->expectException(InvalidArgumentException::class); + Covariance::fromDataset([1, 2, 3], 5, 2); + } } From fbf84ca95fa26650be4705ca85a36502094ad7bc Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 11 Feb 2018 22:10:12 +0100 Subject: [PATCH 244/328] Implement SelectKBest algo for feature selection --- src/FeatureExtraction/TfIdfTransformer.php | 2 +- .../TokenCountVectorizer.php | 2 +- src/FeatureSelection/ScoringFunction.php | 10 ++ .../ScoringFunction/ANOVAFValue.php | 21 +++ src/FeatureSelection/SelectKBest.php | 78 ++++++++++ src/FeatureSelection/VarianceThreshold.php | 4 +- src/Math/Statistic/ANOVA.php | 137 ++++++++++++++++++ src/Preprocessing/Imputer.php | 2 +- src/Preprocessing/Normalizer.php | 2 +- src/Transformer.php | 9 +- tests/Classification/MLPClassifierTest.php | 10 +- .../ScoringFunction/ANOVAFValueTest.php | 25 ++++ tests/FeatureSelection/SelectKBestTest.php | 61 ++++++++ tests/Math/Statistic/ANOVATest.php | 44 ++++++ 14 files changed, 389 insertions(+), 18 deletions(-) create mode 100644 src/FeatureSelection/ScoringFunction.php create mode 100644 src/FeatureSelection/ScoringFunction/ANOVAFValue.php create mode 100644 src/FeatureSelection/SelectKBest.php create mode 100644 src/Math/Statistic/ANOVA.php create mode 100644 tests/FeatureSelection/ScoringFunction/ANOVAFValueTest.php create mode 100644 tests/FeatureSelection/SelectKBestTest.php create mode 100644 tests/Math/Statistic/ANOVATest.php diff --git a/src/FeatureExtraction/TfIdfTransformer.php b/src/FeatureExtraction/TfIdfTransformer.php index 4b678a44..30f0203d 100644 --- a/src/FeatureExtraction/TfIdfTransformer.php +++ b/src/FeatureExtraction/TfIdfTransformer.php @@ -20,7 +20,7 @@ public function __construct(?array $samples = null) } } - public function fit(array $samples): void + public function fit(array $samples, ?array $targets = null): void { $this->countTokensFrequency($samples); diff --git a/src/FeatureExtraction/TokenCountVectorizer.php b/src/FeatureExtraction/TokenCountVectorizer.php index e0d4e107..8c757c02 100644 --- a/src/FeatureExtraction/TokenCountVectorizer.php +++ b/src/FeatureExtraction/TokenCountVectorizer.php @@ -41,7 +41,7 @@ public function __construct(Tokenizer $tokenizer, ?StopWords $stopWords = null, $this->minDF = $minDF; } - public function fit(array $samples): void + public function fit(array $samples, ?array $targets = null): void { $this->buildVocabulary($samples); } diff --git a/src/FeatureSelection/ScoringFunction.php b/src/FeatureSelection/ScoringFunction.php new file mode 100644 index 00000000..4c925f65 --- /dev/null +++ b/src/FeatureSelection/ScoringFunction.php @@ -0,0 +1,10 @@ + $sample) { + $grouped[$targets[$index]][] = $sample; + } + + return ANOVA::oneWayF(array_values($grouped)); + } +} diff --git a/src/FeatureSelection/SelectKBest.php b/src/FeatureSelection/SelectKBest.php new file mode 100644 index 00000000..8f4b2733 --- /dev/null +++ b/src/FeatureSelection/SelectKBest.php @@ -0,0 +1,78 @@ +scoringFunction = $scoringFunction; + $this->k = $k; + } + + public function fit(array $samples, ?array $targets = null): void + { + if ($targets === null || empty($targets)) { + throw InvalidArgumentException::arrayCantBeEmpty(); + } + + $this->scores = $sorted = $this->scoringFunction->score($samples, $targets); + if ($this->k >= count($sorted)) { + return; + } + + arsort($sorted); + $this->keepColumns = array_slice($sorted, 0, $this->k, true); + } + + public function transform(array &$samples): void + { + if ($this->keepColumns === null) { + return; + } + + foreach ($samples as &$sample) { + $sample = array_values(array_intersect_key($sample, $this->keepColumns)); + } + } + + public function scores(): array + { + if ($this->scores === null) { + throw new InvalidOperationException('SelectKBest require to fit first to get scores'); + } + + return $this->scores; + } +} diff --git a/src/FeatureSelection/VarianceThreshold.php b/src/FeatureSelection/VarianceThreshold.php index 6a3d6395..5ca23323 100644 --- a/src/FeatureSelection/VarianceThreshold.php +++ b/src/FeatureSelection/VarianceThreshold.php @@ -33,11 +33,9 @@ public function __construct(float $threshold = 0.0) } $this->threshold = $threshold; - $this->variances = []; - $this->keepColumns = []; } - public function fit(array $samples): void + public function fit(array $samples, ?array $targets = null): void { $this->variances = array_map(function (array $column) { return Variance::population($column); diff --git a/src/Math/Statistic/ANOVA.php b/src/Math/Statistic/ANOVA.php new file mode 100644 index 00000000..b0d0d37f --- /dev/null +++ b/src/Math/Statistic/ANOVA.php @@ -0,0 +1,137 @@ + $msbValue) { + $f[$index] = $msbValue / $msw[$index]; + } + + return $f; + } + + private static function sumOfSquaresPerFeature(array $samples): array + { + $sum = array_fill(0, count($samples[0][0]), 0); + foreach ($samples as $class) { + foreach ($class as $sample) { + foreach ($sample as $index => $feature) { + $sum[$index] += $feature ** 2; + } + } + } + + return $sum; + } + + private static function sumOfFeaturesPerClass(array $samples): array + { + return array_map(function (array $class) { + $sum = array_fill(0, count($class[0]), 0); + foreach ($class as $sample) { + foreach ($sample as $index => $feature) { + $sum[$index] += $feature; + } + } + + return $sum; + }, $samples); + } + + private static function sumOfSquares(array $sums): array + { + $squares = array_fill(0, count($sums[0]), 0); + foreach ($sums as $row) { + foreach ($row as $index => $sum) { + $squares[$index] += $sum; + } + } + + return array_map(function ($sum) { + return $sum ** 2; + }, $squares); + } + + private static function squaresSum(array $sums): array + { + foreach ($sums as &$row) { + foreach ($row as &$sum) { + $sum = $sum ** 2; + } + } + + return $sums; + } + + private static function calculateSsbn(array $samples, array $sumSamplesSquare, array $samplesPerClass, array $squareSumSamples, int $allSamples): array + { + $ssbn = array_fill(0, count($samples[0][0]), 0); + foreach ($sumSamplesSquare as $classIndex => $class) { + foreach ($class as $index => $feature) { + $ssbn[$index] += $feature / $samplesPerClass[$classIndex]; + } + } + + foreach ($squareSumSamples as $index => $sum) { + $ssbn[$index] -= $sum / $allSamples; + } + + return $ssbn; + } + + private static function calculateSswn(array $ssbn, array $ssAllSamples, array $squareSumSamples, int $allSamples): array + { + $sswn = []; + foreach ($ssAllSamples as $index => $ss) { + $sswn[$index] = ($ss - $squareSumSamples[$index] / $allSamples) - $ssbn[$index]; + } + + return $sswn; + } +} diff --git a/src/Preprocessing/Imputer.php b/src/Preprocessing/Imputer.php index 593756c2..fdce6664 100644 --- a/src/Preprocessing/Imputer.php +++ b/src/Preprocessing/Imputer.php @@ -43,7 +43,7 @@ public function __construct($missingValue, Strategy $strategy, int $axis = self: $this->samples = $samples; } - public function fit(array $samples): void + public function fit(array $samples, ?array $targets = null): void { $this->samples = $samples; } diff --git a/src/Preprocessing/Normalizer.php b/src/Preprocessing/Normalizer.php index b2721de9..39b4fbc4 100644 --- a/src/Preprocessing/Normalizer.php +++ b/src/Preprocessing/Normalizer.php @@ -48,7 +48,7 @@ public function __construct(int $norm = self::NORM_L2) $this->norm = $norm; } - public function fit(array $samples): void + public function fit(array $samples, ?array $targets = null): void { if ($this->fitted) { return; diff --git a/src/Transformer.php b/src/Transformer.php index c36e5cae..7350e2ce 100644 --- a/src/Transformer.php +++ b/src/Transformer.php @@ -7,12 +7,9 @@ interface Transformer { /** - * @param array $samples + * most transformers don't require targets to train so null allow to use fit method without setting targets */ - public function fit(array $samples); + public function fit(array $samples, ?array $targets = null): void; - /** - * @param array $samples - */ - public function transform(array &$samples); + public function transform(array &$samples): void; } diff --git a/tests/Classification/MLPClassifierTest.php b/tests/Classification/MLPClassifierTest.php index c46e297c..c4c45c49 100644 --- a/tests/Classification/MLPClassifierTest.php +++ b/tests/Classification/MLPClassifierTest.php @@ -59,7 +59,7 @@ public function testSynapsesGeneration(): void public function testBackpropagationLearning(): void { // Single layer 2 classes. - $network = new MLPClassifier(2, [2], ['a', 'b']); + $network = new MLPClassifier(2, [2], ['a', 'b'], 1000); $network->train( [[1, 0], [0, 1], [1, 1], [0, 0]], ['a', 'b', 'a', 'b'] @@ -118,7 +118,7 @@ public function testBackpropagationPartialTraining(): void public function testBackpropagationLearningMultilayer(): void { // Multi-layer 2 classes. - $network = new MLPClassifier(5, [3, 2], ['a', 'b', 'c']); + $network = new MLPClassifier(5, [3, 2], ['a', 'b', 'c'], 2000); $network->train( [[1, 0, 0, 0, 0], [0, 1, 1, 0, 0], [1, 1, 1, 1, 1], [0, 0, 0, 0, 0]], ['a', 'b', 'a', 'c'] @@ -133,7 +133,7 @@ public function testBackpropagationLearningMultilayer(): void public function testBackpropagationLearningMulticlass(): void { // Multi-layer more than 2 classes. - $network = new MLPClassifier(5, [3, 2], ['a', 'b', 4]); + $network = new MLPClassifier(5, [3, 2], ['a', 'b', 4], 1000); $network->train( [[1, 0, 0, 0, 0], [0, 1, 0, 0, 0], [0, 0, 1, 1, 0], [1, 1, 1, 1, 1], [0, 0, 0, 0, 0]], ['a', 'b', 'a', 'a', 4] @@ -151,7 +151,7 @@ public function testBackpropagationLearningMulticlass(): void */ public function testBackpropagationActivationFunctions(ActivationFunction $activationFunction): void { - $network = new MLPClassifier(5, [3], ['a', 'b'], 10000, $activationFunction); + $network = new MLPClassifier(5, [3], ['a', 'b'], 1000, $activationFunction); $network->train( [[1, 0, 0, 0, 0], [0, 1, 0, 0, 0], [0, 0, 1, 1, 0], [1, 1, 1, 1, 1]], ['a', 'b', 'a', 'a'] @@ -178,7 +178,7 @@ public function testSaveAndRestore(): void // Instantinate new Percetron trained for OR problem $samples = [[0, 0], [1, 0], [0, 1], [1, 1]]; $targets = [0, 1, 1, 1]; - $classifier = new MLPClassifier(2, [2], [0, 1]); + $classifier = new MLPClassifier(2, [2], [0, 1], 1000); $classifier->train($samples, $targets); $testSamples = [[0, 0], [1, 0], [0, 1], [1, 1]]; $predicted = $classifier->predict($testSamples); diff --git a/tests/FeatureSelection/ScoringFunction/ANOVAFValueTest.php b/tests/FeatureSelection/ScoringFunction/ANOVAFValueTest.php new file mode 100644 index 00000000..7a601db2 --- /dev/null +++ b/tests/FeatureSelection/ScoringFunction/ANOVAFValueTest.php @@ -0,0 +1,25 @@ +score($dataset->getSamples(), $dataset->getTargets()), + '', + 0.0001 + ); + } +} diff --git a/tests/FeatureSelection/SelectKBestTest.php b/tests/FeatureSelection/SelectKBestTest.php new file mode 100644 index 00000000..a0355608 --- /dev/null +++ b/tests/FeatureSelection/SelectKBestTest.php @@ -0,0 +1,61 @@ +fit($samples, $targets); + $selector->transform($samples); + + self::assertEquals([[2, 1], [3, 4], [2, 1], [3, 3], [3, 4], [3, 5]], $samples); + } + + public function testSelectKBestWithKBiggerThanFeatures(): void + { + $samples = [[1, 2, 1], [1, 3, 4], [5, 2, 1], [1, 3, 3], [1, 3, 4], [0, 3, 5]]; + $targets = ['a', 'a', 'a', 'b', 'b', 'b']; + $selector = new SelectKBest(null, 4); + $selector->fit($samples, $targets); + $selector->transform($samples); + + self::assertEquals([[1, 2, 1], [1, 3, 4], [5, 2, 1], [1, 3, 3], [1, 3, 4], [0, 3, 5]], $samples); + } + + public function testSelectKBestWithIrisDataset(): void + { + $dataset = new IrisDataset(); + $selector = new SelectKBest(new ANOVAFValue(), 2); + $selector->fit($samples = $dataset->getSamples(), $dataset->getTargets()); + $selector->transform($samples); + + self::assertEquals(2, count($samples[0])); + } + + public function testThrowExceptionOnEmptyTargets(): void + { + $this->expectException(InvalidArgumentException::class); + $selector = new SelectKBest(new ANOVAFValue(), 2); + $selector->fit([[1, 2, 3], [4, 5, 6]], []); + } + + public function testThrowExceptionWhenNotTrained(): void + { + $this->expectException(InvalidOperationException::class); + $selector = new SelectKBest(new ANOVAFValue(), 2); + $selector->scores(); + } +} diff --git a/tests/Math/Statistic/ANOVATest.php b/tests/Math/Statistic/ANOVATest.php new file mode 100644 index 00000000..81717f86 --- /dev/null +++ b/tests/Math/Statistic/ANOVATest.php @@ -0,0 +1,44 @@ +expectException(InvalidArgumentException::class); + $samples = [ + [[1, 2, 1], [1, 3, 4], [5, 2, 1]], + ]; + + ANOVA::oneWayF($samples); + } +} From 9e5b3a0c69214193f4e0b583c81e1df4a85e3399 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Mon, 12 Feb 2018 00:26:34 +0100 Subject: [PATCH 245/328] Implement first regression scoring function UnivariateLinearRegression --- .../UnivariateLinearRegression.php | 81 +++++++++++++++++++ src/Math/Matrix.php | 27 ++++++- .../UnivariateLinearRegressionTest.php | 29 +++++++ tests/FeatureSelection/SelectKBestTest.php | 16 ++++ tests/Math/MatrixTest.php | 51 ++++++++++++ 5 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 src/FeatureSelection/ScoringFunction/UnivariateLinearRegression.php create mode 100644 tests/FeatureSelection/ScoringFunction/UnivariateLinearRegressionTest.php diff --git a/src/FeatureSelection/ScoringFunction/UnivariateLinearRegression.php b/src/FeatureSelection/ScoringFunction/UnivariateLinearRegression.php new file mode 100644 index 00000000..9a819c52 --- /dev/null +++ b/src/FeatureSelection/ScoringFunction/UnivariateLinearRegression.php @@ -0,0 +1,81 @@ +center = $center; + } + + public function score(array $samples, array $targets): array + { + if ($this->center) { + $this->centerTargets($targets); + $this->centerSamples($samples); + } + + $correlations = []; + foreach ($samples[0] as $index => $feature) { + $featureColumn = array_column($samples, $index); + $correlations[$index] = + (Matrix::dot($targets, $featureColumn)[0] / (new Matrix($featureColumn, false))->transpose()->frobeniusNorm()) + / (new Matrix($targets, false))->frobeniusNorm(); + } + + $degreesOfFreedom = count($targets) - ($this->center ? 2 : 1); + + return array_map(function (float $correlation) use ($degreesOfFreedom): float { + return $correlation ** 2 / (1 - $correlation ** 2) * $degreesOfFreedom; + }, $correlations); + } + + private function centerTargets(&$targets): void + { + $mean = Mean::arithmetic($targets); + foreach ($targets as &$target) { + $target -= $mean; + } + } + + private function centerSamples(&$samples): void + { + $means = []; + foreach ($samples[0] as $index => $feature) { + $means[$index] = Mean::arithmetic(array_column($samples, $index)); + } + + foreach ($samples as &$sample) { + foreach ($sample as $index => &$feature) { + $feature -= $means[$index]; + } + } + } +} diff --git a/src/Math/Matrix.php b/src/Math/Matrix.php index 7c1ff3e2..e7bc92e3 100644 --- a/src/Math/Matrix.php +++ b/src/Math/Matrix.php @@ -236,6 +236,29 @@ public function isSingular(): bool return $this->getDeterminant() == 0; } + /** + * Frobenius norm (Hilbert–Schmidt norm, Euclidean norm) (‖A‖F) + * Square root of the sum of the square of all elements. + * + * https://en.wikipedia.org/wiki/Matrix_norm#Frobenius_norm + * + * _____________ + * /ᵐ ⁿ + * ‖A‖F = √ Σ Σ |aᵢⱼ|² + * ᵢ₌₁ ᵢ₌₁ + */ + public function frobeniusNorm(): float + { + $squareSum = 0; + for ($i = 0; $i < $this->rows; ++$i) { + for ($j = 0; $j < $this->columns; ++$j) { + $squareSum += ($this->matrix[$i][$j]) ** 2; + } + } + + return sqrt($squareSum); + } + /** * Returns the transpose of given array */ @@ -259,7 +282,7 @@ public static function dot(array $array1, array $array2): array /** * Element-wise addition or substraction depending on the given sign parameter */ - protected function _add(self $other, int $sign = 1): self + private function _add(self $other, int $sign = 1): self { $a1 = $this->toArray(); $a2 = $other->toArray(); @@ -277,7 +300,7 @@ protected function _add(self $other, int $sign = 1): self /** * Returns diagonal identity matrix of the same size of this matrix */ - protected function getIdentity(): self + private function getIdentity(): self { $array = array_fill(0, $this->rows, array_fill(0, $this->columns, 0)); for ($i = 0; $i < $this->rows; ++$i) { diff --git a/tests/FeatureSelection/ScoringFunction/UnivariateLinearRegressionTest.php b/tests/FeatureSelection/ScoringFunction/UnivariateLinearRegressionTest.php new file mode 100644 index 00000000..0047e5fe --- /dev/null +++ b/tests/FeatureSelection/ScoringFunction/UnivariateLinearRegressionTest.php @@ -0,0 +1,29 @@ +score($samples, $targets), '', 0.0001); + } + + public function testRegressionScoreWithoutCenter(): void + { + $samples = [[73676, 1996], [77006, 1998], [10565, 2000], [146088, 1995], [15000, 2001], [65940, 2000], [9300, 2000], [93739, 1996], [153260, 1994], [17764, 2002], [57000, 1998], [15000, 2000]]; + $targets = [2000, 2750, 15500, 960, 4400, 8800, 7100, 2550, 1025, 5900, 4600, 4400]; + + $function = new UnivariateLinearRegression(false); + self::assertEquals([1.74450, 18.08347], $function->score($samples, $targets), '', 0.0001); + } +} diff --git a/tests/FeatureSelection/SelectKBestTest.php b/tests/FeatureSelection/SelectKBestTest.php index a0355608..df17c08b 100644 --- a/tests/FeatureSelection/SelectKBestTest.php +++ b/tests/FeatureSelection/SelectKBestTest.php @@ -8,6 +8,7 @@ use Phpml\Exception\InvalidArgumentException; use Phpml\Exception\InvalidOperationException; use Phpml\FeatureSelection\ScoringFunction\ANOVAFValue; +use Phpml\FeatureSelection\ScoringFunction\UnivariateLinearRegression; use Phpml\FeatureSelection\SelectKBest; use PHPUnit\Framework\TestCase; @@ -45,6 +46,21 @@ public function testSelectKBestWithIrisDataset(): void self::assertEquals(2, count($samples[0])); } + public function testSelectKBestWithRegressionScoring(): void + { + $samples = [[73676, 1996, 2], [77006, 1998, 5], [10565, 2000, 4], [146088, 1995, 2], [15000, 2001, 2], [65940, 2000, 2], [9300, 2000, 2], [93739, 1996, 2], [153260, 1994, 2], [17764, 2002, 2], [57000, 1998, 2], [15000, 2000, 2]]; + $targets = [2000, 2750, 15500, 960, 4400, 8800, 7100, 2550, 1025, 5900, 4600, 4400]; + + $selector = new SelectKBest(new UnivariateLinearRegression(), 2); + $selector->fit($samples, $targets); + $selector->transform($samples); + + self::assertEquals( + [[73676, 1996], [77006, 1998], [10565, 2000], [146088, 1995], [15000, 2001], [65940, 2000], [9300, 2000], [93739, 1996], [153260, 1994], [17764, 2002], [57000, 1998], [15000, 2000]], + $samples + ); + } + public function testThrowExceptionOnEmptyTargets(): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Math/MatrixTest.php b/tests/Math/MatrixTest.php index da535bfe..50cabacb 100644 --- a/tests/Math/MatrixTest.php +++ b/tests/Math/MatrixTest.php @@ -251,4 +251,55 @@ public function testDot(): void $dot = [6, 12]; $this->assertEquals($dot, Matrix::dot($matrix2, $matrix1)); } + + /** + * @dataProvider dataProviderForFrobeniusNorm + */ + public function testFrobeniusNorm(array $matrix, float $norm): void + { + $matrix = new Matrix($matrix); + + $this->assertEquals($norm, $matrix->frobeniusNorm(), '', 0.0001); + } + + public function dataProviderForFrobeniusNorm() + { + return [ + [ + [ + [1, -7], + [2, 3], + ], 7.93725, + ], + [ + [ + [1, 2, 3], + [2, 3, 4], + [3, 4, 5], + ], 9.643651, + ], + [ + [ + [1, 5, 3, 9], + [2, 3, 4, 12], + [4, 2, 5, 11], + ], 21.330729, + ], + [ + [ + [1, 5, 3], + [2, 3, 4], + [4, 2, 5], + [6, 6, 3], + ], 13.784049, + ], + [ + [ + [5, -4, 2], + [-1, 2, 3], + [-2, 1, 0], + ], 8, + ], + ]; + } } From 998879b6fca2f8a2e3e761724b606ee3594a78ac Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 14 Feb 2018 18:52:11 +0100 Subject: [PATCH 246/328] Switch SelectKBest constructor parameters --- src/FeatureSelection/SelectKBest.php | 2 +- tests/FeatureSelection/SelectKBestTest.php | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/FeatureSelection/SelectKBest.php b/src/FeatureSelection/SelectKBest.php index 8f4b2733..bd84e736 100644 --- a/src/FeatureSelection/SelectKBest.php +++ b/src/FeatureSelection/SelectKBest.php @@ -31,7 +31,7 @@ final class SelectKBest implements Transformer */ private $keepColumns = null; - public function __construct(?ScoringFunction $scoringFunction = null, int $k = 10) + public function __construct(int $k = 10, ?ScoringFunction $scoringFunction = null) { if ($scoringFunction === null) { $scoringFunction = new ANOVAFValue(); diff --git a/tests/FeatureSelection/SelectKBestTest.php b/tests/FeatureSelection/SelectKBestTest.php index df17c08b..ebf119b5 100644 --- a/tests/FeatureSelection/SelectKBestTest.php +++ b/tests/FeatureSelection/SelectKBestTest.php @@ -18,7 +18,7 @@ public function testSelectKBestWithDefaultScoringFunction(): void { $samples = [[1, 2, 1], [1, 3, 4], [5, 2, 1], [1, 3, 3], [1, 3, 4], [0, 3, 5]]; $targets = ['a', 'a', 'a', 'b', 'b', 'b']; - $selector = new SelectKBest(null, 2); + $selector = new SelectKBest(2); $selector->fit($samples, $targets); $selector->transform($samples); @@ -29,7 +29,7 @@ public function testSelectKBestWithKBiggerThanFeatures(): void { $samples = [[1, 2, 1], [1, 3, 4], [5, 2, 1], [1, 3, 3], [1, 3, 4], [0, 3, 5]]; $targets = ['a', 'a', 'a', 'b', 'b', 'b']; - $selector = new SelectKBest(null, 4); + $selector = new SelectKBest(4); $selector->fit($samples, $targets); $selector->transform($samples); @@ -39,7 +39,7 @@ public function testSelectKBestWithKBiggerThanFeatures(): void public function testSelectKBestWithIrisDataset(): void { $dataset = new IrisDataset(); - $selector = new SelectKBest(new ANOVAFValue(), 2); + $selector = new SelectKBest(2, new ANOVAFValue()); $selector->fit($samples = $dataset->getSamples(), $dataset->getTargets()); $selector->transform($samples); @@ -51,7 +51,7 @@ public function testSelectKBestWithRegressionScoring(): void $samples = [[73676, 1996, 2], [77006, 1998, 5], [10565, 2000, 4], [146088, 1995, 2], [15000, 2001, 2], [65940, 2000, 2], [9300, 2000, 2], [93739, 1996, 2], [153260, 1994, 2], [17764, 2002, 2], [57000, 1998, 2], [15000, 2000, 2]]; $targets = [2000, 2750, 15500, 960, 4400, 8800, 7100, 2550, 1025, 5900, 4600, 4400]; - $selector = new SelectKBest(new UnivariateLinearRegression(), 2); + $selector = new SelectKBest(2, new UnivariateLinearRegression()); $selector->fit($samples, $targets); $selector->transform($samples); @@ -64,14 +64,14 @@ public function testSelectKBestWithRegressionScoring(): void public function testThrowExceptionOnEmptyTargets(): void { $this->expectException(InvalidArgumentException::class); - $selector = new SelectKBest(new ANOVAFValue(), 2); + $selector = new SelectKBest(2, new ANOVAFValue()); $selector->fit([[1, 2, 3], [4, 5, 6]], []); } public function testThrowExceptionWhenNotTrained(): void { $this->expectException(InvalidOperationException::class); - $selector = new SelectKBest(new ANOVAFValue(), 2); + $selector = new SelectKBest(2, new ANOVAFValue()); $selector->scores(); } } From b4b190de7fd624892bade2bbd9c9e39f57da4fad Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 14 Feb 2018 19:05:48 +0100 Subject: [PATCH 247/328] Fix pipeline transformers --- src/Pipeline.php | 2 +- tests/PipelineTest.php | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Pipeline.php b/src/Pipeline.php index 480a9800..d57da87f 100644 --- a/src/Pipeline.php +++ b/src/Pipeline.php @@ -54,7 +54,7 @@ public function getEstimator(): Estimator public function train(array $samples, array $targets): void { foreach ($this->transformers as $transformer) { - $transformer->fit($samples); + $transformer->fit($samples, $targets); $transformer->transform($samples); } diff --git a/tests/PipelineTest.php b/tests/PipelineTest.php index 86ff2a9f..6f1562c7 100644 --- a/tests/PipelineTest.php +++ b/tests/PipelineTest.php @@ -7,6 +7,8 @@ use Phpml\Classification\SVC; use Phpml\FeatureExtraction\TfIdfTransformer; use Phpml\FeatureExtraction\TokenCountVectorizer; +use Phpml\FeatureSelection\ScoringFunction\ANOVAFValue; +use Phpml\FeatureSelection\SelectKBest; use Phpml\ModelManager; use Phpml\Pipeline; use Phpml\Preprocessing\Imputer; @@ -106,6 +108,18 @@ public function testPipelineTransformers(): void $this->assertEquals($expected, $predicted); } + public function testPipelineTransformersWithTargets() : void + { + $samples = [[1, 2, 1], [1, 3, 4], [5, 2, 1], [1, 3, 3], [1, 3, 4], [0, 3, 5]]; + $targets = ['a', 'a', 'a', 'b', 'b', 'b']; + + $pipeline = new Pipeline([$selector = new SelectKBest(2)], new SVC()); + $pipeline->train($samples, $targets); + + self::assertEquals([1.47058823, 4.0, 3.0], $selector->scores(), '', 0.00000001); + self::assertEquals(['b'], $pipeline->predict([[1, 3, 5]])); + } + public function testSaveAndRestore(): void { $pipeline = new Pipeline([ From 83b1d7c9ac6bf342f2190894c15caa48bd5dfa08 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 14 Feb 2018 19:24:48 +0100 Subject: [PATCH 248/328] Update coveralls phar --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index fd841637..d4df191a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,6 +40,6 @@ script: after_success: - | if [[ $PHPUNIT_FLAGS != "" ]]; then - wget https://github.com/satooshi/php-coveralls/releases/download/v1.0.1/coveralls.phar; + wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.0.0/php-coveralls.phar php coveralls.phar --verbose; fi From 451f84c2e62f5b1a5cee8d8290e5039a009ec7ea Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 14 Feb 2018 19:51:07 +0100 Subject: [PATCH 249/328] Add SelectKBest docs --- README.md | 3 +- docs/index.md | 3 +- .../feature-selection/selectkbest.md | 96 +++++++++++++++++++ mkdocs.yml | 1 + .../UnivariateLinearRegression.php | 2 +- tests/PipelineTest.php | 3 +- 6 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 docs/machine-learning/feature-selection/selectkbest.md diff --git a/README.md b/README.md index 595714dd..c5d788eb 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Documentation Status](https://readthedocs.org/projects/php-ml/badge/?version=master)](http://php-ml.readthedocs.org/) [![Total Downloads](https://poser.pugx.org/php-ai/php-ml/downloads.svg)](https://packagist.org/packages/php-ai/php-ml) [![License](https://poser.pugx.org/php-ai/php-ml/license.svg)](https://packagist.org/packages/php-ai/php-ml) -[![Coverage Status](https://coveralls.io/repos/github/php-ai/php-ml/badge.svg?branch=coveralls)](https://coveralls.io/github/php-ai/php-ml?branch=coveralls) +[![Coverage Status](https://coveralls.io/repos/github/php-ai/php-ml/badge.svg?branch=master)](https://coveralls.io/github/php-ai/php-ml?branch=master) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/php-ai/php-ml/?branch=master) @@ -89,6 +89,7 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * [Stratified Random Split](http://php-ml.readthedocs.io/en/latest/machine-learning/cross-validation/stratified-random-split/) * Feature Selection * [Variance Threshold](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-selection/variance-threshold/) + * [SelectKBest](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-selection/selectkbest/) * Preprocessing * [Normalization](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/normalization/) * [Imputation missing values](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/imputation-missing-values/) diff --git a/docs/index.md b/docs/index.md index eb505637..14cfba5c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,7 +6,7 @@ [![Documentation Status](https://readthedocs.org/projects/php-ml/badge/?version=master)](http://php-ml.readthedocs.org/) [![Total Downloads](https://poser.pugx.org/php-ai/php-ml/downloads.svg)](https://packagist.org/packages/php-ai/php-ml) [![License](https://poser.pugx.org/php-ai/php-ml/license.svg)](https://packagist.org/packages/php-ai/php-ml) -[![Coverage Status](https://coveralls.io/repos/github/php-ai/php-ml/badge.svg?branch=coveralls)](https://coveralls.io/github/php-ai/php-ml?branch=coveralls) +[![Coverage Status](https://coveralls.io/repos/github/php-ai/php-ml/badge.svg?branch=master)](https://coveralls.io/github/php-ai/php-ml?branch=master) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/php-ai/php-ml/?branch=master) @@ -78,6 +78,7 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * [Stratified Random Split](machine-learning/cross-validation/stratified-random-split.md) * Feature Selection * [Variance Threshold](machine-learning/feature-selection/variance-threshold.md) + * [SelectKBest](machine-learning/feature-selection/selectkbest.md) * Preprocessing * [Normalization](machine-learning/preprocessing/normalization.md) * [Imputation missing values](machine-learning/preprocessing/imputation-missing-values.md) diff --git a/docs/machine-learning/feature-selection/selectkbest.md b/docs/machine-learning/feature-selection/selectkbest.md new file mode 100644 index 00000000..2d8024cd --- /dev/null +++ b/docs/machine-learning/feature-selection/selectkbest.md @@ -0,0 +1,96 @@ +# SelectKBest + +`SelectKBest` - select features according to the k highest scores. + +## Constructor Parameters + +* $k (int) - number of top features to select, rest will be removed (default: 10) +* $scoringFunction (ScoringFunction) - function that take samples and targets and return array with scores (default: ANOVAFValue) + +```php +use Phpml\FeatureSelection\SelectKBest; + +$transformer = new SelectKBest(2); +``` + +## Example of use + +As an example we can perform feature selection on Iris dataset to retrieve only the two best features as follows: + +```php +use Phpml\FeatureSelection\SelectKBest; +use Phpml\Dataset\Demo\IrisDataset; + +$dataset = new IrisDataset(); +$selector = new SelectKBest(2); +$selector->fit($samples = $dataset->getSamples(), $dataset->getTargets()); +$selector->transform($samples); + +/* +$samples[0] = [1.4, 0.2]; +*/ +``` + +## Scores + +You can get a array with the calculated score for each feature. +A higher value means that a given feature is better suited for learning. +Of course, the rating depends on the scoring function used. + +``` +use Phpml\FeatureSelection\SelectKBest; +use Phpml\Dataset\Demo\IrisDataset; + +$dataset = new IrisDataset(); +$selector = new SelectKBest(2); +$selector->fit($samples = $dataset->getSamples(), $dataset->getTargets()); +$selector->scores(); + +/* +..array(4) { + [0]=> + float(119.26450218451) + [1]=> + float(47.364461402997) + [2]=> + float(1179.0343277002) + [3]=> + float(959.32440572573) +} +*/ +``` + +## Scoring function + +Available scoring functions: + +For classification: + - **ANOVAFValue** + The one-way ANOVA tests the null hypothesis that 2 or more groups have the same population mean. + The test is applied to samples from two or more groups, possibly with differing sizes. + +For regression: + - **UnivariateLinearRegression** + Quick linear model for testing the effect of a single regressor, sequentially for many regressors. + This is done in 2 steps: + - 1. The cross correlation between each regressor and the target is computed, that is, ((X[:, i] - mean(X[:, i])) * (y - mean_y)) / (std(X[:, i]) *std(y)). + - 2. It is converted to an F score + +## Pipeline + +`SelectKBest` implements `Transformer` interface so it can be used as part of pipeline: + +```php +use Phpml\FeatureSelection\SelectKBest; +use Phpml\Classification\SVC; +use Phpml\FeatureExtraction\TfIdfTransformer; +use Phpml\Pipeline; + +$transformers = [ + new TfIdfTransformer(), + new SelectKBest(3) +]; +$estimator = new SVC(); + +$pipeline = new Pipeline($transformers, $estimator); +``` diff --git a/mkdocs.yml b/mkdocs.yml index f794320e..92f38375 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -27,6 +27,7 @@ pages: - Stratified Random Split: machine-learning/cross-validation/stratified-random-split.md - Feature Selection: - VarianceThreshold: machine-learning/feature-selection/variance-threshold.md + - SelectKBest: machine-learning/feature-selection/selectkbest.md - Preprocessing: - Normalization: machine-learning/preprocessing/normalization.md - Imputation missing values: machine-learning/preprocessing/imputation-missing-values.md diff --git a/src/FeatureSelection/ScoringFunction/UnivariateLinearRegression.php b/src/FeatureSelection/ScoringFunction/UnivariateLinearRegression.php index 9a819c52..968c4741 100644 --- a/src/FeatureSelection/ScoringFunction/UnivariateLinearRegression.php +++ b/src/FeatureSelection/ScoringFunction/UnivariateLinearRegression.php @@ -16,7 +16,7 @@ * * 1. The cross correlation between each regressor and the target is computed, * that is, ((X[:, i] - mean(X[:, i])) * (y - mean_y)) / (std(X[:, i]) *std(y)). - * 2. It is converted to an F score then to a p-value. + * 2. It is converted to an F score. * * Ported from scikit-learn f_regression function (http://scikit-learn.org/stable/modules/generated/sklearn.feature_selection.f_regression.html#sklearn.feature_selection.f_regression) */ diff --git a/tests/PipelineTest.php b/tests/PipelineTest.php index 6f1562c7..d72e0c81 100644 --- a/tests/PipelineTest.php +++ b/tests/PipelineTest.php @@ -7,7 +7,6 @@ use Phpml\Classification\SVC; use Phpml\FeatureExtraction\TfIdfTransformer; use Phpml\FeatureExtraction\TokenCountVectorizer; -use Phpml\FeatureSelection\ScoringFunction\ANOVAFValue; use Phpml\FeatureSelection\SelectKBest; use Phpml\ModelManager; use Phpml\Pipeline; @@ -108,7 +107,7 @@ public function testPipelineTransformers(): void $this->assertEquals($expected, $predicted); } - public function testPipelineTransformersWithTargets() : void + public function testPipelineTransformersWithTargets(): void { $samples = [[1, 2, 1], [1, 3, 4], [5, 2, 1], [1, 3, 3], [1, 3, 4], [0, 3, 5]]; $targets = ['a', 'a', 'a', 'b', 'b', 'b']; From 6ac61a860c5163d37ba6ce84d15ee5a8818ad9e3 Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Thu, 15 Feb 2018 18:14:06 +0100 Subject: [PATCH 250/328] Fix 'toSmall' typo (#234) --- src/Exception/InvalidArgumentException.php | 2 +- src/Math/Statistic/ANOVA.php | 2 +- src/Math/Statistic/Covariance.php | 4 ++-- src/Math/Statistic/StandardDeviation.php | 2 +- tests/CrossValidation/RandomSplitTest.php | 2 +- tests/Math/Statistic/ANOVATest.php | 2 +- tests/Math/Statistic/CovarianceTest.php | 4 ++-- tests/Math/Statistic/StandardDeviationTest.php | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php index e02d14d8..8aebbe01 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -23,7 +23,7 @@ public static function arrayCantBeEmpty(): self return new self('The array has zero elements'); } - public static function arraySizeToSmall(int $minimumSize = 2): self + public static function arraySizeTooSmall(int $minimumSize = 2): self { return new self(sprintf('The array must have at least %d elements', $minimumSize)); } diff --git a/src/Math/Statistic/ANOVA.php b/src/Math/Statistic/ANOVA.php index b0d0d37f..65548a19 100644 --- a/src/Math/Statistic/ANOVA.php +++ b/src/Math/Statistic/ANOVA.php @@ -25,7 +25,7 @@ public static function oneWayF(array $samples): array { $classes = count($samples); if ($classes < 2) { - throw InvalidArgumentException::arraySizeToSmall(2); + throw InvalidArgumentException::arraySizeTooSmall(2); } $samplesPerClass = array_map(function (array $class): int { diff --git a/src/Math/Statistic/Covariance.php b/src/Math/Statistic/Covariance.php index ac24a53b..3dfce4bf 100644 --- a/src/Math/Statistic/Covariance.php +++ b/src/Math/Statistic/Covariance.php @@ -21,7 +21,7 @@ public static function fromXYArrays(array $x, array $y, bool $sample = true, ?fl $n = count($x); if ($sample && $n === 1) { - throw InvalidArgumentException::arraySizeToSmall(2); + throw InvalidArgumentException::arraySizeTooSmall(2); } if ($meanX === null) { @@ -59,7 +59,7 @@ public static function fromDataset(array $data, int $i, int $k, bool $sample = t $n = count($data); if ($sample && $n === 1) { - throw InvalidArgumentException::arraySizeToSmall(2); + throw InvalidArgumentException::arraySizeTooSmall(2); } if ($i < 0 || $k < 0 || $i >= $n || $k >= $n) { diff --git a/src/Math/Statistic/StandardDeviation.php b/src/Math/Statistic/StandardDeviation.php index 426e4fd5..5bf4940a 100644 --- a/src/Math/Statistic/StandardDeviation.php +++ b/src/Math/Statistic/StandardDeviation.php @@ -20,7 +20,7 @@ public static function population(array $numbers, bool $sample = true): float $n = count($numbers); if ($sample && $n === 1) { - throw InvalidArgumentException::arraySizeToSmall(2); + throw InvalidArgumentException::arraySizeTooSmall(2); } $mean = Mean::arithmetic($numbers); diff --git a/tests/CrossValidation/RandomSplitTest.php b/tests/CrossValidation/RandomSplitTest.php index 75f60ff5..65e8d8b9 100644 --- a/tests/CrossValidation/RandomSplitTest.php +++ b/tests/CrossValidation/RandomSplitTest.php @@ -11,7 +11,7 @@ class RandomSplitTest extends TestCase { - public function testThrowExceptionOnToSmallTestSize(): void + public function testThrowExceptionOnTooSmallTestSize(): void { $this->expectException(InvalidArgumentException::class); new RandomSplit(new ArrayDataset([], []), 0); diff --git a/tests/Math/Statistic/ANOVATest.php b/tests/Math/Statistic/ANOVATest.php index 81717f86..2203bf17 100644 --- a/tests/Math/Statistic/ANOVATest.php +++ b/tests/Math/Statistic/ANOVATest.php @@ -32,7 +32,7 @@ public function testOneWayFWithDifferingSizes(): void self::assertEquals([0.6, 2.4, 1.24615385], ANOVA::oneWayF($samples), '', 0.00000001); } - public function testThrowExceptionOnToSmallSamples(): void + public function testThrowExceptionOnTooSmallSamples(): void { $this->expectException(InvalidArgumentException::class); $samples = [ diff --git a/tests/Math/Statistic/CovarianceTest.php b/tests/Math/Statistic/CovarianceTest.php index 43b775a9..fd7187a3 100644 --- a/tests/Math/Statistic/CovarianceTest.php +++ b/tests/Math/Statistic/CovarianceTest.php @@ -73,7 +73,7 @@ public function testThrowExceptionOnEmptyY(): void Covariance::fromXYArrays([1, 2, 3], []); } - public function testThrowExceptionOnToSmallArrayIfSample(): void + public function testThrowExceptionOnTooSmallArrayIfSample(): void { $this->expectException(InvalidArgumentException::class); Covariance::fromXYArrays([1], [2], true); @@ -85,7 +85,7 @@ public function testThrowExceptionIfEmptyDataset(): void Covariance::fromDataset([], 0, 1); } - public function testThrowExceptionOnToSmallDatasetIfSample(): void + public function testThrowExceptionOnTooSmallDatasetIfSample(): void { $this->expectException(InvalidArgumentException::class); Covariance::fromDataset([1], 0, 1); diff --git a/tests/Math/Statistic/StandardDeviationTest.php b/tests/Math/Statistic/StandardDeviationTest.php index 51c27704..c6fd47e6 100644 --- a/tests/Math/Statistic/StandardDeviationTest.php +++ b/tests/Math/Statistic/StandardDeviationTest.php @@ -32,7 +32,7 @@ public function testThrowExceptionOnEmptyArrayIfNotSample(): void StandardDeviation::population([], false); } - public function testThrowExceptionOnToSmallArray(): void + public function testThrowExceptionOnTooSmallArray(): void { $this->expectException(InvalidArgumentException::class); StandardDeviation::population([1]); From 16dc16b0d97507e8ef91d85fbbf34b4c757502ab Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 16 Feb 2018 07:25:24 +0100 Subject: [PATCH 251/328] Add phpstan strict rules (#233) * Add phpstan strict rules * Fix travis coveralls * Add phpstan-phpunit strict rules * Fix eigen decomposition test name and phpstan ingored error --- .travis.yml | 2 +- composer.json | 2 + composer.lock | 91 ++++++++++++++++++- phpstan.neon | 10 +- src/Association/Apriori.php | 4 +- src/Classification/DecisionTree.php | 26 +++--- .../DecisionTree/DecisionTreeLeaf.php | 20 ++-- src/Classification/Ensemble/AdaBoost.php | 2 +- src/Classification/Ensemble/Bagging.php | 2 +- src/Classification/Linear/Adaline.php | 2 +- src/Classification/Linear/DecisionStump.php | 2 +- .../Linear/LogisticRegression.php | 4 +- src/Classification/Linear/Perceptron.php | 4 +- src/Classification/MLPClassifier.php | 4 +- src/Clustering/FuzzyCMeans.php | 5 +- src/Clustering/KMeans/Cluster.php | 4 +- src/Clustering/KMeans/Space.php | 8 +- src/DimensionReduction/KernelPCA.php | 2 +- src/DimensionReduction/LDA.php | 4 +- src/FeatureExtraction/TfIdfTransformer.php | 4 +- .../TokenCountVectorizer.php | 2 +- .../LinearAlgebra/EigenvalueDecomposition.php | 1 + src/Math/Matrix.php | 2 +- src/Math/Statistic/Mean.php | 2 +- src/Metric/ClassificationReport.php | 2 +- src/Metric/ConfusionMatrix.php | 4 +- src/NeuralNetwork/Layer.php | 2 +- src/Preprocessing/Normalizer.php | 2 +- src/SupportVectorMachine/DataTransformer.php | 4 +- .../SupportVectorMachine.php | 6 +- tests/Clustering/FuzzyCMeansTest.php | 2 +- tests/Clustering/KMeansTest.php | 2 +- ...st.php => EigenvalueDecompositionTest.php} | 2 +- 33 files changed, 164 insertions(+), 71 deletions(-) rename tests/Math/LinearAlgebra/{EigenDecompositionTest.php => EigenvalueDecompositionTest.php} (97%) diff --git a/.travis.yml b/.travis.yml index d4df191a..bc647cb9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,5 +41,5 @@ after_success: - | if [[ $PHPUNIT_FLAGS != "" ]]; then wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.0.0/php-coveralls.phar - php coveralls.phar --verbose; + php php-coveralls.phar --verbose; fi diff --git a/composer.json b/composer.json index b39a39e6..c8652d9b 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,9 @@ "php": "^7.1" }, "require-dev": { + "phpstan/phpstan-phpunit": "^0.9.4", "phpstan/phpstan-shim": "^0.9", + "phpstan/phpstan-strict-rules": "^0.9.0", "phpunit/phpunit": "^7.0.0", "symplify/coding-standard": "^3.1", "symplify/easy-coding-standard": "^3.1" diff --git a/composer.lock b/composer.lock index 1545cc2a..0462e945 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "3e327a50a76dd6df905cef56cbc37e02", + "content-hash": "9135b3dece8c6938922f757123274e95", "packages": [], "packages-dev": [ { @@ -1287,6 +1287,51 @@ ], "time": "2017-11-24T13:59:53+00:00" }, + { + "name": "phpstan/phpstan-phpunit", + "version": "0.9.4", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-phpunit.git", + "reference": "852411f841a37aeca2fa5af0002b0272c485c9bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/852411f841a37aeca2fa5af0002b0272c485c9bf", + "reference": "852411f841a37aeca2fa5af0002b0272c485c9bf", + "shasum": "" + }, + "require": { + "php": "~7.0", + "phpstan/phpstan": "^0.9.1", + "phpunit/phpunit": "^6.3 || ~7.0" + }, + "require-dev": { + "consistence/coding-standard": "^2.0", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/phpstan-strict-rules": "^0.9", + "satooshi/php-coveralls": "^1.0", + "slevomat/coding-standard": "^3.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.9-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPUnit extensions and rules for PHPStan", + "time": "2018-02-02T09:45:47+00:00" + }, { "name": "phpstan/phpstan-shim", "version": "0.9.1", @@ -1324,6 +1369,50 @@ "description": "PHPStan Phar distribution", "time": "2017-12-02T20:14:45+00:00" }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "0.9", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "15be9090622c6b85c079922308f831018d8d9e23" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/15be9090622c6b85c079922308f831018d8d9e23", + "reference": "15be9090622c6b85c079922308f831018d8d9e23", + "shasum": "" + }, + "require": { + "php": "~7.0", + "phpstan/phpstan": "^0.9" + }, + "require-dev": { + "consistence/coding-standard": "^2.0.0", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/phpstan-phpunit": "^0.9", + "phpunit/phpunit": "^6.4", + "slevomat/coding-standard": "^3.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.9-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "time": "2017-11-26T20:12:30+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "6.0.1", diff --git a/phpstan.neon b/phpstan.neon index 0366c23e..7eaee1c8 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,15 +1,17 @@ +includes: + - vendor/phpstan/phpstan-strict-rules/rules.neon + - vendor/phpstan/phpstan-phpunit/extension.neon + - vendor/phpstan/phpstan-phpunit/rules.neon + - vendor/phpstan/phpstan-phpunit/strictRules.neon + parameters: ignoreErrors: - '#Phpml\\Dataset\\FilesDataset::__construct\(\) does not call parent constructor from Phpml\\Dataset\\ArrayDataset#' - # mocks - - '#PHPUnit_Framework_MockObject_MockObject#' - # wide range cases - '#Call to function count\(\) with argument type array\|Phpml\\Clustering\\KMeans\\Point will always result in number 1#' - '#Parameter \#1 \$coordinates of class Phpml\\Clustering\\KMeans\\Point constructor expects array, array\|Phpml\\Clustering\\KMeans\\Point given#' # probably known value - - '#Variable \$j might not be defined#' - '#Method Phpml\\Classification\\DecisionTree::getBestSplit\(\) should return Phpml\\Classification\\DecisionTree\\DecisionTreeLeaf but returns Phpml\\Classification\\DecisionTree\\DecisionTreeLeaf\|null#' - '#Call to an undefined method Phpml\\Helper\\Optimizer\\Optimizer::getCostValues\(\)#' diff --git a/src/Association/Apriori.php b/src/Association/Apriori.php index 1d7f99fe..c3f9c912 100644 --- a/src/Association/Apriori.php +++ b/src/Association/Apriori.php @@ -63,11 +63,11 @@ public function __construct(float $support = 0.0, float $confidence = 0.0) */ public function getRules(): array { - if (!$this->large) { + if (empty($this->large)) { $this->large = $this->apriori(); } - if ($this->rules) { + if (!empty($this->rules)) { return $this->rules; } diff --git a/src/Classification/DecisionTree.php b/src/Classification/DecisionTree.php index 1d5a7548..690f79c8 100644 --- a/src/Classification/DecisionTree.php +++ b/src/Classification/DecisionTree.php @@ -273,15 +273,15 @@ protected function getSplitLeaf(array $records, int $depth = 0): DecisionTreeLea } if ($allSame || $depth >= $this->maxDepth || count($remainingTargets) === 1) { - $split->isTerminal = 1; + $split->isTerminal = true; arsort($remainingTargets); $split->classValue = key($remainingTargets); } else { - if ($leftRecords) { + if (!empty($leftRecords)) { $split->leftLeaf = $this->getSplitLeaf($leftRecords, $depth + 1); } - if ($rightRecords) { + if (!empty($rightRecords)) { $split->rightLeaf = $this->getSplitLeaf($rightRecords, $depth + 1); } } @@ -312,13 +312,13 @@ protected function getBestSplit(array $records): DecisionTreeLeaf $split->value = $baseValue; $split->giniIndex = $gini; $split->columnIndex = $i; - $split->isContinuous = $this->columnTypes[$i] == self::CONTINUOUS; + $split->isContinuous = $this->columnTypes[$i] === self::CONTINUOUS; $split->records = $records; // If a numeric column is to be selected, then // the original numeric value and the selected operator // will also be saved into the leaf for future access - if ($this->columnTypes[$i] == self::CONTINUOUS) { + if ($this->columnTypes[$i] === self::CONTINUOUS) { $matches = []; preg_match("/^([<>=]{1,2})\s*(.*)/", (string) $split->value, $matches); $split->operator = $matches[1]; @@ -349,11 +349,11 @@ protected function getBestSplit(array $records): DecisionTreeLeaf protected function getSelectedFeatures(): array { $allFeatures = range(0, $this->featureCount - 1); - if ($this->numUsableFeatures === 0 && !$this->selectedFeatures) { + if ($this->numUsableFeatures === 0 && empty($this->selectedFeatures)) { return $allFeatures; } - if ($this->selectedFeatures) { + if (!empty($this->selectedFeatures)) { return $this->selectedFeatures; } @@ -363,7 +363,7 @@ protected function getSelectedFeatures(): array } shuffle($allFeatures); - $selectedFeatures = array_slice($allFeatures, 0, $numFeatures, false); + $selectedFeatures = array_slice($allFeatures, 0, $numFeatures); sort($selectedFeatures); return $selectedFeatures; @@ -406,7 +406,7 @@ protected static function isCategoricalColumn(array $columnValues): bool // all values in that column (Lower than or equal to %20 of all values) $numericValues = array_filter($columnValues, 'is_numeric'); $floatValues = array_filter($columnValues, 'is_float'); - if ($floatValues) { + if (!empty($floatValues)) { return false; } @@ -433,7 +433,7 @@ protected function setSelectedFeatures(array $selectedFeatures): void */ protected function getSplitNodesByColumn(int $column, DecisionTreeLeaf $node): array { - if (!$node || $node->isTerminal) { + if ($node->isTerminal) { return []; } @@ -444,11 +444,11 @@ protected function getSplitNodesByColumn(int $column, DecisionTreeLeaf $node): a $lNodes = []; $rNodes = []; - if ($node->leftLeaf) { + if ($node->leftLeaf !== null) { $lNodes = $this->getSplitNodesByColumn($column, $node->leftLeaf); } - if ($node->rightLeaf) { + if ($node->rightLeaf !== null) { $rNodes = $this->getSplitNodesByColumn($column, $node->rightLeaf); } @@ -475,6 +475,6 @@ protected function predictSample(array $sample) } } while ($node); - return $node ? $node->classValue : $this->labels[0]; + return $node !== null ? $node->classValue : $this->labels[0]; } } diff --git a/src/Classification/DecisionTree/DecisionTreeLeaf.php b/src/Classification/DecisionTree/DecisionTreeLeaf.php index cc53eeac..5a106ab3 100644 --- a/src/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Classification/DecisionTree/DecisionTreeLeaf.php @@ -29,14 +29,14 @@ class DecisionTreeLeaf public $columnIndex; /** - * @var DecisionTreeLeaf + * @var ?DecisionTreeLeaf */ - public $leftLeaf = null; + public $leftLeaf; /** - * @var DecisionTreeLeaf + * @var ?DecisionTreeLeaf */ - public $rightLeaf = null; + public $rightLeaf; /** * @var array @@ -52,7 +52,7 @@ class DecisionTreeLeaf public $classValue = ''; /** - * @var bool|int + * @var bool */ public $isTerminal = false; @@ -103,12 +103,12 @@ public function getNodeImpurityDecrease(int $parentRecordCount): float $nodeSampleCount = (float) count($this->records); $iT = $this->giniIndex; - if ($this->leftLeaf) { + if ($this->leftLeaf !== null) { $pL = count($this->leftLeaf->records) / $nodeSampleCount; $iT -= $pL * $this->leftLeaf->giniIndex; } - if ($this->rightLeaf) { + if ($this->rightLeaf !== null) { $pR = count($this->rightLeaf->records) / $nodeSampleCount; $iT -= $pR * $this->rightLeaf->giniIndex; } @@ -140,16 +140,16 @@ public function getHTML($columnNames = null): string $str = "
${value}
"; - if ($this->leftLeaf || $this->rightLeaf) { + if ($this->leftLeaf !== null || $this->rightLeaf !== null) { $str .= ''; - if ($this->leftLeaf) { + if ($this->leftLeaf !== null) { $str .= ''; } else { $str .= ''; } $str .= ''; - if ($this->rightLeaf) { + if ($this->rightLeaf !== null) { $str .= ''; } else { $str .= ''; diff --git a/src/Classification/Ensemble/AdaBoost.php b/src/Classification/Ensemble/AdaBoost.php index 67f71983..b314c81a 100644 --- a/src/Classification/Ensemble/AdaBoost.php +++ b/src/Classification/Ensemble/AdaBoost.php @@ -158,7 +158,7 @@ public function predictSample(array $sample) protected function getBestClassifier(): Classifier { $ref = new ReflectionClass($this->baseClassifier); - if ($this->classifierOptions) { + if (!empty($this->classifierOptions)) { $classifier = $ref->newInstanceArgs($this->classifierOptions); } else { $classifier = $ref->newInstance(); diff --git a/src/Classification/Ensemble/Bagging.php b/src/Classification/Ensemble/Bagging.php index a3d8e5e0..a1d51c25 100644 --- a/src/Classification/Ensemble/Bagging.php +++ b/src/Classification/Ensemble/Bagging.php @@ -145,7 +145,7 @@ protected function initClassifiers(): array $classifiers = []; for ($i = 0; $i < $this->numClassifier; ++$i) { $ref = new ReflectionClass($this->classifier); - if ($this->classifierOptions) { + if (!empty($this->classifierOptions)) { $obj = $ref->newInstanceArgs($this->classifierOptions); } else { $obj = $ref->newInstance(); diff --git a/src/Classification/Linear/Adaline.php b/src/Classification/Linear/Adaline.php index de2e1525..3b6309de 100644 --- a/src/Classification/Linear/Adaline.php +++ b/src/Classification/Linear/Adaline.php @@ -42,7 +42,7 @@ public function __construct( bool $normalizeInputs = true, int $trainingType = self::BATCH_TRAINING ) { - if (!in_array($trainingType, [self::BATCH_TRAINING, self::ONLINE_TRAINING])) { + if (!in_array($trainingType, [self::BATCH_TRAINING, self::ONLINE_TRAINING], true)) { throw new Exception('Adaline can only be trained with batch and online/stochastic gradient descent algorithm'); } diff --git a/src/Classification/Linear/DecisionStump.php b/src/Classification/Linear/DecisionStump.php index b0f7dfa0..1903712f 100644 --- a/src/Classification/Linear/DecisionStump.php +++ b/src/Classification/Linear/DecisionStump.php @@ -118,7 +118,7 @@ protected function trainBinary(array $samples, array $targets, array $labels): v // Check the size of the weights given. // If none given, then assign 1 as a weight to each sample - if ($this->weights) { + if (!empty($this->weights)) { $numWeights = count($this->weights); if ($numWeights != count($samples)) { throw new Exception('Number of sample weights does not match with number of samples'); diff --git a/src/Classification/Linear/LogisticRegression.php b/src/Classification/Linear/LogisticRegression.php index b5955873..13f4b8a9 100644 --- a/src/Classification/Linear/LogisticRegression.php +++ b/src/Classification/Linear/LogisticRegression.php @@ -71,13 +71,13 @@ public function __construct( string $penalty = 'L2' ) { $trainingTypes = range(self::BATCH_TRAINING, self::CONJUGATE_GRAD_TRAINING); - if (!in_array($trainingType, $trainingTypes)) { + if (!in_array($trainingType, $trainingTypes, true)) { throw new Exception('Logistic regression can only be trained with '. 'batch (gradient descent), online (stochastic gradient descent) '. 'or conjugate batch (conjugate gradients) algorithms'); } - if (!in_array($cost, ['log', 'sse'])) { + if (!in_array($cost, ['log', 'sse'], true)) { throw new Exception("Logistic regression cost function can be one of the following: \n". "'log' for log-likelihood and 'sse' for sum of squared errors"); } diff --git a/src/Classification/Linear/Perceptron.php b/src/Classification/Linear/Perceptron.php index c78e7883..5fffc01d 100644 --- a/src/Classification/Linear/Perceptron.php +++ b/src/Classification/Linear/Perceptron.php @@ -97,7 +97,7 @@ public function partialTrain(array $samples, array $targets, array $labels = []) public function trainBinary(array $samples, array $targets, array $labels): void { - if ($this->normalizer) { + if ($this->normalizer !== null) { $this->normalizer->transform($samples); } @@ -196,7 +196,7 @@ protected function runGradientDescent(array $samples, array $targets, Closure $g */ protected function checkNormalizedSample(array $sample): array { - if ($this->normalizer) { + if ($this->normalizer !== null) { $samples = [$sample]; $this->normalizer->transform($samples); $sample = $samples[0]; diff --git a/src/Classification/MLPClassifier.php b/src/Classification/MLPClassifier.php index b225a64c..432582be 100644 --- a/src/Classification/MLPClassifier.php +++ b/src/Classification/MLPClassifier.php @@ -16,11 +16,11 @@ class MLPClassifier extends MultilayerPerceptron implements Classifier */ public function getTargetClass($target): int { - if (!in_array($target, $this->classes)) { + if (!in_array($target, $this->classes, true)) { throw InvalidArgumentException::invalidTarget($target); } - return array_search($target, $this->classes); + return array_search($target, $this->classes, true); } /** diff --git a/src/Clustering/FuzzyCMeans.php b/src/Clustering/FuzzyCMeans.php index 7e177ed5..a5408c03 100644 --- a/src/Clustering/FuzzyCMeans.php +++ b/src/Clustering/FuzzyCMeans.php @@ -20,7 +20,7 @@ class FuzzyCMeans implements Clusterer /** * @var array|Cluster[] */ - private $clusters = null; + private $clusters = []; /** * @var Space @@ -152,8 +152,7 @@ protected function generateRandomMembership(int $rows, int $cols): void protected function updateClusters(): void { $dim = $this->space->getDimension(); - if (!$this->clusters) { - $this->clusters = []; + if (empty($this->clusters)) { for ($i = 0; $i < $this->clustersNumber; ++$i) { $this->clusters[] = new Cluster($this->space, array_fill(0, $dim, 0.0)); } diff --git a/src/Clustering/KMeans/Cluster.php b/src/Clustering/KMeans/Cluster.php index fea1ff82..2011b87d 100644 --- a/src/Clustering/KMeans/Cluster.php +++ b/src/Clustering/KMeans/Cluster.php @@ -76,8 +76,7 @@ public function detachAll(SplObjectStorage $points): void public function updateCentroid(): void { - $count = count($this->points); - if (!$count) { + if (empty($this->points)) { return; } @@ -89,6 +88,7 @@ public function updateCentroid(): void } } + $count = count($this->points); for ($n = 0; $n < $this->dimension; ++$n) { $this->coordinates[$n] = $centroid->coordinates[$n] / $count; } diff --git a/src/Clustering/KMeans/Space.php b/src/Clustering/KMeans/Space.php index 371bbc31..3c9f134f 100644 --- a/src/Clustering/KMeans/Space.php +++ b/src/Clustering/KMeans/Space.php @@ -16,7 +16,7 @@ class Space extends SplObjectStorage */ protected $dimension; - public function __construct($dimension) + public function __construct(int $dimension) { if ($dimension < 1) { throw new LogicException('a space dimension cannot be null or negative'); @@ -75,7 +75,7 @@ public function getDimension(): int */ public function getBoundaries() { - if (!count($this)) { + if (empty($this)) { return false; } @@ -153,8 +153,8 @@ protected function iterate($clusters): bool $closest = $point->getClosest($clusters); if ($closest !== $cluster) { - isset($attach[$closest]) || $attach[$closest] = new SplObjectStorage(); - isset($detach[$cluster]) || $detach[$cluster] = new SplObjectStorage(); + $attach[$closest] ?? $attach[$closest] = new SplObjectStorage(); + $detach[$cluster] ?? $detach[$cluster] = new SplObjectStorage(); $attach[$closest]->attach($point); $detach[$cluster]->attach($point); diff --git a/src/DimensionReduction/KernelPCA.php b/src/DimensionReduction/KernelPCA.php index 5dcc7a0c..b962b3d4 100644 --- a/src/DimensionReduction/KernelPCA.php +++ b/src/DimensionReduction/KernelPCA.php @@ -58,7 +58,7 @@ class KernelPCA extends PCA public function __construct(int $kernel = self::KERNEL_RBF, ?float $totalVariance = null, ?int $numFeatures = null, ?float $gamma = null) { $availableKernels = [self::KERNEL_RBF, self::KERNEL_SIGMOID, self::KERNEL_LAPLACIAN, self::KERNEL_LINEAR]; - if (!in_array($kernel, $availableKernels)) { + if (!in_array($kernel, $availableKernels, true)) { throw new Exception('KernelPCA can be initialized with the following kernels only: Linear, RBF, Sigmoid and Laplacian'); } diff --git a/src/DimensionReduction/LDA.php b/src/DimensionReduction/LDA.php index 6400d141..d188205a 100644 --- a/src/DimensionReduction/LDA.php +++ b/src/DimensionReduction/LDA.php @@ -130,7 +130,7 @@ protected function calculateMeans(array $data, array $classes): array $overallMean = array_fill(0, count($data[0]), 0.0); foreach ($data as $index => $row) { - $label = array_search($classes[$index], $this->labels); + $label = array_search($classes[$index], $this->labels, true); foreach ($row as $col => $val) { if (!isset($means[$label][$col])) { @@ -177,7 +177,7 @@ protected function calculateClassVar(array $data, array $classes): Matrix $sW = new Matrix($s, false); foreach ($data as $index => $row) { - $label = array_search($classes[$index], $this->labels); + $label = array_search($classes[$index], $this->labels, true); $means = $this->means[$label]; $row = $this->calculateVar($row, $means); diff --git a/src/FeatureExtraction/TfIdfTransformer.php b/src/FeatureExtraction/TfIdfTransformer.php index 30f0203d..4a478c30 100644 --- a/src/FeatureExtraction/TfIdfTransformer.php +++ b/src/FeatureExtraction/TfIdfTransformer.php @@ -13,9 +13,9 @@ class TfIdfTransformer implements Transformer */ private $idf = []; - public function __construct(?array $samples = null) + public function __construct(array $samples = []) { - if ($samples) { + if (!empty($samples)) { $this->fit($samples); } } diff --git a/src/FeatureExtraction/TokenCountVectorizer.php b/src/FeatureExtraction/TokenCountVectorizer.php index 8c757c02..c73836c7 100644 --- a/src/FeatureExtraction/TokenCountVectorizer.php +++ b/src/FeatureExtraction/TokenCountVectorizer.php @@ -123,7 +123,7 @@ private function addTokenToVocabulary(string $token): void private function isStopWord(string $token): bool { - return $this->stopWords && $this->stopWords->isStopWord($token); + return $this->stopWords !== null && $this->stopWords->isStopWord($token); } private function updateFrequency(string $token): void diff --git a/src/Math/LinearAlgebra/EigenvalueDecomposition.php b/src/Math/LinearAlgebra/EigenvalueDecomposition.php index 17303091..8aac90b0 100644 --- a/src/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -274,6 +274,7 @@ private function tred2(): void } // Accumulate transformations. + $j = 0; for ($i = 0; $i < $this->n - 1; ++$i) { $this->V[$this->n - 1][$i] = $this->V[$i][$i]; $this->V[$i][$i] = 1.0; diff --git a/src/Math/Matrix.php b/src/Math/Matrix.php index e7bc92e3..908ec4d7 100644 --- a/src/Math/Matrix.php +++ b/src/Math/Matrix.php @@ -105,7 +105,7 @@ public function getColumnValues($column): array */ public function getDeterminant() { - if ($this->determinant) { + if ($this->determinant !== null) { return $this->determinant; } diff --git a/src/Math/Statistic/Mean.php b/src/Math/Statistic/Mean.php index 8791a657..8e761dfb 100644 --- a/src/Math/Statistic/Mean.php +++ b/src/Math/Statistic/Mean.php @@ -50,7 +50,7 @@ public static function mode(array $numbers) $values = array_count_values($numbers); - return array_search(max($values), $values); + return array_search(max($values), $values, true); } /** diff --git a/src/Metric/ClassificationReport.php b/src/Metric/ClassificationReport.php index 755d78b5..4409474d 100644 --- a/src/Metric/ClassificationReport.php +++ b/src/Metric/ClassificationReport.php @@ -57,7 +57,7 @@ class ClassificationReport public function __construct(array $actualLabels, array $predictedLabels, int $average = self::MACRO_AVERAGE) { $averagingMethods = range(self::MICRO_AVERAGE, self::WEIGHTED_AVERAGE); - if (!in_array($average, $averagingMethods)) { + if (!in_array($average, $averagingMethods, true)) { throw new InvalidArgumentException('Averaging method must be MICRO_AVERAGE, MACRO_AVERAGE or WEIGHTED_AVERAGE'); } diff --git a/src/Metric/ConfusionMatrix.php b/src/Metric/ConfusionMatrix.php index e86a8ed9..a1f49ce4 100644 --- a/src/Metric/ConfusionMatrix.php +++ b/src/Metric/ConfusionMatrix.php @@ -6,9 +6,9 @@ class ConfusionMatrix { - public static function compute(array $actualLabels, array $predictedLabels, ?array $labels = null): array + public static function compute(array $actualLabels, array $predictedLabels, array $labels = []): array { - $labels = $labels ? array_flip($labels) : self::getUniqueLabels($actualLabels); + $labels = !empty($labels) ? array_flip($labels) : self::getUniqueLabels($actualLabels); $matrix = self::generateMatrixWithZeros($labels); foreach ($actualLabels as $index => $actual) { diff --git a/src/NeuralNetwork/Layer.php b/src/NeuralNetwork/Layer.php index 7424348f..a604a536 100644 --- a/src/NeuralNetwork/Layer.php +++ b/src/NeuralNetwork/Layer.php @@ -19,7 +19,7 @@ class Layer */ public function __construct(int $nodesNumber = 0, string $nodeClass = Neuron::class, ?ActivationFunction $activationFunction = null) { - if (!in_array(Node::class, class_implements($nodeClass))) { + if (!in_array(Node::class, class_implements($nodeClass), true)) { throw InvalidArgumentException::invalidLayerNodeClass(); } diff --git a/src/Preprocessing/Normalizer.php b/src/Preprocessing/Normalizer.php index 39b4fbc4..9654e48a 100644 --- a/src/Preprocessing/Normalizer.php +++ b/src/Preprocessing/Normalizer.php @@ -41,7 +41,7 @@ class Normalizer implements Preprocessor */ public function __construct(int $norm = self::NORM_L2) { - if (!in_array($norm, [self::NORM_L1, self::NORM_L2, self::NORM_STD])) { + if (!in_array($norm, [self::NORM_L1, self::NORM_L2, self::NORM_STD], true)) { throw NormalizerException::unknownNorm(); } diff --git a/src/SupportVectorMachine/DataTransformer.php b/src/SupportVectorMachine/DataTransformer.php index a4123230..0a99aa38 100644 --- a/src/SupportVectorMachine/DataTransformer.php +++ b/src/SupportVectorMachine/DataTransformer.php @@ -42,7 +42,7 @@ public static function predictions(string $rawPredictions, array $labels): array $results = []; foreach (explode(PHP_EOL, $rawPredictions) as $result) { if (isset($result[0])) { - $results[] = array_search($result, $numericLabels); + $results[] = array_search((int) $result, $numericLabels, true); } } @@ -61,7 +61,7 @@ public static function probabilities(string $rawPredictions, array $labels): arr $columnLabels = []; foreach ($headerColumns as $numericLabel) { - $columnLabels[] = array_search($numericLabel, $numericLabels); + $columnLabels[] = array_search((int) $numericLabel, $numericLabels, true); } $results = []; diff --git a/src/SupportVectorMachine/SupportVectorMachine.php b/src/SupportVectorMachine/SupportVectorMachine.php index 3ec3ed8c..561f65bd 100644 --- a/src/SupportVectorMachine/SupportVectorMachine.php +++ b/src/SupportVectorMachine/SupportVectorMachine.php @@ -149,7 +149,7 @@ public function train(array $samples, array $targets): void $this->samples = array_merge($this->samples, $samples); $this->targets = array_merge($this->targets, $targets); - $trainingSet = DataTransformer::trainingSet($this->samples, $this->targets, in_array($this->type, [Type::EPSILON_SVR, Type::NU_SVR])); + $trainingSet = DataTransformer::trainingSet($this->samples, $this->targets, in_array($this->type, [Type::EPSILON_SVR, Type::NU_SVR], true)); file_put_contents($trainingSetFileName = $this->varPath.uniqid('phpml', true), $trainingSet); $modelFileName = $trainingSetFileName.'-model'; @@ -182,7 +182,7 @@ public function predict(array $samples) { $predictions = $this->runSvmPredict($samples, false); - if (in_array($this->type, [Type::C_SVC, Type::NU_SVC])) { + if (in_array($this->type, [Type::C_SVC, Type::NU_SVC], true)) { $predictions = DataTransformer::predictions($predictions, $this->targets); } else { $predictions = explode(PHP_EOL, trim($predictions)); @@ -208,7 +208,7 @@ public function predictProbability(array $samples) $predictions = $this->runSvmPredict($samples, true); - if (in_array($this->type, [Type::C_SVC, Type::NU_SVC])) { + if (in_array($this->type, [Type::C_SVC, Type::NU_SVC], true)) { $predictions = DataTransformer::probabilities($predictions, $this->targets); } else { $predictions = explode(PHP_EOL, trim($predictions)); diff --git a/tests/Clustering/FuzzyCMeansTest.php b/tests/Clustering/FuzzyCMeansTest.php index 1c3af15d..72454816 100644 --- a/tests/Clustering/FuzzyCMeansTest.php +++ b/tests/Clustering/FuzzyCMeansTest.php @@ -16,7 +16,7 @@ public function testFCMSamplesClustering() $clusters = $fcm->cluster($samples); $this->assertCount(2, $clusters); foreach ($samples as $index => $sample) { - if (in_array($sample, $clusters[0]) || in_array($sample, $clusters[1])) { + if (in_array($sample, $clusters[0], true) || in_array($sample, $clusters[1], true)) { unset($samples[$index]); } } diff --git a/tests/Clustering/KMeansTest.php b/tests/Clustering/KMeansTest.php index dedf9814..032c8048 100644 --- a/tests/Clustering/KMeansTest.php +++ b/tests/Clustering/KMeansTest.php @@ -20,7 +20,7 @@ public function testKMeansSamplesClustering(): void $this->assertCount(2, $clusters); foreach ($samples as $index => $sample) { - if (in_array($sample, $clusters[0]) || in_array($sample, $clusters[1])) { + if (in_array($sample, $clusters[0], true) || in_array($sample, $clusters[1], true)) { unset($samples[$index]); } } diff --git a/tests/Math/LinearAlgebra/EigenDecompositionTest.php b/tests/Math/LinearAlgebra/EigenvalueDecompositionTest.php similarity index 97% rename from tests/Math/LinearAlgebra/EigenDecompositionTest.php rename to tests/Math/LinearAlgebra/EigenvalueDecompositionTest.php index 47c47988..a08ddc17 100644 --- a/tests/Math/LinearAlgebra/EigenDecompositionTest.php +++ b/tests/Math/LinearAlgebra/EigenvalueDecompositionTest.php @@ -8,7 +8,7 @@ use Phpml\Math\Matrix; use PHPUnit\Framework\TestCase; -class EigenDecompositionTest extends TestCase +class EigenvalueDecompositionTest extends TestCase { public function testSymmetricMatrixEigenPairs(): void { From 797952e1bcac18f139bb00d623510ffec0cbd926 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 16 Feb 2018 20:41:37 +0100 Subject: [PATCH 252/328] Prepare CHANGELOG.md for next release --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a87d3db2..5491e033 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,32 @@ CHANGELOG This changelog references the relevant changes done in PHP-ML library. +* 0.6.0 (2018-02-16) + * feature [FeatureSelection] implement SelectKBest with scoring functions (#232) + * feature [FeatureSelection] implement VarianceThreshold - simple baseline approach to feature selection. (#228) + * feature [Classification] support probability estimation in SVC (#218) + * feature [NeuralNetwork] configure an Activation Function per hidden layer (#208) + * feature [NeuralNetwork] Ability to update learningRate in MLP (#160) + * feature [Metric] Choose averaging method in classification report (#205) + * enhancement Add phpstan strict rules (#233) + * enhancement Flatten directory structure (#220) + * enhancement Update phpunit/phpunit (#219) + * enhancement Cache dependencies installed with composer on Travis (#215) + * enhancement Add support for coveralls.io (#153) + * enhancement Add phpstan and easy coding standards (#156, #168) + * enhancement Throw exception when libsvm command fails to run (#200, #202) + * enhancement Normalize composer.json and sort packages (#214, #210) + * enhancement Rewrite DBSCAN (#185) + * fix phpunit include tests path (#230) + * fix support of a rule in Apriori (#229) + * fix apriori generates an empty array as a part of the frequent item sets (#224) + * fix backpropagation random error (#157) + * fix logistic regression implementation (#169) + * fix activation functions support (#163) + * fix string representation of integer labels issue in NaiveBayes (#206) + * fix the implementation of conjugate gradient method (#184) + * typo, tests and documentation fixes (#234, #221, #181, #183, #155, #159, #165, #187, #154, #191, #203, #209, #213, #212, #211) + * 0.5.0 (2017-11-14) * general [php] Upgrade to PHP 7.1 (#150) * general [coding standard] fix imports order and drop unused docs typehints From 0a15561352b84ebbb7452f7d359bd39d62a2c68e Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 18 Feb 2018 00:09:24 +0100 Subject: [PATCH 253/328] Fix KMeans and EigenvalueDecomposition (#235) * Fix kmeans cluster and eigenvalue decomposition * Fix kmeans space * Fix code style --- src/Clustering/KMeans/Cluster.php | 4 +- src/Clustering/KMeans/Space.php | 2 +- .../LinearAlgebra/EigenvalueDecomposition.php | 10 +-- .../EigenvalueDecompositionTest.php | 66 +++++++++++++++---- 4 files changed, 60 insertions(+), 22 deletions(-) diff --git a/src/Clustering/KMeans/Cluster.php b/src/Clustering/KMeans/Cluster.php index 2011b87d..a4462fe7 100644 --- a/src/Clustering/KMeans/Cluster.php +++ b/src/Clustering/KMeans/Cluster.php @@ -76,7 +76,8 @@ public function detachAll(SplObjectStorage $points): void public function updateCentroid(): void { - if (empty($this->points)) { + $count = count($this->points); + if ($count === 0) { return; } @@ -88,7 +89,6 @@ public function updateCentroid(): void } } - $count = count($this->points); for ($n = 0; $n < $this->dimension; ++$n) { $this->coordinates[$n] = $centroid->coordinates[$n] / $count; } diff --git a/src/Clustering/KMeans/Space.php b/src/Clustering/KMeans/Space.php index 3c9f134f..b85b329f 100644 --- a/src/Clustering/KMeans/Space.php +++ b/src/Clustering/KMeans/Space.php @@ -75,7 +75,7 @@ public function getDimension(): int */ public function getBoundaries() { - if (empty($this)) { + if (count($this) === 0) { return false; } diff --git a/src/Math/LinearAlgebra/EigenvalueDecomposition.php b/src/Math/LinearAlgebra/EigenvalueDecomposition.php index 8aac90b0..19f3c433 100644 --- a/src/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -99,7 +99,7 @@ public function __construct(array $Arg) $this->n = count($Arg[0]); $this->symmetric = true; - for ($j = 0; ($j < $this->n) && $this->symmetric; ++$j) { + for ($j = 0; ($j < $this->n) & $this->symmetric; ++$j) { for ($i = 0; ($i < $this->n) & $this->symmetric; ++$i) { $this->symmetric = ($this->A[$i][$j] == $this->A[$j][$i]); } @@ -204,7 +204,7 @@ private function tred2(): void $scale += array_sum(array_map('abs', $this->d)); if ($scale == 0.0) { $this->e[$i] = $this->d[$i_]; - $this->d = array_slice($this->V[$i_], 0, $i_); + $this->d = array_slice($this->V[$i_], 0, $this->n - 1); for ($j = 0; $j < $i; ++$j) { $this->V[$j][$i] = $this->V[$i][$j] = 0.0; } @@ -244,7 +244,8 @@ private function tred2(): void } $f = 0.0; - if ($h === 0 || $h < 1e-32) { + + if ($h == 0.0) { $h = 1e-32; } @@ -274,7 +275,6 @@ private function tred2(): void } // Accumulate transformations. - $j = 0; for ($i = 0; $i < $this->n - 1; ++$i) { $this->V[$this->n - 1][$i] = $this->V[$i][$i]; $this->V[$i][$i] = 1.0; @@ -302,7 +302,7 @@ private function tred2(): void } $this->d = $this->V[$this->n - 1]; - $this->V[$this->n - 1] = array_fill(0, $j, 0.0); + $this->V[$this->n - 1] = array_fill(0, $this->n, 0.0); $this->V[$this->n - 1][$this->n - 1] = 1.0; $this->e[0] = 0.0; } diff --git a/tests/Math/LinearAlgebra/EigenvalueDecompositionTest.php b/tests/Math/LinearAlgebra/EigenvalueDecompositionTest.php index a08ddc17..73018d03 100644 --- a/tests/Math/LinearAlgebra/EigenvalueDecompositionTest.php +++ b/tests/Math/LinearAlgebra/EigenvalueDecompositionTest.php @@ -10,34 +10,72 @@ class EigenvalueDecompositionTest extends TestCase { - public function testSymmetricMatrixEigenPairs(): void + public function testKnownSymmetricMatrixDecomposition(): void { - // Acceptable error - $epsilon = 0.001; - // First a simple example whose result is known and given in // http://www.cs.otago.ac.nz/cosc453/student_tutorials/principal_components.pdf $matrix = [ [0.616555556, 0.615444444], [0.614444444, 0.716555556], ]; - $knownEigvalues = [0.0490833989, 1.28402771]; - $knownEigvectors = [[-0.735178656, 0.677873399], [-0.677873399, -0.735178656]]; $decomp = new EigenvalueDecomposition($matrix); - $eigVectors = $decomp->getEigenvectors(); - $eigValues = $decomp->getRealEigenvalues(); - $this->assertEquals($knownEigvalues, $eigValues, '', $epsilon); - $this->assertEquals($knownEigvectors, $eigVectors, '', $epsilon); + self::assertEquals([0.0490833989, 1.28402771], $decomp->getRealEigenvalues(), '', 0.001); + self::assertEquals([ + [-0.735178656, 0.677873399], + [-0.677873399, -0.735178656], + ], $decomp->getEigenvectors(), '', 0.001); + } + + public function testMatrixWithAllZeroRow(): void + { + // http://www.wolframalpha.com/widgets/view.jsp?id=9aa01caf50c9307e9dabe159c9068c41 + $matrix = [ + [10, 0, 0], + [0, 6, 0], + [0, 0, 0], + ]; + + $decomp = new EigenvalueDecomposition($matrix); + + self::assertEquals([0.0, 6.0, 10.0], $decomp->getRealEigenvalues(), '', 0.0001); + self::assertEquals([ + [0, 0, 1], + [0, 1, 0], + [1, 0, 0], + ], $decomp->getEigenvectors(), '', 0.0001); + } + + public function testMatrixThatCauseErrorWithStrictComparision(): void + { + // http://www.wolframalpha.com/widgets/view.jsp?id=9aa01caf50c9307e9dabe159c9068c41 + $matrix = [ + [1, 0, 3], + [0, 1, 7], + [3, 7, 4], + ]; + + $decomp = new EigenvalueDecomposition($matrix); + + self::assertEquals([-5.2620873481, 1.0, 10.2620873481], $decomp->getRealEigenvalues(), '', 0.000001); + self::assertEquals([ + [-0.3042688, -0.709960552, 0.63511928], + [-0.9191450, 0.393919298, 0.0], + [0.25018574, 0.5837667, 0.7724140], + ], $decomp->getEigenvectors(), '', 0.0001); + } + + public function testRandomSymmetricMatrixEigenPairs(): void + { + // Acceptable error + $epsilon = 0.001; // Secondly, generate a symmetric square matrix // and test for A.v=λ.v - // // (We, for now, omit non-symmetric matrices whose eigenvalues can be complex numbers) $len = 3; + srand((int) microtime(true) * 1000); $A = array_fill(0, $len, array_fill(0, $len, 0.0)); - $seed = microtime(true) * 1000; - srand((int) $seed); for ($i = 0; $i < $len; ++$i) { for ($k = 0; $k < $len; ++$k) { if ($i > $k) { @@ -60,7 +98,7 @@ public function testSymmetricMatrixEigenPairs(): void $leftSide = $m1->multiply($m2)->toArray(); $rightSide = $m2->multiplyByScalar($lambda)->toArray(); - $this->assertEquals($leftSide, $rightSide, '', $epsilon); + self::assertEquals($leftSide, $rightSide, '', $epsilon); } } } From 8aed3b928661237b90c101f97ec0306e2f00c2c7 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 18 Feb 2018 00:11:54 +0100 Subject: [PATCH 254/328] Prepare CHANGELOG for next fix release --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5491e033..ac42d01b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ CHANGELOG This changelog references the relevant changes done in PHP-ML library. +* 0.6.1 (2018-02-18) + * Fix KMeans and EigenvalueDecomposition (#235) + * 0.6.0 (2018-02-16) * feature [FeatureSelection] implement SelectKBest with scoring functions (#232) * feature [FeatureSelection] implement VarianceThreshold - simple baseline approach to feature selection. (#228) From add00c6108677056d4269053bf7d8cf3bd130289 Mon Sep 17 00:00:00 2001 From: Yuji Uchiyama Date: Fri, 23 Feb 2018 01:02:55 +0900 Subject: [PATCH 255/328] Fix apriori keys (#238) * Add test to check keys of rules * Reindex after array_filter/array_unique in Apriori --- src/Association/Apriori.php | 6 +++--- tests/Association/AprioriTest.php | 12 ++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Association/Apriori.php b/src/Association/Apriori.php index c3f9c912..abbdf470 100644 --- a/src/Association/Apriori.php +++ b/src/Association/Apriori.php @@ -212,9 +212,9 @@ private function items(): array */ private function frequent(array $samples): array { - return array_filter($samples, function ($entry) { + return array_values(array_filter($samples, function ($entry) { return $this->support($entry) >= $this->support; - }); + })); } /** @@ -234,7 +234,7 @@ private function candidates(array $samples): array continue; } - $candidate = array_unique(array_merge($p, $q)); + $candidate = array_values(array_unique(array_merge($p, $q))); if ($this->contains($candidates, $candidate)) { continue; diff --git a/tests/Association/AprioriTest.php b/tests/Association/AprioriTest.php index 5ed4f8c0..81a6ce68 100644 --- a/tests/Association/AprioriTest.php +++ b/tests/Association/AprioriTest.php @@ -101,6 +101,18 @@ public function testAprioriSingleItem(): void $this->assertEquals([['a']], $L[1]); } + public function testAprioriL3(): void + { + $sample = [['a', 'b', 'c']]; + + $apriori = new Apriori(0, 0); + $apriori->train($sample, []); + + $L = $apriori->apriori(); + + $this->assertEquals([['a', 'b', 'c']], $L[3]); + } + public function testGetRules(): void { $apriori = new Apriori(0.4, 0.8); From 83f3e8de7021d58297f5357cfeb293a347a2fa39 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 22 Feb 2018 17:06:31 +0100 Subject: [PATCH 256/328] Update CHANGELOG with #238 fix --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac42d01b..61ecd632 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ CHANGELOG This changelog references the relevant changes done in PHP-ML library. +* 0.6.2 (2018-02-22) + * Fix Apriori array keys (#238) + * 0.6.1 (2018-02-18) * Fix KMeans and EigenvalueDecomposition (#235) From a96f03e8ddf1f4caf1a99fd14a8f00785b9ce5df Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 23 Feb 2018 23:05:46 +0100 Subject: [PATCH 257/328] Fix Optimizer initial theta randomization (#239) * Fix Optimizer initial theta randomization * Add more tests for LUDecomposition and FuzzyCMeans --- src/Helper/Optimizer/Optimizer.php | 17 ++++----- src/Helper/Optimizer/StochasticGD.php | 12 ++++++ src/Math/LinearAlgebra/LUDecomposition.php | 15 +------- tests/Clustering/FuzzyCMeansTest.php | 16 ++++++++ .../Optimizer/ConjugateGradientTest.php | 37 +++++++++++++++++++ .../LinearAlgebra/LUDecompositionTest.php | 31 ++++++++++++++++ 6 files changed, 105 insertions(+), 23 deletions(-) create mode 100644 tests/Math/LinearAlgebra/LUDecompositionTest.php diff --git a/src/Helper/Optimizer/Optimizer.php b/src/Helper/Optimizer/Optimizer.php index 7ef317cc..9bac3bef 100644 --- a/src/Helper/Optimizer/Optimizer.php +++ b/src/Helper/Optimizer/Optimizer.php @@ -5,10 +5,12 @@ namespace Phpml\Helper\Optimizer; use Closure; -use Exception; +use Phpml\Exception\InvalidArgumentException; abstract class Optimizer { + public $initialTheta; + /** * Unknown variables to be found * @@ -33,21 +35,16 @@ public function __construct(int $dimensions) // Inits the weights randomly $this->theta = []; for ($i = 0; $i < $this->dimensions; ++$i) { - $this->theta[] = random_int(0, getrandmax()) / (float) getrandmax(); + $this->theta[] = (random_int(0, PHP_INT_MAX) / PHP_INT_MAX) + 0.1; } + + $this->initialTheta = $this->theta; } - /** - * Sets the weights manually - * - * @return $this - * - * @throws \Exception - */ public function setInitialTheta(array $theta) { if (count($theta) != $this->dimensions) { - throw new Exception("Number of values in the weights array should be ${this}->dimensions"); + throw new InvalidArgumentException(sprintf('Number of values in the weights array should be %s', $this->dimensions)); } $this->theta = $theta; diff --git a/src/Helper/Optimizer/StochasticGD.php b/src/Helper/Optimizer/StochasticGD.php index 18a5f0ca..e1cbeea1 100644 --- a/src/Helper/Optimizer/StochasticGD.php +++ b/src/Helper/Optimizer/StochasticGD.php @@ -5,6 +5,7 @@ namespace Phpml\Helper\Optimizer; use Closure; +use Phpml\Exception\InvalidArgumentException; /** * Stochastic Gradient Descent optimization method @@ -88,6 +89,17 @@ public function __construct(int $dimensions) $this->dimensions = $dimensions; } + public function setInitialTheta(array $theta) + { + if (count($theta) != $this->dimensions + 1) { + throw new InvalidArgumentException(sprintf('Number of values in the weights array should be %s', $this->dimensions + 1)); + } + + $this->theta = $theta; + + return $this; + } + /** * Sets minimum value for the change in the theta values * between iterations to continue the iterations.
diff --git a/src/Math/LinearAlgebra/LUDecomposition.php b/src/Math/LinearAlgebra/LUDecomposition.php index 151e2ccd..1594837a 100644 --- a/src/Math/LinearAlgebra/LUDecomposition.php +++ b/src/Math/LinearAlgebra/LUDecomposition.php @@ -225,25 +225,14 @@ public function isNonsingular(): bool return true; } - /** - * Count determinants - * - * @return float|int d matrix determinant - * - * @throws MatrixException - */ - public function det() + public function det(): float { - if ($this->m !== $this->n) { - throw MatrixException::notSquareMatrix(); - } - $d = $this->pivsign; for ($j = 0; $j < $this->n; ++$j) { $d *= $this->LU[$j][$j]; } - return $d; + return (float) $d; } /** diff --git a/tests/Clustering/FuzzyCMeansTest.php b/tests/Clustering/FuzzyCMeansTest.php index 72454816..b2005a1d 100644 --- a/tests/Clustering/FuzzyCMeansTest.php +++ b/tests/Clustering/FuzzyCMeansTest.php @@ -5,6 +5,7 @@ namespace Phpml\Tests\Clustering; use Phpml\Clustering\FuzzyCMeans; +use Phpml\Exception\InvalidArgumentException; use PHPUnit\Framework\TestCase; class FuzzyCMeansTest extends TestCase @@ -45,4 +46,19 @@ public function testMembershipMatrix(): void $this->assertEquals(1, array_sum($col)); } } + + /** + * @dataProvider invalidClusterNumberProvider + */ + public function testInvalidClusterNumber(int $clusters): void + { + $this->expectException(InvalidArgumentException::class); + + new FuzzyCMeans($clusters); + } + + public function invalidClusterNumberProvider(): array + { + return [[0], [-1]]; + } } diff --git a/tests/Helper/Optimizer/ConjugateGradientTest.php b/tests/Helper/Optimizer/ConjugateGradientTest.php index b05f998f..78eb7182 100644 --- a/tests/Helper/Optimizer/ConjugateGradientTest.php +++ b/tests/Helper/Optimizer/ConjugateGradientTest.php @@ -4,6 +4,7 @@ namespace Phpml\Tests\Helper\Optimizer; +use Phpml\Exception\InvalidArgumentException; use Phpml\Helper\Optimizer\ConjugateGradient; use PHPUnit\Framework\TestCase; @@ -35,6 +36,34 @@ public function testRunOptimization(): void $this->assertEquals([-1, 2], $theta, '', 0.1); } + public function testRunOptimizationWithCustomInitialTheta(): void + { + // 200 samples from y = -1 + 2x (i.e. theta = [-1, 2]) + $samples = []; + $targets = []; + for ($i = -100; $i <= 100; ++$i) { + $x = $i / 100; + $samples[] = [$x]; + $targets[] = -1 + 2 * $x; + } + + $callback = function ($theta, $sample, $target) { + $y = $theta[0] + $theta[1] * $sample[0]; + $cost = ($y - $target) ** 2 / 2; + $grad = $y - $target; + + return [$cost, $grad]; + }; + + $optimizer = new ConjugateGradient(1); + // set very weak theta to trigger very bad result + $optimizer->setInitialTheta([0.0000001, 0.0000001]); + + $theta = $optimizer->runOptimization($samples, $targets, $callback); + + $this->assertEquals([-1.087708, 2.212034], $theta, '', 0.000001); + } + public function testRunOptimization2Dim(): void { // 100 samples from y = -1 + 2x0 - 3x1 (i.e. theta = [-1, 2, -3]) @@ -62,4 +91,12 @@ public function testRunOptimization2Dim(): void $this->assertEquals([-1, 2, -3], $theta, '', 0.1); } + + public function testThrowExceptionOnInvalidTheta(): void + { + $opimizer = new ConjugateGradient(2); + + $this->expectException(InvalidArgumentException::class); + $opimizer->setInitialTheta([0.15]); + } } diff --git a/tests/Math/LinearAlgebra/LUDecompositionTest.php b/tests/Math/LinearAlgebra/LUDecompositionTest.php new file mode 100644 index 00000000..b678673f --- /dev/null +++ b/tests/Math/LinearAlgebra/LUDecompositionTest.php @@ -0,0 +1,31 @@ +expectException(MatrixException::class); + + new LUDecomposition(new Matrix([1, 2, 3, 4, 5])); + } + + public function testSolveWithInvalidMatrix(): void + { + $this->expectException(MatrixException::class); + + $lu = new LUDecomposition(new Matrix([[1, 2], [3, 4]])); + $lu->solve(new Matrix([1, 2, 3])); + } +} From 4562f1dfc95ea2643ba53186d1364e71591ae719 Mon Sep 17 00:00:00 2001 From: Yuji Uchiyama Date: Sat, 24 Feb 2018 19:17:35 +0900 Subject: [PATCH 258/328] Add a SvmDataset class for SVM-Light (or LibSVM) format files (#237) * Add data loader for svm format * Add tests for error cases * Set proper exception messages * Add documents * Add error checking code for invalid column format * Add missing documents --- README.md | 1 + docs/index.md | 7 +- docs/machine-learning/datasets/svm-dataset.md | 13 ++ mkdocs.yml | 1 + src/Dataset/SvmDataset.php | 130 +++++++++++ src/Exception/DatasetException.php | 15 ++ tests/Dataset/Resources/svm/1x1.svm | 1 + tests/Dataset/Resources/svm/3x1.svm | 3 + tests/Dataset/Resources/svm/3x4.svm | 3 + tests/Dataset/Resources/svm/comments.svm | 2 + tests/Dataset/Resources/svm/empty.svm | 0 .../Dataset/Resources/svm/err_empty_line.svm | 3 + .../Dataset/Resources/svm/err_index_zero.svm | 1 + .../Resources/svm/err_invalid_feature.svm | 1 + .../Resources/svm/err_invalid_spaces.svm | 1 + .../Resources/svm/err_invalid_value.svm | 1 + tests/Dataset/Resources/svm/err_no_labels.svm | 1 + .../Resources/svm/err_string_index.svm | 1 + .../Resources/svm/err_string_labels.svm | 1 + tests/Dataset/Resources/svm/sparse.svm | 2 + tests/Dataset/Resources/svm/tabs.svm | 1 + tests/Dataset/SvmDatasetTest.php | 212 ++++++++++++++++++ 22 files changed, 398 insertions(+), 3 deletions(-) create mode 100644 docs/machine-learning/datasets/svm-dataset.md create mode 100644 src/Dataset/SvmDataset.php create mode 100644 tests/Dataset/Resources/svm/1x1.svm create mode 100644 tests/Dataset/Resources/svm/3x1.svm create mode 100644 tests/Dataset/Resources/svm/3x4.svm create mode 100644 tests/Dataset/Resources/svm/comments.svm create mode 100644 tests/Dataset/Resources/svm/empty.svm create mode 100644 tests/Dataset/Resources/svm/err_empty_line.svm create mode 100644 tests/Dataset/Resources/svm/err_index_zero.svm create mode 100644 tests/Dataset/Resources/svm/err_invalid_feature.svm create mode 100644 tests/Dataset/Resources/svm/err_invalid_spaces.svm create mode 100644 tests/Dataset/Resources/svm/err_invalid_value.svm create mode 100644 tests/Dataset/Resources/svm/err_no_labels.svm create mode 100644 tests/Dataset/Resources/svm/err_string_index.svm create mode 100644 tests/Dataset/Resources/svm/err_string_labels.svm create mode 100644 tests/Dataset/Resources/svm/sparse.svm create mode 100644 tests/Dataset/Resources/svm/tabs.svm create mode 100644 tests/Dataset/SvmDatasetTest.php diff --git a/README.md b/README.md index c5d788eb..ddca60a1 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,7 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * [Array](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/array-dataset/) * [CSV](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/csv-dataset/) * [Files](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/files-dataset/) + * [SVM](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/svm-dataset/) * Ready to use: * [Iris](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/demo/iris/) * [Wine](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/demo/wine/) diff --git a/docs/index.md b/docs/index.md index 14cfba5c..2e204e87 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,7 +12,7 @@
- + ![PHP-ML - Machine Learning library for PHP](assets/php-ml-logo.png) Fresh approach to Machine Learning in PHP. Algorithms, Cross Validation, Neural Network, Preprocessing, Feature Extraction and much more in one library. @@ -31,7 +31,7 @@ $labels = ['a', 'a', 'a', 'b', 'b', 'b']; $classifier = new KNearestNeighbors(); $classifier->train($samples, $labels); -$classifier->predict([3, 2]); +$classifier->predict([3, 2]); // return 'b' ``` @@ -89,6 +89,7 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * [Array](machine-learning/datasets/array-dataset.md) * [CSV](machine-learning/datasets/csv-dataset.md) * [Files](machine-learning/datasets/files-dataset.md) + * [SVM](machine-learning/datasets/svm-dataset.md) * Ready to use: * [Iris](machine-learning/datasets/demo/iris.md) * [Wine](machine-learning/datasets/demo/wine.md) @@ -100,7 +101,7 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * [Matrix](math/matrix.md) * [Set](math/set.md) * [Statistic](math/statistic.md) - + ## Contribute diff --git a/docs/machine-learning/datasets/svm-dataset.md b/docs/machine-learning/datasets/svm-dataset.md new file mode 100644 index 00000000..8ac1c268 --- /dev/null +++ b/docs/machine-learning/datasets/svm-dataset.md @@ -0,0 +1,13 @@ +# SvmDataset + +Helper class that loads data from SVM-Light format file. It extends the `ArrayDataset`. + +### Constructors Parameters + +* $filepath - (string) path to the file + +``` +$dataset = new SvmDataset('dataset.svm'); +``` + +See [ArrayDataset](array-dataset.md) for more information. diff --git a/mkdocs.yml b/mkdocs.yml index 92f38375..490e5dc0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -38,6 +38,7 @@ pages: - Array Dataset: machine-learning/datasets/array-dataset.md - CSV Dataset: machine-learning/datasets/csv-dataset.md - Files Dataset: machine-learning/datasets/files-dataset.md + - SVM Dataset: machine-learning/datasets/svm-dataset.md - Ready to use datasets: - Iris: machine-learning/datasets/demo/iris.md - Wine: machine-learning/datasets/demo/wine.md diff --git a/src/Dataset/SvmDataset.php b/src/Dataset/SvmDataset.php new file mode 100644 index 00000000..8bd172b5 --- /dev/null +++ b/src/Dataset/SvmDataset.php @@ -0,0 +1,130 @@ + $maxIndex) { + $maxIndex = $index; + $sample = array_pad($sample, $maxIndex + 1, 0); + } + + $sample[$index] = $value; + } + + return [$sample, $target, $maxIndex]; + } + + private static function parseLine(string $line): array + { + $line = explode('#', $line, 2)[0]; + $line = rtrim($line); + $line = str_replace("\t", ' ', $line); + + $columns = explode(' ', $line); + + return $columns; + } + + private static function parseTargetColumn(string $column): float + { + if (!is_numeric($column)) { + throw DatasetException::invalidTarget($column); + } + + return (float) $column; + } + + private static function parseFeatureColumn(string $column): array + { + $feature = explode(':', $column, 2); + if (count($feature) != 2) { + throw DatasetException::invalidValue($column); + } + + $index = self::parseFeatureIndex($feature[0]); + $value = self::parseFeatureValue($feature[1]); + + return [$index, $value]; + } + + private static function parseFeatureIndex(string $index): int + { + if (!is_numeric($index) || !ctype_digit($index)) { + throw DatasetException::invalidIndex($index); + } + + if ((int) $index < 1) { + throw DatasetException::invalidIndex($index); + } + + return (int) $index - 1; + } + + private static function parseFeatureValue(string $value): float + { + if (!is_numeric($value)) { + throw DatasetException::invalidValue($value); + } + + return (float) $value; + } +} diff --git a/src/Exception/DatasetException.php b/src/Exception/DatasetException.php index 8d6d5da6..1cb0bfc4 100644 --- a/src/Exception/DatasetException.php +++ b/src/Exception/DatasetException.php @@ -12,4 +12,19 @@ public static function missingFolder(string $path): self { return new self(sprintf('Dataset root folder "%s" missing.', $path)); } + + public static function invalidTarget(string $target): self + { + return new self(sprintf('Invalid target "%s".', $target)); + } + + public static function invalidIndex(string $index): self + { + return new self(sprintf('Invalid index "%s".', $index)); + } + + public static function invalidValue(string $value): self + { + return new self(sprintf('Invalid value "%s".', $value)); + } } diff --git a/tests/Dataset/Resources/svm/1x1.svm b/tests/Dataset/Resources/svm/1x1.svm new file mode 100644 index 00000000..fdd6c1fe --- /dev/null +++ b/tests/Dataset/Resources/svm/1x1.svm @@ -0,0 +1 @@ +0 1:2.3 diff --git a/tests/Dataset/Resources/svm/3x1.svm b/tests/Dataset/Resources/svm/3x1.svm new file mode 100644 index 00000000..d817c96f --- /dev/null +++ b/tests/Dataset/Resources/svm/3x1.svm @@ -0,0 +1,3 @@ +1 1:2.3 +0 1:4.56 +1 1:78.9 diff --git a/tests/Dataset/Resources/svm/3x4.svm b/tests/Dataset/Resources/svm/3x4.svm new file mode 100644 index 00000000..5f6d015e --- /dev/null +++ b/tests/Dataset/Resources/svm/3x4.svm @@ -0,0 +1,3 @@ +1 1:2 2:4 3:6 4:8 +2 1:3 2:5 3:7 4:9 +0 1:1.2 2:3.4 3:5.6 4:7.8 diff --git a/tests/Dataset/Resources/svm/comments.svm b/tests/Dataset/Resources/svm/comments.svm new file mode 100644 index 00000000..7cf6fc44 --- /dev/null +++ b/tests/Dataset/Resources/svm/comments.svm @@ -0,0 +1,2 @@ +0 1:2 # This is a comment. +1 1:34 # This # is # : # also # a # comment # . diff --git a/tests/Dataset/Resources/svm/empty.svm b/tests/Dataset/Resources/svm/empty.svm new file mode 100644 index 00000000..e69de29b diff --git a/tests/Dataset/Resources/svm/err_empty_line.svm b/tests/Dataset/Resources/svm/err_empty_line.svm new file mode 100644 index 00000000..289e2b52 --- /dev/null +++ b/tests/Dataset/Resources/svm/err_empty_line.svm @@ -0,0 +1,3 @@ +1 1:2.3 + +0 1:4.56 diff --git a/tests/Dataset/Resources/svm/err_index_zero.svm b/tests/Dataset/Resources/svm/err_index_zero.svm new file mode 100644 index 00000000..56c20f87 --- /dev/null +++ b/tests/Dataset/Resources/svm/err_index_zero.svm @@ -0,0 +1 @@ +0 0:2.3 diff --git a/tests/Dataset/Resources/svm/err_invalid_feature.svm b/tests/Dataset/Resources/svm/err_invalid_feature.svm new file mode 100644 index 00000000..f57b6c5d --- /dev/null +++ b/tests/Dataset/Resources/svm/err_invalid_feature.svm @@ -0,0 +1 @@ +0 12345 diff --git a/tests/Dataset/Resources/svm/err_invalid_spaces.svm b/tests/Dataset/Resources/svm/err_invalid_spaces.svm new file mode 100644 index 00000000..77ff868c --- /dev/null +++ b/tests/Dataset/Resources/svm/err_invalid_spaces.svm @@ -0,0 +1 @@ + 0 1:2.3 diff --git a/tests/Dataset/Resources/svm/err_invalid_value.svm b/tests/Dataset/Resources/svm/err_invalid_value.svm new file mode 100644 index 00000000..b358890e --- /dev/null +++ b/tests/Dataset/Resources/svm/err_invalid_value.svm @@ -0,0 +1 @@ +0 1:xyz diff --git a/tests/Dataset/Resources/svm/err_no_labels.svm b/tests/Dataset/Resources/svm/err_no_labels.svm new file mode 100644 index 00000000..789be384 --- /dev/null +++ b/tests/Dataset/Resources/svm/err_no_labels.svm @@ -0,0 +1 @@ +1:2.3 diff --git a/tests/Dataset/Resources/svm/err_string_index.svm b/tests/Dataset/Resources/svm/err_string_index.svm new file mode 100644 index 00000000..25cb2968 --- /dev/null +++ b/tests/Dataset/Resources/svm/err_string_index.svm @@ -0,0 +1 @@ +0 x:2.3 diff --git a/tests/Dataset/Resources/svm/err_string_labels.svm b/tests/Dataset/Resources/svm/err_string_labels.svm new file mode 100644 index 00000000..8cc16f7e --- /dev/null +++ b/tests/Dataset/Resources/svm/err_string_labels.svm @@ -0,0 +1 @@ +A 1:2.3 diff --git a/tests/Dataset/Resources/svm/sparse.svm b/tests/Dataset/Resources/svm/sparse.svm new file mode 100644 index 00000000..23d7485c --- /dev/null +++ b/tests/Dataset/Resources/svm/sparse.svm @@ -0,0 +1,2 @@ +0 2:3.45 +1 5:6.789 diff --git a/tests/Dataset/Resources/svm/tabs.svm b/tests/Dataset/Resources/svm/tabs.svm new file mode 100644 index 00000000..bf8757f4 --- /dev/null +++ b/tests/Dataset/Resources/svm/tabs.svm @@ -0,0 +1 @@ +1 1:23 2:45 # comments diff --git a/tests/Dataset/SvmDatasetTest.php b/tests/Dataset/SvmDatasetTest.php new file mode 100644 index 00000000..884da3a5 --- /dev/null +++ b/tests/Dataset/SvmDatasetTest.php @@ -0,0 +1,212 @@ +assertEquals($expectedSamples, $dataset->getSamples()); + $this->assertEquals($expectedTargets, $dataset->getTargets()); + } + + public function testSvmDataset1x1(): void + { + $filePath = self::getFilePath('1x1.svm'); + $dataset = new SvmDataset($filePath); + + $expectedSamples = [ + [2.3], + ]; + $expectedTargets = [ + 0, + ]; + + $this->assertEquals($expectedSamples, $dataset->getSamples()); + $this->assertEquals($expectedTargets, $dataset->getTargets()); + } + + public function testSvmDataset3x1(): void + { + $filePath = self::getFilePath('3x1.svm'); + $dataset = new SvmDataset($filePath); + + $expectedSamples = [ + [2.3], + [4.56], + [78.9], + ]; + $expectedTargets = [ + 1, + 0, + 1, + ]; + + $this->assertEquals($expectedSamples, $dataset->getSamples()); + $this->assertEquals($expectedTargets, $dataset->getTargets()); + } + + public function testSvmDataset3x4(): void + { + $filePath = self::getFilePath('3x4.svm'); + $dataset = new SvmDataset($filePath); + + $expectedSamples = [ + [2, 4, 6, 8], + [3, 5, 7, 9], + [1.2, 3.4, 5.6, 7.8], + ]; + $expectedTargets = [ + 1, + 2, + 0, + ]; + + $this->assertEquals($expectedSamples, $dataset->getSamples()); + $this->assertEquals($expectedTargets, $dataset->getTargets()); + } + + public function testSvmDatasetSparse(): void + { + $filePath = self::getFilePath('sparse.svm'); + $dataset = new SvmDataset($filePath); + + $expectedSamples = [ + [0, 3.45, 0, 0, 0], + [0, 0, 0, 0, 6.789], + ]; + $expectedTargets = [ + 0, + 1, + ]; + + $this->assertEquals($expectedSamples, $dataset->getSamples()); + $this->assertEquals($expectedTargets, $dataset->getTargets()); + } + + public function testSvmDatasetComments(): void + { + $filePath = self::getFilePath('comments.svm'); + $dataset = new SvmDataset($filePath); + + $expectedSamples = [ + [2], + [34], + ]; + $expectedTargets = [ + 0, + 1, + ]; + + $this->assertEquals($expectedSamples, $dataset->getSamples()); + $this->assertEquals($expectedTargets, $dataset->getTargets()); + } + + public function testSvmDatasetTabs(): void + { + $filePath = self::getFilePath('tabs.svm'); + $dataset = new SvmDataset($filePath); + + $expectedSamples = [ + [23, 45], + ]; + $expectedTargets = [ + 1, + ]; + + $this->assertEquals($expectedSamples, $dataset->getSamples()); + $this->assertEquals($expectedTargets, $dataset->getTargets()); + } + + public function testSvmDatasetMissingFile(): void + { + $this->expectException(FileException::class); + + $filePath = self::getFilePath('err_file_not_exists.svm'); + $dataset = new SvmDataset($filePath); + } + + public function testSvmDatasetEmptyLine(): void + { + $this->expectException(DatasetException::class); + + $filePath = self::getFilePath('err_empty_line.svm'); + $dataset = new SvmDataset($filePath); + } + + public function testSvmDatasetNoLabels(): void + { + $this->expectException(DatasetException::class); + + $filePath = self::getFilePath('err_no_labels.svm'); + $dataset = new SvmDataset($filePath); + } + + public function testSvmDatasetStringLabels(): void + { + $this->expectException(DatasetException::class); + + $filePath = self::getFilePath('err_string_labels.svm'); + $dataset = new SvmDataset($filePath); + } + + public function testSvmDatasetInvalidSpaces(): void + { + $this->expectException(DatasetException::class); + + $filePath = self::getFilePath('err_invalid_spaces.svm'); + $dataset = new SvmDataset($filePath); + } + + public function testSvmDatasetStringIndex(): void + { + $this->expectException(DatasetException::class); + + $filePath = self::getFilePath('err_string_index.svm'); + $dataset = new SvmDataset($filePath); + } + + public function testSvmDatasetIndexZero(): void + { + $this->expectException(DatasetException::class); + + $filePath = self::getFilePath('err_index_zero.svm'); + $dataset = new SvmDataset($filePath); + } + + public function testSvmDatasetInvalidValue(): void + { + $this->expectException(DatasetException::class); + + $filePath = self::getFilePath('err_invalid_value.svm'); + $dataset = new SvmDataset($filePath); + } + + public function testSvmDatasetInvalidFeature(): void + { + $this->expectException(DatasetException::class); + + $filePath = self::getFilePath('err_invalid_feature.svm'); + $dataset = new SvmDataset($filePath); + } + + private static function getFilePath(string $baseName): string + { + return dirname(__FILE__).'/Resources/svm/'.$baseName; + } +} From 9e375ca5443f07d1bab822676623ba0cfb593128 Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Sun, 25 Feb 2018 22:56:36 +0100 Subject: [PATCH 259/328] Ensure DataTransformer::testSet samples array is not empty (#204) --- src/SupportVectorMachine/DataTransformer.php | 6 ++++++ tests/SupportVectorMachine/DataTransformerTest.php | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/src/SupportVectorMachine/DataTransformer.php b/src/SupportVectorMachine/DataTransformer.php index 0a99aa38..e1aeb7de 100644 --- a/src/SupportVectorMachine/DataTransformer.php +++ b/src/SupportVectorMachine/DataTransformer.php @@ -4,6 +4,8 @@ namespace Phpml\SupportVectorMachine; +use Phpml\Exception\InvalidArgumentException; + class DataTransformer { public static function trainingSet(array $samples, array $labels, bool $targets = false): string @@ -24,6 +26,10 @@ public static function trainingSet(array $samples, array $labels, bool $targets public static function testSet(array $samples): string { + if (empty($samples)) { + throw InvalidArgumentException::arrayCantBeEmpty(); + } + if (!is_array($samples[0])) { $samples = [$samples]; } diff --git a/tests/SupportVectorMachine/DataTransformerTest.php b/tests/SupportVectorMachine/DataTransformerTest.php index 79dcb494..75c23d6c 100644 --- a/tests/SupportVectorMachine/DataTransformerTest.php +++ b/tests/SupportVectorMachine/DataTransformerTest.php @@ -4,6 +4,7 @@ namespace Phpml\Tests\SupportVectorMachine; +use Phpml\Exception\InvalidArgumentException; use Phpml\SupportVectorMachine\DataTransformer; use PHPUnit\Framework\TestCase; @@ -78,4 +79,12 @@ public function testProbabilities(): void $this->assertEquals($probabilities, DataTransformer::probabilities($rawPredictions, $labels)); } + + public function testThrowExceptionWhenTestSetIsEmpty(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The array has zero elements'); + + DataTransformer::testSet([]); + } } From d188790276602eb3a91d27f05b872b118cee5f0b Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Mon, 26 Feb 2018 00:02:04 +0100 Subject: [PATCH 260/328] Add MLP partial train test after restore from file (#243) --- tests/Classification/MLPClassifierTest.php | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/Classification/MLPClassifierTest.php b/tests/Classification/MLPClassifierTest.php index c4c45c49..d3680b60 100644 --- a/tests/Classification/MLPClassifierTest.php +++ b/tests/Classification/MLPClassifierTest.php @@ -193,6 +193,35 @@ public function testSaveAndRestore(): void $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); } + public function testSaveAndRestoreWithPartialTraining(): void + { + $network = new MLPClassifier(2, [2], ['a', 'b'], 1000); + $network->partialTrain( + [[1, 0], [0, 1]], + ['a', 'b'] + ); + + $this->assertEquals('a', $network->predict([1, 0])); + $this->assertEquals('b', $network->predict([0, 1])); + + $filename = 'perceptron-test-'.random_int(100, 999).'-'.uniqid(); + $filepath = tempnam(sys_get_temp_dir(), $filename); + $modelManager = new ModelManager(); + $modelManager->saveToFile($network, $filepath); + + /** @var MLPClassifier $restoredNetwork */ + $restoredNetwork = $modelManager->restoreFromFile($filepath); + $restoredNetwork->partialTrain( + [[1, 1], [0, 0]], + ['a', 'b'] + ); + + $this->assertEquals('a', $restoredNetwork->predict([1, 0])); + $this->assertEquals('b', $restoredNetwork->predict([0, 1])); + $this->assertEquals('a', $restoredNetwork->predict([1, 1])); + $this->assertEquals('b', $restoredNetwork->predict([0, 0])); + } + public function testThrowExceptionOnInvalidLayersNumber(): void { $this->expectException(InvalidArgumentException::class); From 9c195559df60beaf9486cbcd5ea5fd254721f8f4 Mon Sep 17 00:00:00 2001 From: Yuji Uchiyama Date: Wed, 28 Feb 2018 02:50:07 +0900 Subject: [PATCH 261/328] Update apriori documentation (#245) * Fix a wrong word * More precise description about support and confidence --- docs/machine-learning/association/apriori.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/machine-learning/association/apriori.md b/docs/machine-learning/association/apriori.md index 6f597bea..bbf829ba 100644 --- a/docs/machine-learning/association/apriori.md +++ b/docs/machine-learning/association/apriori.md @@ -4,8 +4,8 @@ Association rule learning based on [Apriori algorithm](https://en.wikipedia.org/ ### Constructor Parameters -* $support - [confidence](https://en.wikipedia.org/wiki/Association_rule_learning#Support), minimum relative amount of frequent item set in train sample -* $confidence - [confidence](https://en.wikipedia.org/wiki/Association_rule_learning#Confidence), minimum relative amount of item set in frequent item sets +* $support - minimum threshold of [support](https://en.wikipedia.org/wiki/Association_rule_learning#Support), i.e. the ratio of samples which contain both X and Y for a rule "if X then Y" +* $confidence - minimum threshold of [confidence](https://en.wikipedia.org/wiki/Association_rule_learning#Confidence), i.e. the ratio of samples containing both X and Y to those containing X ``` use Phpml\Association\Apriori; @@ -44,7 +44,7 @@ $associator->predict([['alpha','epsilon'],['beta','theta']]); ### Associating Get generated association rules simply use `rules` method. - + ``` $associator->getRules(); // return [['antecedent' => ['alpha', 'theta'], 'consequent' => ['beta'], 'support' => 1.0, 'confidence' => 1.0], ... ] From af9ccfe722f26c48307776842003ec2b1659fca0 Mon Sep 17 00:00:00 2001 From: Yuji Uchiyama Date: Sat, 3 Mar 2018 19:19:58 +0900 Subject: [PATCH 262/328] Add tests for LogisticRegression (#248) --- .../Linear/LogisticRegressionTest.php | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/tests/Classification/Linear/LogisticRegressionTest.php b/tests/Classification/Linear/LogisticRegressionTest.php index f60d3080..ed9b878d 100644 --- a/tests/Classification/Linear/LogisticRegressionTest.php +++ b/tests/Classification/Linear/LogisticRegressionTest.php @@ -8,9 +8,49 @@ use PHPUnit\Framework\TestCase; use ReflectionMethod; use ReflectionProperty; +use Throwable; class LogisticRegressionTest extends TestCase { + public function testConstructorThrowWhenInvalidTrainingType(): void + { + $this->expectException(Throwable::class); + + $classifier = new LogisticRegression( + 500, + true, + -1, + 'log', + 'L2' + ); + } + + public function testConstructorThrowWhenInvalidCost(): void + { + $this->expectException(Throwable::class); + + $classifier = new LogisticRegression( + 500, + true, + LogisticRegression::CONJUGATE_GRAD_TRAINING, + 'invalid', + 'L2' + ); + } + + public function testConstructorThrowWhenInvalidPenalty(): void + { + $this->expectException(Throwable::class); + + $classifier = new LogisticRegression( + 500, + true, + LogisticRegression::CONJUGATE_GRAD_TRAINING, + 'log', + 'invalid' + ); + } + public function testPredictSingleSample(): void { // AND problem @@ -22,6 +62,76 @@ public function testPredictSingleSample(): void $this->assertEquals(1, $classifier->predict([0.9, 0.9])); } + public function testPredictSingleSampleWithBatchTraining(): void + { + $samples = [[0, 0], [1, 0], [0, 1], [1, 1], [0.4, 0.4], [0.6, 0.6]]; + $targets = [0, 0, 0, 1, 0, 1]; + + // $maxIterations is set to 10000 as batch training needs more + // iteration to converge than CG method in general. + $classifier = new LogisticRegression( + 10000, + true, + LogisticRegression::BATCH_TRAINING, + 'log', + 'L2' + ); + $classifier->train($samples, $targets); + $this->assertEquals(0, $classifier->predict([0.1, 0.1])); + $this->assertEquals(1, $classifier->predict([0.9, 0.9])); + } + + public function testPredictSingleSampleWithOnlineTraining(): void + { + $samples = [[0, 0], [1, 0], [0, 1], [1, 1], [0.4, 0.4], [0.6, 0.6]]; + $targets = [0, 0, 0, 1, 0, 1]; + + // $penalty is set to empty (no penalty) because L2 penalty seems to + // prevent convergence in online training for this dataset. + $classifier = new LogisticRegression( + 10000, + true, + LogisticRegression::ONLINE_TRAINING, + 'log', + '' + ); + $classifier->train($samples, $targets); + $this->assertEquals(0, $classifier->predict([0.1, 0.1])); + $this->assertEquals(1, $classifier->predict([0.9, 0.9])); + } + + public function testPredictSingleSampleWithSSECost(): void + { + $samples = [[0, 0], [1, 0], [0, 1], [1, 1], [0.4, 0.4], [0.6, 0.6]]; + $targets = [0, 0, 0, 1, 0, 1]; + $classifier = new LogisticRegression( + 500, + true, + LogisticRegression::CONJUGATE_GRAD_TRAINING, + 'sse', + 'L2' + ); + $classifier->train($samples, $targets); + $this->assertEquals(0, $classifier->predict([0.1, 0.1])); + $this->assertEquals(1, $classifier->predict([0.9, 0.9])); + } + + public function testPredictSingleSampleWithoutPenalty(): void + { + $samples = [[0, 0], [1, 0], [0, 1], [1, 1], [0.4, 0.4], [0.6, 0.6]]; + $targets = [0, 0, 0, 1, 0, 1]; + $classifier = new LogisticRegression( + 500, + true, + LogisticRegression::CONJUGATE_GRAD_TRAINING, + 'log', + '' + ); + $classifier->train($samples, $targets); + $this->assertEquals(0, $classifier->predict([0.1, 0.1])); + $this->assertEquals(1, $classifier->predict([0.9, 0.9])); + } + public function testPredictMultiClassSample(): void { // By use of One-v-Rest, Perceptron can perform multi-class classification From cbd9f5fde192548669245d3aa56cb2858eac9399 Mon Sep 17 00:00:00 2001 From: Yuji Uchiyama Date: Sun, 4 Mar 2018 00:03:53 +0900 Subject: [PATCH 263/328] Inline static constructors of exceptions (#250) --- src/Classification/MLPClassifier.php | 4 +- src/Clustering/FuzzyCMeans.php | 2 +- src/Clustering/KMeans.php | 2 +- src/CrossValidation/Split.php | 2 +- src/Dataset/ArrayDataset.php | 2 +- src/Dataset/CsvDataset.php | 4 +- src/Dataset/FilesDataset.php | 2 +- src/Dataset/SvmDataset.php | 14 +-- src/Exception/DatasetException.php | 19 ---- src/Exception/FileException.php | 14 --- src/Exception/InvalidArgumentException.php | 92 ------------------- src/Exception/LibsvmCommandException.php | 4 - src/Exception/MatrixException.php | 14 --- src/Exception/NormalizerException.php | 4 - src/Exception/SerializeException.php | 9 -- src/FeatureExtraction/StopWords.php | 2 +- src/FeatureSelection/SelectKBest.php | 2 +- src/Math/Comparison.php | 2 +- src/Math/Distance/Chebyshev.php | 2 +- src/Math/Distance/Euclidean.php | 2 +- src/Math/Distance/Manhattan.php | 2 +- src/Math/Distance/Minkowski.php | 2 +- src/Math/LinearAlgebra/LUDecomposition.php | 6 +- src/Math/Matrix.php | 10 +- src/Math/Statistic/ANOVA.php | 2 +- src/Math/Statistic/Correlation.php | 2 +- src/Math/Statistic/Covariance.php | 8 +- src/Math/Statistic/Mean.php | 2 +- src/Math/Statistic/StandardDeviation.php | 6 +- src/Metric/Accuracy.php | 2 +- src/ModelManager.php | 10 +- src/NeuralNetwork/Layer.php | 2 +- .../Network/MultilayerPerceptron.php | 8 +- src/Preprocessing/Normalizer.php | 2 +- src/SupportVectorMachine/DataTransformer.php | 2 +- .../SupportVectorMachine.php | 16 ++-- 36 files changed, 66 insertions(+), 214 deletions(-) diff --git a/src/Classification/MLPClassifier.php b/src/Classification/MLPClassifier.php index 432582be..13963f3d 100644 --- a/src/Classification/MLPClassifier.php +++ b/src/Classification/MLPClassifier.php @@ -17,7 +17,9 @@ class MLPClassifier extends MultilayerPerceptron implements Classifier public function getTargetClass($target): int { if (!in_array($target, $this->classes, true)) { - throw InvalidArgumentException::invalidTarget($target); + throw new InvalidArgumentException( + sprintf('Target with value "%s" is not part of the accepted classes', $target) + ); } return array_search($target, $this->classes, true); diff --git a/src/Clustering/FuzzyCMeans.php b/src/Clustering/FuzzyCMeans.php index a5408c03..5e6fa0c0 100644 --- a/src/Clustering/FuzzyCMeans.php +++ b/src/Clustering/FuzzyCMeans.php @@ -63,7 +63,7 @@ class FuzzyCMeans implements Clusterer public function __construct(int $clustersNumber, float $fuzziness = 2.0, float $epsilon = 1e-2, int $maxIterations = 100) { if ($clustersNumber <= 0) { - throw InvalidArgumentException::invalidClustersNumber(); + throw new InvalidArgumentException('Invalid clusters number'); } $this->clustersNumber = $clustersNumber; diff --git a/src/Clustering/KMeans.php b/src/Clustering/KMeans.php index 78a2e4ab..86ad754b 100644 --- a/src/Clustering/KMeans.php +++ b/src/Clustering/KMeans.php @@ -26,7 +26,7 @@ class KMeans implements Clusterer public function __construct(int $clustersNumber, int $initialization = self::INIT_KMEANS_PLUS_PLUS) { if ($clustersNumber <= 0) { - throw InvalidArgumentException::invalidClustersNumber(); + throw new InvalidArgumentException('Invalid clusters number'); } $this->clustersNumber = $clustersNumber; diff --git a/src/CrossValidation/Split.php b/src/CrossValidation/Split.php index 96c9019d..bffb59aa 100644 --- a/src/CrossValidation/Split.php +++ b/src/CrossValidation/Split.php @@ -32,7 +32,7 @@ abstract class Split public function __construct(Dataset $dataset, float $testSize = 0.3, ?int $seed = null) { if ($testSize <= 0 || $testSize >= 1) { - throw InvalidArgumentException::percentNotInRange('testSize'); + throw new InvalidArgumentException('testsize must be between 0.0 and 1.0'); } $this->seedGenerator($seed); diff --git a/src/Dataset/ArrayDataset.php b/src/Dataset/ArrayDataset.php index 7d30b0b0..618814ae 100644 --- a/src/Dataset/ArrayDataset.php +++ b/src/Dataset/ArrayDataset.php @@ -24,7 +24,7 @@ class ArrayDataset implements Dataset public function __construct(array $samples, array $targets) { if (count($samples) != count($targets)) { - throw InvalidArgumentException::arraySizeNotMatch(); + throw new InvalidArgumentException('Size of given arrays does not match'); } $this->samples = $samples; diff --git a/src/Dataset/CsvDataset.php b/src/Dataset/CsvDataset.php index f88fe314..631c6a6e 100644 --- a/src/Dataset/CsvDataset.php +++ b/src/Dataset/CsvDataset.php @@ -19,12 +19,12 @@ class CsvDataset extends ArrayDataset public function __construct(string $filepath, int $features, bool $headingRow = true, string $delimiter = ',', int $maxLineLength = 0) { if (!file_exists($filepath)) { - throw FileException::missingFile(basename($filepath)); + throw new FileException(sprintf('File "%s" missing.', basename($filepath))); } $handle = fopen($filepath, 'rb'); if ($handle === false) { - throw FileException::cantOpenFile(basename($filepath)); + throw new FileException(sprintf('File "%s" can\'t be open.', basename($filepath))); } if ($headingRow) { diff --git a/src/Dataset/FilesDataset.php b/src/Dataset/FilesDataset.php index ca04a3e6..a1597535 100644 --- a/src/Dataset/FilesDataset.php +++ b/src/Dataset/FilesDataset.php @@ -11,7 +11,7 @@ class FilesDataset extends ArrayDataset public function __construct(string $rootPath) { if (!is_dir($rootPath)) { - throw DatasetException::missingFolder($rootPath); + throw new DatasetException(sprintf('Dataset root folder "%s" missing.', $rootPath)); } $this->scanRootPath($rootPath); diff --git a/src/Dataset/SvmDataset.php b/src/Dataset/SvmDataset.php index 8bd172b5..c1e261b2 100644 --- a/src/Dataset/SvmDataset.php +++ b/src/Dataset/SvmDataset.php @@ -41,12 +41,12 @@ private static function readProblem(string $filePath): array private static function openFile(string $filePath) { if (!file_exists($filePath)) { - throw FileException::missingFile(basename($filePath)); + throw new FileException(sprintf('File "%s" missing.', basename($filePath))); } $handle = fopen($filePath, 'rb'); if ($handle === false) { - throw FileException::cantOpenFile(basename($filePath)); + throw new FileException(sprintf('File "%s" can\'t be open.', basename($filePath))); } return $handle; @@ -87,7 +87,7 @@ private static function parseLine(string $line): array private static function parseTargetColumn(string $column): float { if (!is_numeric($column)) { - throw DatasetException::invalidTarget($column); + throw new DatasetException(sprintf('Invalid target "%s".', $column)); } return (float) $column; @@ -97,7 +97,7 @@ private static function parseFeatureColumn(string $column): array { $feature = explode(':', $column, 2); if (count($feature) != 2) { - throw DatasetException::invalidValue($column); + throw new DatasetException(sprintf('Invalid value "%s".', $column)); } $index = self::parseFeatureIndex($feature[0]); @@ -109,11 +109,11 @@ private static function parseFeatureColumn(string $column): array private static function parseFeatureIndex(string $index): int { if (!is_numeric($index) || !ctype_digit($index)) { - throw DatasetException::invalidIndex($index); + throw new DatasetException(sprintf('Invalid index "%s".', $index)); } if ((int) $index < 1) { - throw DatasetException::invalidIndex($index); + throw new DatasetException(sprintf('Invalid index "%s".', $index)); } return (int) $index - 1; @@ -122,7 +122,7 @@ private static function parseFeatureIndex(string $index): int private static function parseFeatureValue(string $value): float { if (!is_numeric($value)) { - throw DatasetException::invalidValue($value); + throw new DatasetException(sprintf('Invalid value "%s".', $value)); } return (float) $value; diff --git a/src/Exception/DatasetException.php b/src/Exception/DatasetException.php index 1cb0bfc4..d6f22192 100644 --- a/src/Exception/DatasetException.php +++ b/src/Exception/DatasetException.php @@ -8,23 +8,4 @@ class DatasetException extends Exception { - public static function missingFolder(string $path): self - { - return new self(sprintf('Dataset root folder "%s" missing.', $path)); - } - - public static function invalidTarget(string $target): self - { - return new self(sprintf('Invalid target "%s".', $target)); - } - - public static function invalidIndex(string $index): self - { - return new self(sprintf('Invalid index "%s".', $index)); - } - - public static function invalidValue(string $value): self - { - return new self(sprintf('Invalid value "%s".', $value)); - } } diff --git a/src/Exception/FileException.php b/src/Exception/FileException.php index 719c2c2b..e00acff8 100644 --- a/src/Exception/FileException.php +++ b/src/Exception/FileException.php @@ -8,18 +8,4 @@ class FileException extends Exception { - public static function missingFile(string $filepath): self - { - return new self(sprintf('File "%s" missing.', $filepath)); - } - - public static function cantOpenFile(string $filepath): self - { - return new self(sprintf('File "%s" can\'t be open.', $filepath)); - } - - public static function cantSaveFile(string $filepath): self - { - return new self(sprintf('File "%s" can\'t be saved.', $filepath)); - } } diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php index 8aebbe01..a25599b4 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -8,96 +8,4 @@ class InvalidArgumentException extends Exception { - public static function arraySizeNotMatch(): self - { - return new self('Size of given arrays does not match'); - } - - public static function percentNotInRange($name): self - { - return new self(sprintf('%s must be between 0.0 and 1.0', $name)); - } - - public static function arrayCantBeEmpty(): self - { - return new self('The array has zero elements'); - } - - public static function arraySizeTooSmall(int $minimumSize = 2): self - { - return new self(sprintf('The array must have at least %d elements', $minimumSize)); - } - - public static function matrixDimensionsDidNotMatch(): self - { - return new self('Matrix dimensions did not match'); - } - - public static function inconsistentMatrixSupplied(): self - { - return new self('Inconsistent matrix supplied'); - } - - public static function invalidClustersNumber(): self - { - return new self('Invalid clusters number'); - } - - /** - * @param mixed $target - */ - public static function invalidTarget($target): self - { - return new self(sprintf('Target with value "%s" is not part of the accepted classes', $target)); - } - - public static function invalidStopWordsLanguage(string $language): self - { - return new self(sprintf('Can\'t find "%s" language for StopWords', $language)); - } - - public static function invalidLayerNodeClass(): self - { - return new self('Layer node class must implement Node interface'); - } - - public static function invalidLayersNumber(): self - { - return new self('Provide at least 1 hidden layer'); - } - - public static function invalidClassesNumber(): self - { - return new self('Provide at least 2 different classes'); - } - - public static function inconsistentClasses(): self - { - return new self('The provided classes don\'t match the classes provided in the constructor'); - } - - public static function fileNotFound(string $file): self - { - return new self(sprintf('File "%s" not found', $file)); - } - - public static function fileNotExecutable(string $file): self - { - return new self(sprintf('File "%s" is not executable', $file)); - } - - public static function pathNotFound(string $path): self - { - return new self(sprintf('The specified path "%s" does not exist', $path)); - } - - public static function pathNotWritable(string $path): self - { - return new self(sprintf('The specified path "%s" is not writable', $path)); - } - - public static function invalidOperator(string $operator): self - { - return new self(sprintf('Invalid operator "%s" provided', $operator)); - } } diff --git a/src/Exception/LibsvmCommandException.php b/src/Exception/LibsvmCommandException.php index a9d11e3a..d4d353eb 100644 --- a/src/Exception/LibsvmCommandException.php +++ b/src/Exception/LibsvmCommandException.php @@ -8,8 +8,4 @@ class LibsvmCommandException extends Exception { - public static function failedToRun(string $command, string $reason): self - { - return new self(sprintf('Failed running libsvm command: "%s" with reason: "%s"', $command, $reason)); - } } diff --git a/src/Exception/MatrixException.php b/src/Exception/MatrixException.php index b309bfff..eb6e9d3a 100644 --- a/src/Exception/MatrixException.php +++ b/src/Exception/MatrixException.php @@ -8,18 +8,4 @@ class MatrixException extends Exception { - public static function notSquareMatrix(): self - { - return new self('Matrix is not square matrix'); - } - - public static function columnOutOfRange(): self - { - return new self('Column out of range'); - } - - public static function singularMatrix(): self - { - return new self('Matrix is singular'); - } } diff --git a/src/Exception/NormalizerException.php b/src/Exception/NormalizerException.php index 282fa1b3..197876ea 100644 --- a/src/Exception/NormalizerException.php +++ b/src/Exception/NormalizerException.php @@ -8,8 +8,4 @@ class NormalizerException extends Exception { - public static function unknownNorm(): self - { - return new self('Unknown norm supplied.'); - } } diff --git a/src/Exception/SerializeException.php b/src/Exception/SerializeException.php index 6d1abaae..27cd744f 100644 --- a/src/Exception/SerializeException.php +++ b/src/Exception/SerializeException.php @@ -8,13 +8,4 @@ class SerializeException extends Exception { - public static function cantUnserialize(string $filepath): self - { - return new self(sprintf('"%s" can not be unserialized.', $filepath)); - } - - public static function cantSerialize(string $classname): self - { - return new self(sprintf('Class "%s" can not be serialized.', $classname)); - } } diff --git a/src/FeatureExtraction/StopWords.php b/src/FeatureExtraction/StopWords.php index f8fc69e8..f5622fd6 100644 --- a/src/FeatureExtraction/StopWords.php +++ b/src/FeatureExtraction/StopWords.php @@ -28,7 +28,7 @@ public static function factory(string $language = 'English'): self $className = __NAMESPACE__."\\StopWords\\${language}"; if (!class_exists($className)) { - throw InvalidArgumentException::invalidStopWordsLanguage($language); + throw new InvalidArgumentException(sprintf('Can\'t find "%s" language for StopWords', $language)); } return new $className(); diff --git a/src/FeatureSelection/SelectKBest.php b/src/FeatureSelection/SelectKBest.php index bd84e736..b0ff6449 100644 --- a/src/FeatureSelection/SelectKBest.php +++ b/src/FeatureSelection/SelectKBest.php @@ -44,7 +44,7 @@ public function __construct(int $k = 10, ?ScoringFunction $scoringFunction = nul public function fit(array $samples, ?array $targets = null): void { if ($targets === null || empty($targets)) { - throw InvalidArgumentException::arrayCantBeEmpty(); + throw new InvalidArgumentException('The array has zero elements'); } $this->scores = $sorted = $this->scoringFunction->score($samples, $targets); diff --git a/src/Math/Comparison.php b/src/Math/Comparison.php index de7414d1..d9ad00cc 100644 --- a/src/Math/Comparison.php +++ b/src/Math/Comparison.php @@ -33,7 +33,7 @@ public static function compare($a, $b, string $operator): bool case '!==': return $a !== $b; default: - throw InvalidArgumentException::invalidOperator($operator); + throw new InvalidArgumentException(sprintf('Invalid operator "%s" provided', $operator)); } } } diff --git a/src/Math/Distance/Chebyshev.php b/src/Math/Distance/Chebyshev.php index 52e969cb..0ccd29a8 100644 --- a/src/Math/Distance/Chebyshev.php +++ b/src/Math/Distance/Chebyshev.php @@ -15,7 +15,7 @@ class Chebyshev implements Distance public function distance(array $a, array $b): float { if (count($a) !== count($b)) { - throw InvalidArgumentException::arraySizeNotMatch(); + throw new InvalidArgumentException('Size of given arrays does not match'); } $differences = []; diff --git a/src/Math/Distance/Euclidean.php b/src/Math/Distance/Euclidean.php index 4ecc576e..4f437dcf 100644 --- a/src/Math/Distance/Euclidean.php +++ b/src/Math/Distance/Euclidean.php @@ -15,7 +15,7 @@ class Euclidean implements Distance public function distance(array $a, array $b): float { if (count($a) !== count($b)) { - throw InvalidArgumentException::arraySizeNotMatch(); + throw new InvalidArgumentException('Size of given arrays does not match'); } $distance = 0; diff --git a/src/Math/Distance/Manhattan.php b/src/Math/Distance/Manhattan.php index 457333c4..459a5ec4 100644 --- a/src/Math/Distance/Manhattan.php +++ b/src/Math/Distance/Manhattan.php @@ -15,7 +15,7 @@ class Manhattan implements Distance public function distance(array $a, array $b): float { if (count($a) !== count($b)) { - throw InvalidArgumentException::arraySizeNotMatch(); + throw new InvalidArgumentException('Size of given arrays does not match'); } return array_sum(array_map(function ($m, $n) { diff --git a/src/Math/Distance/Minkowski.php b/src/Math/Distance/Minkowski.php index 5ff7364f..36edf9be 100644 --- a/src/Math/Distance/Minkowski.php +++ b/src/Math/Distance/Minkowski.php @@ -25,7 +25,7 @@ public function __construct(float $lambda = 3.0) public function distance(array $a, array $b): float { if (count($a) !== count($b)) { - throw InvalidArgumentException::arraySizeNotMatch(); + throw new InvalidArgumentException('Size of given arrays does not match'); } $distance = 0; diff --git a/src/Math/LinearAlgebra/LUDecomposition.php b/src/Math/LinearAlgebra/LUDecomposition.php index 1594837a..44e8a268 100644 --- a/src/Math/LinearAlgebra/LUDecomposition.php +++ b/src/Math/LinearAlgebra/LUDecomposition.php @@ -81,7 +81,7 @@ class LUDecomposition public function __construct(Matrix $A) { if ($A->getRows() != $A->getColumns()) { - throw MatrixException::notSquareMatrix(); + throw new MatrixException('Matrix is not square matrix'); } // Use a "left-looking", dot-product, Crout/Doolittle algorithm. @@ -247,11 +247,11 @@ public function det(): float public function solve(Matrix $B): array { if ($B->getRows() != $this->m) { - throw MatrixException::notSquareMatrix(); + throw new MatrixException('Matrix is not square matrix'); } if (!$this->isNonsingular()) { - throw MatrixException::singularMatrix(); + throw new MatrixException('Matrix is singular'); } // Copy right hand side with pivoting diff --git a/src/Math/Matrix.php b/src/Math/Matrix.php index 908ec4d7..f69d8e6a 100644 --- a/src/Math/Matrix.php +++ b/src/Math/Matrix.php @@ -48,7 +48,7 @@ public function __construct(array $matrix, bool $validate = true) if ($validate) { for ($i = 0; $i < $this->rows; ++$i) { if (count($matrix[$i]) !== $this->columns) { - throw InvalidArgumentException::matrixDimensionsDidNotMatch(); + throw new InvalidArgumentException('Matrix dimensions did not match'); } } } @@ -92,7 +92,7 @@ public function getColumns(): int public function getColumnValues($column): array { if ($column >= $this->columns) { - throw MatrixException::columnOutOfRange(); + throw new MatrixException('Column out of range'); } return array_column($this->matrix, $column); @@ -110,7 +110,7 @@ public function getDeterminant() } if (!$this->isSquare()) { - throw MatrixException::notSquareMatrix(); + throw new MatrixException('Matrix is not square matrix'); } $lu = new LUDecomposition($this); @@ -139,7 +139,7 @@ public function transpose(): self public function multiply(self $matrix): self { if ($this->columns != $matrix->getRows()) { - throw InvalidArgumentException::inconsistentMatrixSupplied(); + throw new InvalidArgumentException('Inconsistent matrix supplied'); } $product = []; @@ -200,7 +200,7 @@ public function subtract(self $other): self public function inverse(): self { if (!$this->isSquare()) { - throw MatrixException::notSquareMatrix(); + throw new MatrixException('Matrix is not square matrix'); } $LU = new LUDecomposition($this); diff --git a/src/Math/Statistic/ANOVA.php b/src/Math/Statistic/ANOVA.php index 65548a19..2732ba79 100644 --- a/src/Math/Statistic/ANOVA.php +++ b/src/Math/Statistic/ANOVA.php @@ -25,7 +25,7 @@ public static function oneWayF(array $samples): array { $classes = count($samples); if ($classes < 2) { - throw InvalidArgumentException::arraySizeTooSmall(2); + throw new InvalidArgumentException('The array must have at least 2 elements'); } $samplesPerClass = array_map(function (array $class): int { diff --git a/src/Math/Statistic/Correlation.php b/src/Math/Statistic/Correlation.php index 8803cbfb..ce52f3b6 100644 --- a/src/Math/Statistic/Correlation.php +++ b/src/Math/Statistic/Correlation.php @@ -17,7 +17,7 @@ class Correlation public static function pearson(array $x, array $y): float { if (count($x) !== count($y)) { - throw InvalidArgumentException::arraySizeNotMatch(); + throw new InvalidArgumentException('Size of given arrays does not match'); } $count = count($x); diff --git a/src/Math/Statistic/Covariance.php b/src/Math/Statistic/Covariance.php index 3dfce4bf..4ed07764 100644 --- a/src/Math/Statistic/Covariance.php +++ b/src/Math/Statistic/Covariance.php @@ -16,12 +16,12 @@ class Covariance public static function fromXYArrays(array $x, array $y, bool $sample = true, ?float $meanX = null, ?float $meanY = null): float { if (empty($x) || empty($y)) { - throw InvalidArgumentException::arrayCantBeEmpty(); + throw new InvalidArgumentException('The array has zero elements'); } $n = count($x); if ($sample && $n === 1) { - throw InvalidArgumentException::arraySizeTooSmall(2); + throw new InvalidArgumentException('The array must have at least 2 elements'); } if ($meanX === null) { @@ -54,12 +54,12 @@ public static function fromXYArrays(array $x, array $y, bool $sample = true, ?fl public static function fromDataset(array $data, int $i, int $k, bool $sample = true, ?float $meanX = null, ?float $meanY = null): float { if (empty($data)) { - throw InvalidArgumentException::arrayCantBeEmpty(); + throw new InvalidArgumentException('The array has zero elements'); } $n = count($data); if ($sample && $n === 1) { - throw InvalidArgumentException::arraySizeTooSmall(2); + throw new InvalidArgumentException('The array must have at least 2 elements'); } if ($i < 0 || $k < 0 || $i >= $n || $k >= $n) { diff --git a/src/Math/Statistic/Mean.php b/src/Math/Statistic/Mean.php index 8e761dfb..6b6d555a 100644 --- a/src/Math/Statistic/Mean.php +++ b/src/Math/Statistic/Mean.php @@ -59,7 +59,7 @@ public static function mode(array $numbers) private static function checkArrayLength(array $array): void { if (empty($array)) { - throw InvalidArgumentException::arrayCantBeEmpty(); + throw new InvalidArgumentException('The array has zero elements'); } } } diff --git a/src/Math/Statistic/StandardDeviation.php b/src/Math/Statistic/StandardDeviation.php index 5bf4940a..f1eae8a8 100644 --- a/src/Math/Statistic/StandardDeviation.php +++ b/src/Math/Statistic/StandardDeviation.php @@ -14,13 +14,13 @@ class StandardDeviation public static function population(array $numbers, bool $sample = true): float { if (empty($numbers)) { - throw InvalidArgumentException::arrayCantBeEmpty(); + throw new InvalidArgumentException('The array has zero elements'); } $n = count($numbers); if ($sample && $n === 1) { - throw InvalidArgumentException::arraySizeTooSmall(2); + throw new InvalidArgumentException('The array must have at least 2 elements'); } $mean = Mean::arithmetic($numbers); @@ -45,7 +45,7 @@ public static function population(array $numbers, bool $sample = true): float public static function sumOfSquares(array $numbers): float { if (empty($numbers)) { - throw InvalidArgumentException::arrayCantBeEmpty(); + throw new InvalidArgumentException('The array has zero elements'); } $mean = Mean::arithmetic($numbers); diff --git a/src/Metric/Accuracy.php b/src/Metric/Accuracy.php index 3fd545cd..92f6860d 100644 --- a/src/Metric/Accuracy.php +++ b/src/Metric/Accuracy.php @@ -16,7 +16,7 @@ class Accuracy public static function score(array $actualLabels, array $predictedLabels, bool $normalize = true) { if (count($actualLabels) != count($predictedLabels)) { - throw InvalidArgumentException::arraySizeNotMatch(); + throw new InvalidArgumentException('Size of given arrays does not match'); } $score = 0; diff --git a/src/ModelManager.php b/src/ModelManager.php index ebcdbe46..36e8e2c1 100644 --- a/src/ModelManager.php +++ b/src/ModelManager.php @@ -12,29 +12,29 @@ class ModelManager public function saveToFile(Estimator $estimator, string $filepath): void { if (!is_writable(dirname($filepath))) { - throw FileException::cantSaveFile(basename($filepath)); + throw new FileException(sprintf('File "%s" can\'t be saved.', basename($filepath))); } $serialized = serialize($estimator); if (empty($serialized)) { - throw SerializeException::cantSerialize(gettype($estimator)); + throw new SerializeException(sprintf('Class "%s" can not be serialized.', gettype($estimator))); } $result = file_put_contents($filepath, $serialized, LOCK_EX); if ($result === false) { - throw FileException::cantSaveFile(basename($filepath)); + throw new FileException(sprintf('File "%s" can\'t be saved.', basename($filepath))); } } public function restoreFromFile(string $filepath): Estimator { if (!file_exists($filepath) || !is_readable($filepath)) { - throw FileException::cantOpenFile(basename($filepath)); + throw new FileException(sprintf('File "%s" can\'t be open.', basename($filepath))); } $object = unserialize(file_get_contents($filepath)); if ($object === false) { - throw SerializeException::cantUnserialize(basename($filepath)); + throw new SerializeException(sprintf('"%s" can not be unserialized.', basename($filepath))); } return $object; diff --git a/src/NeuralNetwork/Layer.php b/src/NeuralNetwork/Layer.php index a604a536..1c681f89 100644 --- a/src/NeuralNetwork/Layer.php +++ b/src/NeuralNetwork/Layer.php @@ -20,7 +20,7 @@ class Layer public function __construct(int $nodesNumber = 0, string $nodeClass = Neuron::class, ?ActivationFunction $activationFunction = null) { if (!in_array(Node::class, class_implements($nodeClass), true)) { - throw InvalidArgumentException::invalidLayerNodeClass(); + throw new InvalidArgumentException('Layer node class must implement Node interface'); } for ($i = 0; $i < $nodesNumber; ++$i) { diff --git a/src/NeuralNetwork/Network/MultilayerPerceptron.php b/src/NeuralNetwork/Network/MultilayerPerceptron.php index a6d3be0d..36260639 100644 --- a/src/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/NeuralNetwork/Network/MultilayerPerceptron.php @@ -62,11 +62,11 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, public function __construct(int $inputLayerFeatures, array $hiddenLayers, array $classes, int $iterations = 10000, ?ActivationFunction $activationFunction = null, float $learningRate = 1) { if (empty($hiddenLayers)) { - throw InvalidArgumentException::invalidLayersNumber(); + throw new InvalidArgumentException('Provide at least 1 hidden layer'); } if (count($classes) < 2) { - throw InvalidArgumentException::invalidClassesNumber(); + throw new InvalidArgumentException('Provide at least 2 different classes'); } $this->classes = array_values($classes); @@ -93,7 +93,9 @@ public function partialTrain(array $samples, array $targets, array $classes = [] { if (!empty($classes) && array_values($classes) !== $this->classes) { // We require the list of classes in the constructor. - throw InvalidArgumentException::inconsistentClasses(); + throw new InvalidArgumentException( + 'The provided classes don\'t match the classes provided in the constructor' + ); } for ($i = 0; $i < $this->iterations; ++$i) { diff --git a/src/Preprocessing/Normalizer.php b/src/Preprocessing/Normalizer.php index 9654e48a..95927ea6 100644 --- a/src/Preprocessing/Normalizer.php +++ b/src/Preprocessing/Normalizer.php @@ -42,7 +42,7 @@ class Normalizer implements Preprocessor public function __construct(int $norm = self::NORM_L2) { if (!in_array($norm, [self::NORM_L1, self::NORM_L2, self::NORM_STD], true)) { - throw NormalizerException::unknownNorm(); + throw new NormalizerException('Unknown norm supplied.'); } $this->norm = $norm; diff --git a/src/SupportVectorMachine/DataTransformer.php b/src/SupportVectorMachine/DataTransformer.php index e1aeb7de..06272e20 100644 --- a/src/SupportVectorMachine/DataTransformer.php +++ b/src/SupportVectorMachine/DataTransformer.php @@ -27,7 +27,7 @@ public static function trainingSet(array $samples, array $labels, bool $targets public static function testSet(array $samples): string { if (empty($samples)) { - throw InvalidArgumentException::arrayCantBeEmpty(); + throw new InvalidArgumentException('The array has zero elements'); } if (!is_array($samples[0])) { diff --git a/src/SupportVectorMachine/SupportVectorMachine.php b/src/SupportVectorMachine/SupportVectorMachine.php index 561f65bd..be16ff4f 100644 --- a/src/SupportVectorMachine/SupportVectorMachine.php +++ b/src/SupportVectorMachine/SupportVectorMachine.php @@ -137,7 +137,7 @@ public function setBinPath(string $binPath): void public function setVarPath(string $varPath): void { if (!is_writable($varPath)) { - throw InvalidArgumentException::pathNotWritable($varPath); + throw new InvalidArgumentException(sprintf('The specified path "%s" is not writable', $varPath)); } $this->ensureDirectorySeparator($varPath); @@ -160,7 +160,9 @@ public function train(array $samples, array $targets): void unlink($trainingSetFileName); if ($return !== 0) { - throw LibsvmCommandException::failedToRun($command, array_pop($output)); + throw new LibsvmCommandException( + sprintf('Failed running libsvm command: "%s" with reason: "%s"', $command, array_pop($output)) + ); } $this->model = file_get_contents($modelFileName); @@ -244,7 +246,9 @@ private function runSvmPredict(array $samples, bool $probabilityEstimates): stri unlink($outputFileName); if ($return !== 0) { - throw LibsvmCommandException::failedToRun($command, array_pop($output)); + throw new LibsvmCommandException( + sprintf('Failed running libsvm command: "%s" with reason: "%s"', $command, array_pop($output)) + ); } return $predictions; @@ -312,18 +316,18 @@ private function ensureDirectorySeparator(string &$path): void private function verifyBinPath(string $path): void { if (!is_dir($path)) { - throw InvalidArgumentException::pathNotFound($path); + throw new InvalidArgumentException(sprintf('The specified path "%s" does not exist', $path)); } $osExtension = $this->getOSExtension(); foreach (['svm-predict', 'svm-scale', 'svm-train'] as $filename) { $filePath = $path.$filename.$osExtension; if (!file_exists($filePath)) { - throw InvalidArgumentException::fileNotFound($filePath); + throw new InvalidArgumentException(sprintf('File "%s" not found', $filePath)); } if (!is_executable($filePath)) { - throw InvalidArgumentException::fileNotExecutable($filePath); + throw new InvalidArgumentException(sprintf('File "%s" is not executable', $filePath)); } } } From 8976047cbc9f5ff54fb9171734fe1d5a17f2e00e Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sat, 3 Mar 2018 16:04:21 +0100 Subject: [PATCH 264/328] Add removeColumns function to ArrayDataset (#249) * Add removeColumns function to ArrayDataset * Add removeColumns to docs * Fix cs --- .../datasets/array-dataset.md | 20 +++++++++++++++++++ src/Dataset/ArrayDataset.php | 19 ++++++++++++++++++ tests/Dataset/ArrayDatasetTest.php | 11 ++++++++++ 3 files changed, 50 insertions(+) diff --git a/docs/machine-learning/datasets/array-dataset.md b/docs/machine-learning/datasets/array-dataset.md index de495569..8bbcc37a 100644 --- a/docs/machine-learning/datasets/array-dataset.md +++ b/docs/machine-learning/datasets/array-dataset.md @@ -8,6 +8,8 @@ Helper class that holds data as PHP `array` type. Implements the `Dataset` inter * $labels - (array) of labels ``` +use Phpml\Dataset\ArrayDataset; + $dataset = new ArrayDataset([[1, 1], [2, 1], [3, 2], [4, 1]], ['a', 'a', 'b', 'b']); ``` @@ -19,3 +21,21 @@ To get samples or labels you can use getters: $dataset->getSamples(); $dataset->getTargets(); ``` + +### Remove columns + +You can remove columns by index numbers, for example: + +``` +use Phpml\Dataset\ArrayDataset; + +$dataset = new ArrayDataset( + [[1,2,3,4], [2,3,4,5], [3,4,5,6], [4,5,6,7]], + ['a', 'a', 'b', 'b'] +); + +$dataset->removeColumns([0,2]); + +// now from each sample column 0 and 2 are removed +// [[2,4], [3,5], [4,6], [5,7]] +``` diff --git a/src/Dataset/ArrayDataset.php b/src/Dataset/ArrayDataset.php index 618814ae..e0e78224 100644 --- a/src/Dataset/ArrayDataset.php +++ b/src/Dataset/ArrayDataset.php @@ -40,4 +40,23 @@ public function getTargets(): array { return $this->targets; } + + /** + * @param int[] $columns + */ + public function removeColumns(array $columns): void + { + foreach ($this->samples as &$sample) { + $this->removeColumnsFromSample($sample, $columns); + } + } + + private function removeColumnsFromSample(array &$sample, array $columns): void + { + foreach ($columns as $index) { + unset($sample[$index]); + } + + $sample = array_values($sample); + } } diff --git a/tests/Dataset/ArrayDatasetTest.php b/tests/Dataset/ArrayDatasetTest.php index bca9e434..04319596 100644 --- a/tests/Dataset/ArrayDatasetTest.php +++ b/tests/Dataset/ArrayDatasetTest.php @@ -26,4 +26,15 @@ public function testArrayDataset(): void $this->assertEquals($samples, $dataset->getSamples()); $this->assertEquals($labels, $dataset->getTargets()); } + + public function testRemoveColumns(): void + { + $dataset = new ArrayDataset( + [[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6], [4, 5, 6, 7]], + ['a', 'a', 'b', 'b'] + ); + $dataset->removeColumns([0, 2]); + + $this->assertEquals([[2, 4], [3, 5], [4, 6], [5, 7]], $dataset->getSamples()); + } } From 941d240ab6e760f4389c0f95c736ab3d5c5a23fe Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Sun, 4 Mar 2018 17:02:36 +0100 Subject: [PATCH 265/328] Add RandomForest exception tests (#251) --- .../DecisionTree/DecisionTreeLeaf.php | 4 +- src/Classification/Ensemble/RandomForest.php | 24 +++++------ .../Ensemble/RandomForestTest.php | 43 +++++++++++++++---- 3 files changed, 47 insertions(+), 24 deletions(-) diff --git a/src/Classification/DecisionTree/DecisionTreeLeaf.php b/src/Classification/DecisionTree/DecisionTreeLeaf.php index 5a106ab3..649c7433 100644 --- a/src/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Classification/DecisionTree/DecisionTreeLeaf.php @@ -29,12 +29,12 @@ class DecisionTreeLeaf public $columnIndex; /** - * @var ?DecisionTreeLeaf + * @var DecisionTreeLeaf|null */ public $leftLeaf; /** - * @var ?DecisionTreeLeaf + * @var DecisionTreeLeaf|null */ public $rightLeaf; diff --git a/src/Classification/Ensemble/RandomForest.php b/src/Classification/Ensemble/RandomForest.php index 1f2d1f5c..d871fe2b 100644 --- a/src/Classification/Ensemble/RandomForest.php +++ b/src/Classification/Ensemble/RandomForest.php @@ -4,9 +4,9 @@ namespace Phpml\Classification\Ensemble; -use Exception; use Phpml\Classification\Classifier; use Phpml\Classification\DecisionTree; +use Phpml\Exception\InvalidArgumentException; class RandomForest extends Bagging { @@ -41,20 +41,20 @@ public function __construct(int $numClassifier = 50) * Default value for the ratio is 'log' which results in log(numFeatures, 2) + 1 * features to be taken into consideration while selecting subspace of features * - * @param mixed $ratio string or float should be given - * - * @return $this - * - * @throws \Exception + * @param string|float $ratio */ - public function setFeatureSubsetRatio($ratio) + public function setFeatureSubsetRatio($ratio): self { + if (!is_string($ratio) && !is_float($ratio)) { + throw new InvalidArgumentException('Feature subset ratio must be a string or a float'); + } + if (is_float($ratio) && ($ratio < 0.1 || $ratio > 1.0)) { - throw new Exception('When a float given, feature subset ratio should be between 0.1 and 1.0'); + throw new InvalidArgumentException('When a float is given, feature subset ratio should be between 0.1 and 1.0'); } if (is_string($ratio) && $ratio != 'sqrt' && $ratio != 'log') { - throw new Exception("When a string given, feature subset ratio can only be 'sqrt' or 'log' "); + throw new InvalidArgumentException("When a string is given, feature subset ratio can only be 'sqrt' or 'log'"); } $this->featureSubsetRatio = $ratio; @@ -66,13 +66,11 @@ public function setFeatureSubsetRatio($ratio) * RandomForest algorithm is usable *only* with DecisionTree * * @return $this - * - * @throws \Exception */ public function setClassifer(string $classifier, array $classifierOptions = []) { if ($classifier != DecisionTree::class) { - throw new Exception('RandomForest can only use DecisionTree as base classifier'); + throw new InvalidArgumentException('RandomForest can only use DecisionTree as base classifier'); } return parent::setClassifer($classifier, $classifierOptions); @@ -133,7 +131,7 @@ protected function initSingleClassifier(Classifier $classifier): Classifier { if (is_float($this->featureSubsetRatio)) { $featureCount = (int) ($this->featureSubsetRatio * $this->featureCount); - } elseif ($this->featureCount == 'sqrt') { + } elseif ($this->featureSubsetRatio == 'sqrt') { $featureCount = (int) sqrt($this->featureCount) + 1; } else { $featureCount = (int) log($this->featureCount, 2) + 1; diff --git a/tests/Classification/Ensemble/RandomForestTest.php b/tests/Classification/Ensemble/RandomForestTest.php index ea0cce12..93353a36 100644 --- a/tests/Classification/Ensemble/RandomForestTest.php +++ b/tests/Classification/Ensemble/RandomForestTest.php @@ -7,19 +7,44 @@ use Phpml\Classification\DecisionTree; use Phpml\Classification\Ensemble\RandomForest; use Phpml\Classification\NaiveBayes; -use Throwable; +use Phpml\Exception\InvalidArgumentException; class RandomForestTest extends BaggingTest { - public function testOtherBaseClassifier(): void + public function testThrowExceptionWithInvalidClassifier(): void { - try { - $classifier = new RandomForest(); - $classifier->setClassifer(NaiveBayes::class); - $this->assertEquals(0, 1); - } catch (Throwable $ex) { - $this->assertEquals(1, 1); - } + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('RandomForest can only use DecisionTree as base classifier'); + + $classifier = new RandomForest(); + $classifier->setClassifer(NaiveBayes::class); + } + + public function testThrowExceptionWithInvalidFeatureSubsetRatioType(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Feature subset ratio must be a string or a float'); + + $classifier = new RandomForest(); + $classifier->setFeatureSubsetRatio(1); + } + + public function testThrowExceptionWithInvalidFeatureSubsetRatioFloat(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('When a float is given, feature subset ratio should be between 0.1 and 1.0'); + + $classifier = new RandomForest(); + $classifier->setFeatureSubsetRatio(1.1); + } + + public function testThrowExceptionWithInvalidFeatureSubsetRatioString(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("When a string is given, feature subset ratio can only be 'sqrt' or 'log'"); + + $classifier = new RandomForest(); + $classifier->setFeatureSubsetRatio('pow'); } protected function getClassifier($numBaseClassifiers = 50) From 33efab20a54be1c2c3b2137ba7bb89e8869b34c3 Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Sun, 4 Mar 2018 17:05:25 +0100 Subject: [PATCH 266/328] Add LUDecomposition triangular factor tests (#253) --- .../Math/LinearAlgebra/LUDecompositionTest.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/Math/LinearAlgebra/LUDecompositionTest.php b/tests/Math/LinearAlgebra/LUDecompositionTest.php index b678673f..ea96f77d 100644 --- a/tests/Math/LinearAlgebra/LUDecompositionTest.php +++ b/tests/Math/LinearAlgebra/LUDecompositionTest.php @@ -28,4 +28,22 @@ public function testSolveWithInvalidMatrix(): void $lu = new LUDecomposition(new Matrix([[1, 2], [3, 4]])); $lu->solve(new Matrix([1, 2, 3])); } + + public function testLowerTriangularFactor(): void + { + $lu = new LUDecomposition(new Matrix([[1, 2], [3, 4]])); + $L = $lu->getL(); + + $this->assertInstanceOf(Matrix::class, $L); + $this->assertSame([[1.0, 0.0], [0.3333333333333333, 1.0]], $L->toArray()); + } + + public function testUpperTriangularFactor(): void + { + $lu = new LUDecomposition(new Matrix([[1, 2], [3, 4]])); + $U = $lu->getU(); + + $this->assertInstanceOf(Matrix::class, $U); + $this->assertSame([[3.0, 4.0], [0.0, 0.6666666666666667]], $U->toArray()); + } } From 55749c7c9225fa4189e2d92f8d1dd1716f3806e3 Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Sun, 4 Mar 2018 17:06:46 +0100 Subject: [PATCH 267/328] Add Cluster tests (#254) --- src/Clustering/KMeans/Cluster.php | 2 +- tests/Clustering/KMeans/ClusterTest.php | 49 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 tests/Clustering/KMeans/ClusterTest.php diff --git a/src/Clustering/KMeans/Cluster.php b/src/Clustering/KMeans/Cluster.php index a4462fe7..89369263 100644 --- a/src/Clustering/KMeans/Cluster.php +++ b/src/Clustering/KMeans/Cluster.php @@ -49,7 +49,7 @@ public function toArray(): array public function attach(Point $point): Point { if ($point instanceof self) { - throw new LogicException('cannot attach a cluster to another'); + throw new LogicException('Cannot attach a cluster to another'); } $this->points->attach($point); diff --git a/tests/Clustering/KMeans/ClusterTest.php b/tests/Clustering/KMeans/ClusterTest.php new file mode 100644 index 00000000..80b98373 --- /dev/null +++ b/tests/Clustering/KMeans/ClusterTest.php @@ -0,0 +1,49 @@ +expectException(LogicException::class); + $this->expectExceptionMessage('Cannot attach a cluster to another'); + + $cluster = new Cluster(new Space(1), []); + $cluster->attach(clone $cluster); + } + + public function testToArray(): void + { + $cluster = new Cluster(new Space(2), [1, 2]); + $cluster->attach(new Point([1, 1])); + + $this->assertSame([ + 'centroid' => [1, 2], + 'points' => [ + [1, 1], + ], + ], $cluster->toArray()); + } + + public function testDetach(): void + { + $cluster = new Cluster(new Space(2), []); + $cluster->attach(new Point([1, 2])); + $cluster->attach($point = new Point([1, 1])); + + $detachedPoint = $cluster->detach($point); + + $this->assertSame($detachedPoint, $point); + $this->assertNotContains($point, $cluster->getPoints()); + $this->assertCount(1, $cluster); + } +} From a40c50b48bd710d07575aa5827305f5e6020b2b7 Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Sun, 4 Mar 2018 22:44:22 +0100 Subject: [PATCH 268/328] Add Optimizer tests and remove initialTheta (#252) * Add Optimizer tests * Remove Optimizer.initialTheta and rename Optimizer.setInitialTheta to setTheta --- src/Helper/Optimizer/Optimizer.php | 6 +--- src/Helper/Optimizer/StochasticGD.php | 2 +- .../Optimizer/ConjugateGradientTest.php | 4 +-- tests/Helper/Optimizer/OptimizerTest.php | 34 +++++++++++++++++++ 4 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 tests/Helper/Optimizer/OptimizerTest.php diff --git a/src/Helper/Optimizer/Optimizer.php b/src/Helper/Optimizer/Optimizer.php index 9bac3bef..dba0cd06 100644 --- a/src/Helper/Optimizer/Optimizer.php +++ b/src/Helper/Optimizer/Optimizer.php @@ -9,8 +9,6 @@ abstract class Optimizer { - public $initialTheta; - /** * Unknown variables to be found * @@ -37,11 +35,9 @@ public function __construct(int $dimensions) for ($i = 0; $i < $this->dimensions; ++$i) { $this->theta[] = (random_int(0, PHP_INT_MAX) / PHP_INT_MAX) + 0.1; } - - $this->initialTheta = $this->theta; } - public function setInitialTheta(array $theta) + public function setTheta(array $theta) { if (count($theta) != $this->dimensions) { throw new InvalidArgumentException(sprintf('Number of values in the weights array should be %s', $this->dimensions)); diff --git a/src/Helper/Optimizer/StochasticGD.php b/src/Helper/Optimizer/StochasticGD.php index e1cbeea1..c4fabd33 100644 --- a/src/Helper/Optimizer/StochasticGD.php +++ b/src/Helper/Optimizer/StochasticGD.php @@ -89,7 +89,7 @@ public function __construct(int $dimensions) $this->dimensions = $dimensions; } - public function setInitialTheta(array $theta) + public function setTheta(array $theta) { if (count($theta) != $this->dimensions + 1) { throw new InvalidArgumentException(sprintf('Number of values in the weights array should be %s', $this->dimensions + 1)); diff --git a/tests/Helper/Optimizer/ConjugateGradientTest.php b/tests/Helper/Optimizer/ConjugateGradientTest.php index 78eb7182..09c250c8 100644 --- a/tests/Helper/Optimizer/ConjugateGradientTest.php +++ b/tests/Helper/Optimizer/ConjugateGradientTest.php @@ -57,7 +57,7 @@ public function testRunOptimizationWithCustomInitialTheta(): void $optimizer = new ConjugateGradient(1); // set very weak theta to trigger very bad result - $optimizer->setInitialTheta([0.0000001, 0.0000001]); + $optimizer->setTheta([0.0000001, 0.0000001]); $theta = $optimizer->runOptimization($samples, $targets, $callback); @@ -97,6 +97,6 @@ public function testThrowExceptionOnInvalidTheta(): void $opimizer = new ConjugateGradient(2); $this->expectException(InvalidArgumentException::class); - $opimizer->setInitialTheta([0.15]); + $opimizer->setTheta([0.15]); } } diff --git a/tests/Helper/Optimizer/OptimizerTest.php b/tests/Helper/Optimizer/OptimizerTest.php new file mode 100644 index 00000000..22efdd22 --- /dev/null +++ b/tests/Helper/Optimizer/OptimizerTest.php @@ -0,0 +1,34 @@ +expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Number of values in the weights array should be 3'); + /** @var Optimizer $optimizer */ + $optimizer = $this->getMockForAbstractClass(Optimizer::class, [3]); + + $optimizer->setTheta([]); + } + + public function testSetTheta(): void + { + /** @var Optimizer $optimizer */ + $optimizer = $this->getMockForAbstractClass(Optimizer::class, [2]); + $object = $optimizer->setTheta([0.3, 1]); + + $theta = $this->getObjectAttribute($optimizer, 'theta'); + + $this->assertSame($object, $optimizer); + $this->assertSame([0.3, 1], $theta); + } +} From 66ca874062795671e97c093e7b614f91749eb481 Mon Sep 17 00:00:00 2001 From: Yuji Uchiyama Date: Wed, 7 Mar 2018 07:26:36 +0900 Subject: [PATCH 269/328] Throw proper exception (#259) * Throw proper exception * Fix coding style --- src/Classification/Ensemble/AdaBoost.php | 6 +-- src/Classification/Ensemble/Bagging.php | 6 +-- src/Classification/Linear/Adaline.php | 6 +-- src/Classification/Linear/DecisionStump.php | 6 +-- .../Linear/LogisticRegression.php | 23 +++++++---- src/Classification/Linear/Perceptron.php | 8 ++-- src/DimensionReduction/KernelPCA.php | 14 ++++--- src/DimensionReduction/LDA.php | 17 ++++---- src/DimensionReduction/PCA.php | 17 ++++---- .../Classification/Ensemble/AdaBoostTest.php | 24 ++++++++++-- tests/Classification/Ensemble/BaggingTest.php | 13 +++++-- tests/Classification/Linear/AdalineTest.php | 12 ++++++ .../Linear/DecisionStumpTest.php | 13 +++++++ .../Linear/LogisticRegressionTest.php | 8 ++-- .../Classification/Linear/PerceptronTest.php | 13 +++++++ tests/DimensionReduction/KernelPCATest.php | 32 +++++++++++++++ tests/DimensionReduction/LDATest.php | 39 +++++++++++++++++++ tests/DimensionReduction/PCATest.php | 39 +++++++++++++++++++ 18 files changed, 242 insertions(+), 54 deletions(-) diff --git a/src/Classification/Ensemble/AdaBoost.php b/src/Classification/Ensemble/AdaBoost.php index b314c81a..c7b5e75e 100644 --- a/src/Classification/Ensemble/AdaBoost.php +++ b/src/Classification/Ensemble/AdaBoost.php @@ -4,10 +4,10 @@ namespace Phpml\Classification\Ensemble; -use Exception; use Phpml\Classification\Classifier; use Phpml\Classification\Linear\DecisionStump; use Phpml\Classification\WeightedClassifier; +use Phpml\Exception\InvalidArgumentException; use Phpml\Helper\Predictable; use Phpml\Helper\Trainable; use Phpml\Math\Statistic\Mean; @@ -93,14 +93,14 @@ public function setBaseClassifier(string $baseClassifier = DecisionStump::class, } /** - * @throws \Exception + * @throws InvalidArgumentException */ public function train(array $samples, array $targets): void { // Initialize usual variables $this->labels = array_keys(array_count_values($targets)); if (count($this->labels) != 2) { - throw new Exception('AdaBoost is a binary classifier and can classify between two classes only'); + throw new InvalidArgumentException('AdaBoost is a binary classifier and can classify between two classes only'); } // Set all target values to either -1 or 1 diff --git a/src/Classification/Ensemble/Bagging.php b/src/Classification/Ensemble/Bagging.php index a1d51c25..02e958ba 100644 --- a/src/Classification/Ensemble/Bagging.php +++ b/src/Classification/Ensemble/Bagging.php @@ -4,9 +4,9 @@ namespace Phpml\Classification\Ensemble; -use Exception; use Phpml\Classification\Classifier; use Phpml\Classification\DecisionTree; +use Phpml\Exception\InvalidArgumentException; use Phpml\Helper\Predictable; use Phpml\Helper\Trainable; use ReflectionClass; @@ -77,12 +77,12 @@ public function __construct(int $numClassifier = 50) * * @return $this * - * @throws \Exception + * @throws InvalidArgumentException */ public function setSubsetRatio(float $ratio) { if ($ratio < 0.1 || $ratio > 1.0) { - throw new Exception('Subset ratio should be between 0.1 and 1.0'); + throw new InvalidArgumentException('Subset ratio should be between 0.1 and 1.0'); } $this->subsetRatio = $ratio; diff --git a/src/Classification/Linear/Adaline.php b/src/Classification/Linear/Adaline.php index 3b6309de..a1337328 100644 --- a/src/Classification/Linear/Adaline.php +++ b/src/Classification/Linear/Adaline.php @@ -4,7 +4,7 @@ namespace Phpml\Classification\Linear; -use Exception; +use Phpml\Exception\InvalidArgumentException; class Adaline extends Perceptron { @@ -34,7 +34,7 @@ class Adaline extends Perceptron * If normalizeInputs is set to true, then every input given to the algorithm will be standardized * by use of standard deviation and mean calculation * - * @throws \Exception + * @throws InvalidArgumentException */ public function __construct( float $learningRate = 0.001, @@ -43,7 +43,7 @@ public function __construct( int $trainingType = self::BATCH_TRAINING ) { if (!in_array($trainingType, [self::BATCH_TRAINING, self::ONLINE_TRAINING], true)) { - throw new Exception('Adaline can only be trained with batch and online/stochastic gradient descent algorithm'); + throw new InvalidArgumentException('Adaline can only be trained with batch and online/stochastic gradient descent algorithm'); } $this->trainingType = $trainingType; diff --git a/src/Classification/Linear/DecisionStump.php b/src/Classification/Linear/DecisionStump.php index 1903712f..ef6a4589 100644 --- a/src/Classification/Linear/DecisionStump.php +++ b/src/Classification/Linear/DecisionStump.php @@ -4,9 +4,9 @@ namespace Phpml\Classification\Linear; -use Exception; use Phpml\Classification\DecisionTree; use Phpml\Classification\WeightedClassifier; +use Phpml\Exception\InvalidArgumentException; use Phpml\Helper\OneVsRest; use Phpml\Helper\Predictable; use Phpml\Math\Comparison; @@ -104,7 +104,7 @@ public function setNumericalSplitCount(float $count): void } /** - * @throws \Exception + * @throws InvalidArgumentException */ protected function trainBinary(array $samples, array $targets, array $labels): void { @@ -121,7 +121,7 @@ protected function trainBinary(array $samples, array $targets, array $labels): v if (!empty($this->weights)) { $numWeights = count($this->weights); if ($numWeights != count($samples)) { - throw new Exception('Number of sample weights does not match with number of samples'); + throw new InvalidArgumentException('Number of sample weights does not match with number of samples'); } } else { $this->weights = array_fill(0, count($samples), 1); diff --git a/src/Classification/Linear/LogisticRegression.php b/src/Classification/Linear/LogisticRegression.php index 13f4b8a9..0414d594 100644 --- a/src/Classification/Linear/LogisticRegression.php +++ b/src/Classification/Linear/LogisticRegression.php @@ -6,6 +6,7 @@ use Closure; use Exception; +use Phpml\Exception\InvalidArgumentException; use Phpml\Helper\Optimizer\ConjugateGradient; class LogisticRegression extends Adaline @@ -61,7 +62,7 @@ class LogisticRegression extends Adaline * * Penalty (Regularization term) can be 'L2' or empty string to cancel penalty term * - * @throws \Exception + * @throws InvalidArgumentException */ public function __construct( int $maxIterations = 500, @@ -72,18 +73,24 @@ public function __construct( ) { $trainingTypes = range(self::BATCH_TRAINING, self::CONJUGATE_GRAD_TRAINING); if (!in_array($trainingType, $trainingTypes, true)) { - throw new Exception('Logistic regression can only be trained with '. + throw new InvalidArgumentException( + 'Logistic regression can only be trained with '. 'batch (gradient descent), online (stochastic gradient descent) '. - 'or conjugate batch (conjugate gradients) algorithms'); + 'or conjugate batch (conjugate gradients) algorithms' + ); } if (!in_array($cost, ['log', 'sse'], true)) { - throw new Exception("Logistic regression cost function can be one of the following: \n". - "'log' for log-likelihood and 'sse' for sum of squared errors"); + throw new InvalidArgumentException( + "Logistic regression cost function can be one of the following: \n". + "'log' for log-likelihood and 'sse' for sum of squared errors" + ); } if ($penalty != '' && strtoupper($penalty) !== 'L2') { - throw new Exception("Logistic regression supports only 'L2' regularization"); + throw new InvalidArgumentException( + "Logistic regression supports only 'L2' regularization" + ); } $this->learningRate = 0.001; @@ -140,7 +147,8 @@ protected function runTraining(array $samples, array $targets): void return; default: - throw new Exception('Logistic regression has invalid training type: %s.', $this->trainingType); + // Not reached + throw new Exception(sprintf('Logistic regression has invalid training type: %d.', $this->trainingType)); } } @@ -232,6 +240,7 @@ protected function getCostFunction(): Closure return $callback; default: + // Not reached throw new Exception(sprintf('Logistic regression has invalid cost function: %s.', $this->costFunction)); } } diff --git a/src/Classification/Linear/Perceptron.php b/src/Classification/Linear/Perceptron.php index 5fffc01d..038b4c88 100644 --- a/src/Classification/Linear/Perceptron.php +++ b/src/Classification/Linear/Perceptron.php @@ -5,8 +5,8 @@ namespace Phpml\Classification\Linear; use Closure; -use Exception; use Phpml\Classification\Classifier; +use Phpml\Exception\InvalidArgumentException; use Phpml\Helper\OneVsRest; use Phpml\Helper\Optimizer\GD; use Phpml\Helper\Optimizer\StochasticGD; @@ -70,16 +70,16 @@ class Perceptron implements Classifier, IncrementalEstimator * @param float $learningRate Value between 0.0(exclusive) and 1.0(inclusive) * @param int $maxIterations Must be at least 1 * - * @throws \Exception + * @throws InvalidArgumentException */ public function __construct(float $learningRate = 0.001, int $maxIterations = 1000, bool $normalizeInputs = true) { if ($learningRate <= 0.0 || $learningRate > 1.0) { - throw new Exception('Learning rate should be a float value between 0.0(exclusive) and 1.0(inclusive)'); + throw new InvalidArgumentException('Learning rate should be a float value between 0.0(exclusive) and 1.0(inclusive)'); } if ($maxIterations <= 0) { - throw new Exception('Maximum number of iterations must be an integer greater than 0'); + throw new InvalidArgumentException('Maximum number of iterations must be an integer greater than 0'); } if ($normalizeInputs) { diff --git a/src/DimensionReduction/KernelPCA.php b/src/DimensionReduction/KernelPCA.php index b962b3d4..29deb4c4 100644 --- a/src/DimensionReduction/KernelPCA.php +++ b/src/DimensionReduction/KernelPCA.php @@ -6,6 +6,8 @@ use Closure; use Exception; +use Phpml\Exception\InvalidArgumentException; +use Phpml\Exception\InvalidOperationException; use Phpml\Math\Distance\Euclidean; use Phpml\Math\Distance\Manhattan; use Phpml\Math\Matrix; @@ -53,13 +55,13 @@ class KernelPCA extends PCA * @param int $numFeatures Number of columns to be returned * @param float $gamma Gamma parameter is used with RBF and Sigmoid kernels * - * @throws \Exception + * @throws InvalidArgumentException */ public function __construct(int $kernel = self::KERNEL_RBF, ?float $totalVariance = null, ?int $numFeatures = null, ?float $gamma = null) { $availableKernels = [self::KERNEL_RBF, self::KERNEL_SIGMOID, self::KERNEL_LAPLACIAN, self::KERNEL_LINEAR]; if (!in_array($kernel, $availableKernels, true)) { - throw new Exception('KernelPCA can be initialized with the following kernels only: Linear, RBF, Sigmoid and Laplacian'); + throw new InvalidArgumentException('KernelPCA can be initialized with the following kernels only: Linear, RBF, Sigmoid and Laplacian'); } parent::__construct($totalVariance, $numFeatures); @@ -97,16 +99,17 @@ public function fit(array $data): array * Transforms the given sample to a lower dimensional vector by using * the variables obtained during the last run of fit. * - * @throws \Exception + * @throws InvalidArgumentException + * @throws InvalidOperationException */ public function transform(array $sample): array { if (!$this->fit) { - throw new Exception('KernelPCA has not been fitted with respect to original dataset, please run KernelPCA::fit() first'); + throw new InvalidOperationException('KernelPCA has not been fitted with respect to original dataset, please run KernelPCA::fit() first'); } if (is_array($sample[0])) { - throw new Exception('KernelPCA::transform() accepts only one-dimensional arrays'); + throw new InvalidArgumentException('KernelPCA::transform() accepts only one-dimensional arrays'); } $pairs = $this->getDistancePairs($sample); @@ -199,6 +202,7 @@ protected function getKernel(): Closure }; default: + // Not reached throw new Exception(sprintf('KernelPCA initialized with invalid kernel: %d', $this->kernel)); } } diff --git a/src/DimensionReduction/LDA.php b/src/DimensionReduction/LDA.php index d188205a..68ab0cda 100644 --- a/src/DimensionReduction/LDA.php +++ b/src/DimensionReduction/LDA.php @@ -4,7 +4,8 @@ namespace Phpml\DimensionReduction; -use Exception; +use Phpml\Exception\InvalidArgumentException; +use Phpml\Exception\InvalidOperationException; use Phpml\Math\Matrix; class LDA extends EigenTransformerBase @@ -46,20 +47,20 @@ class LDA extends EigenTransformerBase * @param float|null $totalVariance Total explained variance to be preserved * @param int|null $numFeatures Number of features to be preserved * - * @throws \Exception + * @throws InvalidArgumentException */ public function __construct(?float $totalVariance = null, ?int $numFeatures = null) { if ($totalVariance !== null && ($totalVariance < 0.1 || $totalVariance > 0.99)) { - throw new Exception('Total variance can be a value between 0.1 and 0.99'); + throw new InvalidArgumentException('Total variance can be a value between 0.1 and 0.99'); } if ($numFeatures !== null && $numFeatures <= 0) { - throw new Exception('Number of features to be preserved should be greater than 0'); + throw new InvalidArgumentException('Number of features to be preserved should be greater than 0'); } - if ($totalVariance !== null && $numFeatures !== null) { - throw new Exception('Either totalVariance or numFeatures should be specified in order to run the algorithm'); + if (($totalVariance !== null) === ($numFeatures !== null)) { + throw new InvalidArgumentException('Either totalVariance or numFeatures should be specified in order to run the algorithm'); } if ($numFeatures !== null) { @@ -94,12 +95,12 @@ public function fit(array $data, array $classes): array * Transforms the given sample to a lower dimensional vector by using * the eigenVectors obtained in the last run of fit. * - * @throws \Exception + * @throws InvalidOperationException */ public function transform(array $sample): array { if (!$this->fit) { - throw new Exception('LDA has not been fitted with respect to original dataset, please run LDA::fit() first'); + throw new InvalidOperationException('LDA has not been fitted with respect to original dataset, please run LDA::fit() first'); } if (!is_array($sample[0])) { diff --git a/src/DimensionReduction/PCA.php b/src/DimensionReduction/PCA.php index 18879bbd..a3d8a4d0 100644 --- a/src/DimensionReduction/PCA.php +++ b/src/DimensionReduction/PCA.php @@ -4,7 +4,8 @@ namespace Phpml\DimensionReduction; -use Exception; +use Phpml\Exception\InvalidArgumentException; +use Phpml\Exception\InvalidOperationException; use Phpml\Math\Statistic\Covariance; use Phpml\Math\Statistic\Mean; @@ -31,20 +32,20 @@ class PCA extends EigenTransformerBase * @param float $totalVariance Total explained variance to be preserved * @param int $numFeatures Number of features to be preserved * - * @throws \Exception + * @throws InvalidArgumentException */ public function __construct(?float $totalVariance = null, ?int $numFeatures = null) { if ($totalVariance !== null && ($totalVariance < 0.1 || $totalVariance > 0.99)) { - throw new Exception('Total variance can be a value between 0.1 and 0.99'); + throw new InvalidArgumentException('Total variance can be a value between 0.1 and 0.99'); } if ($numFeatures !== null && $numFeatures <= 0) { - throw new Exception('Number of features to be preserved should be greater than 0'); + throw new InvalidArgumentException('Number of features to be preserved should be greater than 0'); } - if ($totalVariance !== null && $numFeatures !== null) { - throw new Exception('Either totalVariance or numFeatures should be specified in order to run the algorithm'); + if (($totalVariance !== null) === ($numFeatures !== null)) { + throw new InvalidArgumentException('Either totalVariance or numFeatures should be specified in order to run the algorithm'); } if ($numFeatures !== null) { @@ -81,12 +82,12 @@ public function fit(array $data): array * Transforms the given sample to a lower dimensional vector by using * the eigenVectors obtained in the last run of fit. * - * @throws \Exception + * @throws InvalidOperationException */ public function transform(array $sample): array { if (!$this->fit) { - throw new Exception('PCA has not been fitted with respect to original dataset, please run PCA::fit() first'); + throw new InvalidOperationException('PCA has not been fitted with respect to original dataset, please run PCA::fit() first'); } if (!is_array($sample[0])) { diff --git a/tests/Classification/Ensemble/AdaBoostTest.php b/tests/Classification/Ensemble/AdaBoostTest.php index 7677c319..095cde03 100644 --- a/tests/Classification/Ensemble/AdaBoostTest.php +++ b/tests/Classification/Ensemble/AdaBoostTest.php @@ -5,12 +5,32 @@ namespace Phpml\Tests\Classification\Ensemble; use Phpml\Classification\Ensemble\AdaBoost; +use Phpml\Exception\InvalidArgumentException; use Phpml\ModelManager; use PHPUnit\Framework\TestCase; class AdaBoostTest extends TestCase { - public function testPredictSingleSample() + public function testTrainThrowWhenMultiClassTargetGiven(): void + { + $samples = [ + [0, 0], + [0.5, 0.5], + [1, 1], + ]; + $targets = [ + 0, + 1, + 2, + ]; + + $classifier = new AdaBoost(); + + $this->expectException(InvalidArgumentException::class); + $classifier->train($samples, $targets); + } + + public function testPredictSingleSample(): void { // AND problem $samples = [[0.1, 0.3], [1, 0], [0, 1], [1, 1], [0.9, 0.8], [1.1, 1.1]]; @@ -38,8 +58,6 @@ public function testPredictSingleSample() $this->assertEquals(0, $classifier->predict([0.1, 0.1])); $this->assertEquals(1, $classifier->predict([0, 0.999])); $this->assertEquals(0, $classifier->predict([1.1, 0.8])); - - return $classifier; } public function testSaveAndRestore(): void diff --git a/tests/Classification/Ensemble/BaggingTest.php b/tests/Classification/Ensemble/BaggingTest.php index 69c4d010..5b2e47bf 100644 --- a/tests/Classification/Ensemble/BaggingTest.php +++ b/tests/Classification/Ensemble/BaggingTest.php @@ -7,6 +7,7 @@ use Phpml\Classification\DecisionTree; use Phpml\Classification\Ensemble\Bagging; use Phpml\Classification\NaiveBayes; +use Phpml\Exception\InvalidArgumentException; use Phpml\ModelManager; use PHPUnit\Framework\TestCase; @@ -34,7 +35,15 @@ class BaggingTest extends TestCase ['scorching', 0, 0, 'false', 'Dont_play'], ]; - public function testPredictSingleSample() + public function testSetSubsetRatioThrowWhenRatioOutOfBounds(): void + { + $classifier = $this->getClassifier(); + + $this->expectException(InvalidArgumentException::class); + $classifier->setSubsetRatio(0); + } + + public function testPredictSingleSample(): void { [$data, $targets] = $this->getData($this->data); $classifier = $this->getClassifier(); @@ -48,8 +57,6 @@ public function testPredictSingleSample() $classifier->train($data, $targets); $this->assertEquals('Dont_play', $classifier->predict(['scorching', 95, 90, 'true'])); $this->assertEquals('Play', $classifier->predict(['overcast', 60, 60, 'false'])); - - return $classifier; } public function testSaveAndRestore(): void diff --git a/tests/Classification/Linear/AdalineTest.php b/tests/Classification/Linear/AdalineTest.php index c8ca7529..62224e87 100644 --- a/tests/Classification/Linear/AdalineTest.php +++ b/tests/Classification/Linear/AdalineTest.php @@ -5,11 +5,23 @@ namespace Phpml\Tests\Classification\Linear; use Phpml\Classification\Linear\Adaline; +use Phpml\Exception\InvalidArgumentException; use Phpml\ModelManager; use PHPUnit\Framework\TestCase; class AdalineTest extends TestCase { + public function testAdalineThrowWhenInvalidTrainingType(): void + { + $this->expectException(InvalidArgumentException::class); + $classifier = new Adaline( + 0.001, + 1000, + true, + 0 + ); + } + public function testPredictSingleSample(): void { // AND problem diff --git a/tests/Classification/Linear/DecisionStumpTest.php b/tests/Classification/Linear/DecisionStumpTest.php index 93c8595d..1295ac53 100644 --- a/tests/Classification/Linear/DecisionStumpTest.php +++ b/tests/Classification/Linear/DecisionStumpTest.php @@ -5,11 +5,24 @@ namespace Phpml\Tests\Classification\Linear; use Phpml\Classification\Linear\DecisionStump; +use Phpml\Exception\InvalidArgumentException; use Phpml\ModelManager; use PHPUnit\Framework\TestCase; class DecisionStumpTest extends TestCase { + public function testTrainThrowWhenSample(): void + { + $samples = [[0, 0], [1, 0], [0, 1], [1, 1]]; + $targets = [0, 0, 1, 1]; + + $classifier = new DecisionStump(); + $classifier->setSampleWeights([0.1, 0.1, 0.1]); + + $this->expectException(InvalidArgumentException::class); + $classifier->train($samples, $targets); + } + public function testPredictSingleSample() { // Samples should be separable with a line perpendicular diff --git a/tests/Classification/Linear/LogisticRegressionTest.php b/tests/Classification/Linear/LogisticRegressionTest.php index ed9b878d..37b51828 100644 --- a/tests/Classification/Linear/LogisticRegressionTest.php +++ b/tests/Classification/Linear/LogisticRegressionTest.php @@ -5,16 +5,16 @@ namespace Phpml\Tests\Classification\Linear; use Phpml\Classification\Linear\LogisticRegression; +use Phpml\Exception\InvalidArgumentException; use PHPUnit\Framework\TestCase; use ReflectionMethod; use ReflectionProperty; -use Throwable; class LogisticRegressionTest extends TestCase { public function testConstructorThrowWhenInvalidTrainingType(): void { - $this->expectException(Throwable::class); + $this->expectException(InvalidArgumentException::class); $classifier = new LogisticRegression( 500, @@ -27,7 +27,7 @@ public function testConstructorThrowWhenInvalidTrainingType(): void public function testConstructorThrowWhenInvalidCost(): void { - $this->expectException(Throwable::class); + $this->expectException(InvalidArgumentException::class); $classifier = new LogisticRegression( 500, @@ -40,7 +40,7 @@ public function testConstructorThrowWhenInvalidCost(): void public function testConstructorThrowWhenInvalidPenalty(): void { - $this->expectException(Throwable::class); + $this->expectException(InvalidArgumentException::class); $classifier = new LogisticRegression( 500, diff --git a/tests/Classification/Linear/PerceptronTest.php b/tests/Classification/Linear/PerceptronTest.php index 35af855c..731eac13 100644 --- a/tests/Classification/Linear/PerceptronTest.php +++ b/tests/Classification/Linear/PerceptronTest.php @@ -5,11 +5,24 @@ namespace Phpml\Tests\Classification\Linear; use Phpml\Classification\Linear\Perceptron; +use Phpml\Exception\InvalidArgumentException; use Phpml\ModelManager; use PHPUnit\Framework\TestCase; class PerceptronTest extends TestCase { + public function testPerceptronThrowWhenLearningRateOutOfRange(): void + { + $this->expectException(InvalidArgumentException::class); + $classifier = new Perceptron(0, 5000); + } + + public function testPerceptronThrowWhenMaxIterationsOutOfRange(): void + { + $this->expectException(InvalidArgumentException::class); + $classifier = new Perceptron(0.001, 0); + } + public function testPredictSingleSample(): void { // AND problem diff --git a/tests/DimensionReduction/KernelPCATest.php b/tests/DimensionReduction/KernelPCATest.php index 05a4138e..dfd8bb93 100644 --- a/tests/DimensionReduction/KernelPCATest.php +++ b/tests/DimensionReduction/KernelPCATest.php @@ -5,6 +5,8 @@ namespace Phpml\Tests\DimensionReduction; use Phpml\DimensionReduction\KernelPCA; +use Phpml\Exception\InvalidArgumentException; +use Phpml\Exception\InvalidOperationException; use PHPUnit\Framework\TestCase; class KernelPCATest extends TestCase @@ -48,4 +50,34 @@ public function testKernelPCA(): void $newTransformed2 = $kpca->transform($newData); $this->assertEquals(abs($newTransformed[0]), abs($newTransformed2[0]), '', $epsilon); } + + public function testKernelPCAThrowWhenKernelInvalid(): void + { + $this->expectException(InvalidArgumentException::class); + $kpca = new KernelPCA(0, null, 1, 15); + } + + public function testTransformThrowWhenNotFitted(): void + { + $samples = [1, 0]; + + $kpca = new KernelPCA(KernelPCA::KERNEL_RBF, null, 1, 15); + + $this->expectException(InvalidOperationException::class); + $kpca->transform($samples); + } + + public function testTransformThrowWhenMultiDimensionalArrayGiven(): void + { + $samples = [ + [1, 0], + [1, 1], + ]; + + $kpca = new KernelPCA(KernelPCA::KERNEL_RBF, null, 1, 15); + $kpca->fit($samples); + + $this->expectException(InvalidArgumentException::class); + $kpca->transform($samples); + } } diff --git a/tests/DimensionReduction/LDATest.php b/tests/DimensionReduction/LDATest.php index 2803a4b4..641c226a 100644 --- a/tests/DimensionReduction/LDATest.php +++ b/tests/DimensionReduction/LDATest.php @@ -6,6 +6,8 @@ use Phpml\Dataset\Demo\IrisDataset; use Phpml\DimensionReduction\LDA; +use Phpml\Exception\InvalidArgumentException; +use Phpml\Exception\InvalidOperationException; use PHPUnit\Framework\TestCase; class LDATest extends TestCase @@ -62,4 +64,41 @@ public function testLDA(): void array_map($check, $newRow, $newRow2); } } + + public function testLDAThrowWhenTotalVarianceOutOfRange(): void + { + $this->expectException(InvalidArgumentException::class); + $pca = new LDA(0, null); + } + + public function testLDAThrowWhenNumFeaturesOutOfRange(): void + { + $this->expectException(InvalidArgumentException::class); + $pca = new LDA(null, 0); + } + + public function testLDAThrowWhenParameterNotSpecified(): void + { + $this->expectException(InvalidArgumentException::class); + $pca = new LDA(); + } + + public function testLDAThrowWhenBothParameterSpecified(): void + { + $this->expectException(InvalidArgumentException::class); + $pca = new LDA(0.9, 1); + } + + public function testTransformThrowWhenNotFitted(): void + { + $samples = [ + [1, 0], + [1, 1], + ]; + + $pca = new LDA(0.9); + + $this->expectException(InvalidOperationException::class); + $pca->transform($samples); + } } diff --git a/tests/DimensionReduction/PCATest.php b/tests/DimensionReduction/PCATest.php index 337b2533..5fca2113 100644 --- a/tests/DimensionReduction/PCATest.php +++ b/tests/DimensionReduction/PCATest.php @@ -5,6 +5,8 @@ namespace Phpml\Tests\DimensionReduction; use Phpml\DimensionReduction\PCA; +use Phpml\Exception\InvalidArgumentException; +use Phpml\Exception\InvalidOperationException; use PHPUnit\Framework\TestCase; class PCATest extends TestCase @@ -54,4 +56,41 @@ public function testPCA(): void }, $newRow, $newRow2); } } + + public function testPCAThrowWhenTotalVarianceOutOfRange(): void + { + $this->expectException(InvalidArgumentException::class); + $pca = new PCA(0, null); + } + + public function testPCAThrowWhenNumFeaturesOutOfRange(): void + { + $this->expectException(InvalidArgumentException::class); + $pca = new PCA(null, 0); + } + + public function testPCAThrowWhenParameterNotSpecified(): void + { + $this->expectException(InvalidArgumentException::class); + $pca = new PCA(); + } + + public function testPCAThrowWhenBothParameterSpecified(): void + { + $this->expectException(InvalidArgumentException::class); + $pca = new PCA(0.9, 1); + } + + public function testTransformThrowWhenNotFitted(): void + { + $samples = [ + [1, 0], + [1, 1], + ]; + + $pca = new PCA(0.9); + + $this->expectException(InvalidOperationException::class); + $pca->transform($samples); + } } From e1560765392121ec3bddbb17f46125ac9b858e56 Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Wed, 7 Mar 2018 23:16:25 +0100 Subject: [PATCH 270/328] Add DecisionTreeLeaf.getNodeImpurityDecrease test (#261) --- .../DecisionTree/DecisionTreeLeafTest.php | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/Classification/DecisionTree/DecisionTreeLeafTest.php b/tests/Classification/DecisionTree/DecisionTreeLeafTest.php index abcf10da..e6948782 100644 --- a/tests/Classification/DecisionTree/DecisionTreeLeafTest.php +++ b/tests/Classification/DecisionTree/DecisionTreeLeafTest.php @@ -23,4 +23,28 @@ public function testHTMLOutput(): void $this->assertEquals('
${value}
| Yes
'.$this->leftLeaf->getHTML($columnNames).'
 No |
'.$this->rightLeaf->getHTML($columnNames).'
col_0 =1
Gini: 0.00
 No |
col_1 <= 2
Gini: 0.00
', $leaf->getHTML()); } + + public function testNodeImpurityDecreaseShouldBeZeroWhenLeafIsTerminal(): void + { + $leaf = new DecisionTreeLeaf(); + $leaf->isTerminal = true; + + $this->assertEquals(0.0, $leaf->getNodeImpurityDecrease(1)); + } + + public function testNodeImpurityDecrease(): void + { + $leaf = new DecisionTreeLeaf(); + $leaf->giniIndex = 0.5; + $leaf->records = [1, 2, 3]; + + $leaf->leftLeaf = new DecisionTreeLeaf(); + $leaf->leftLeaf->records = [5, 2]; + + $leaf->rightLeaf = new DecisionTreeLeaf(); + $leaf->rightLeaf->records = []; + $leaf->rightLeaf->giniIndex = 0.3; + + $this->assertSame(0.75, $leaf->getNodeImpurityDecrease(2)); + } } From 0d80c78c574d017fa3a77c56f7c0eb80eabb0e40 Mon Sep 17 00:00:00 2001 From: Mustafa Karabulut Date: Thu, 8 Mar 2018 20:19:09 +0200 Subject: [PATCH 271/328] Micro optimization for matrix multiplication (#255) * Micro optimization for matrix multiplication * code cs fix * added a comment block for the change --- src/Math/Matrix.php | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/Math/Matrix.php b/src/Math/Matrix.php index f69d8e6a..ae89d203 100644 --- a/src/Math/Matrix.php +++ b/src/Math/Matrix.php @@ -142,15 +142,24 @@ public function multiply(self $matrix): self throw new InvalidArgumentException('Inconsistent matrix supplied'); } + $array1 = $this->toArray(); + $array2 = $matrix->toArray(); + $colCount = $matrix->columns; + + /* + - To speed-up multiplication, we need to avoid use of array index operator [ ] as much as possible( See #255 for details) + - A combination of "foreach" and "array_column" works much faster then accessing the array via index operator + */ $product = []; - $multiplier = $matrix->toArray(); - for ($i = 0; $i < $this->rows; ++$i) { - $columns = $matrix->getColumns(); - for ($j = 0; $j < $columns; ++$j) { - $product[$i][$j] = 0; - for ($k = 0; $k < $this->columns; ++$k) { - $product[$i][$j] += $this->matrix[$i][$k] * $multiplier[$k][$j]; + foreach ($array1 as $row => $rowData) { + for ($col = 0; $col < $colCount; ++$col) { + $columnData = array_column($array2, $col); + $sum = 0; + foreach ($rowData as $key => $valueData) { + $sum += $valueData * $columnData[$key]; } + + $product[$row][$col] = $sum; } } From af2d732194176cf4a923764cf7d40b483e5932f0 Mon Sep 17 00:00:00 2001 From: Ivana Momcilovic Date: Thu, 8 Mar 2018 22:27:16 +0100 Subject: [PATCH 272/328] KMeans associative clustering (#262) * KMeans associative clustering added * fix travis error * KMeans will return provided keys as point label if they are provided * fix travis * fix travis --- docs/machine-learning/clustering/k-means.md | 4 +++- src/Clustering/KMeans.php | 6 ++--- src/Clustering/KMeans/Cluster.php | 6 ++++- src/Clustering/KMeans/Point.php | 8 ++++++- src/Clustering/KMeans/Space.php | 8 +++---- tests/Clustering/KMeansTest.php | 26 +++++++++++++++++++++ 6 files changed, 48 insertions(+), 10 deletions(-) diff --git a/docs/machine-learning/clustering/k-means.md b/docs/machine-learning/clustering/k-means.md index 296feb13..661f7172 100644 --- a/docs/machine-learning/clustering/k-means.md +++ b/docs/machine-learning/clustering/k-means.md @@ -19,10 +19,12 @@ To divide the samples into clusters simply use `cluster` method. It's return the ``` $samples = [[1, 1], [8, 7], [1, 2], [7, 8], [2, 1], [8, 9]]; +Or if you need to keep your indentifiers along with yours samples you can use array keys as labels. +$samples = [ 'Label1' => [1, 1], 'Label2' => [8, 7], 'Label3' => [1, 2]]; $kmeans = new KMeans(2); $kmeans->cluster($samples); -// return [0=>[[1, 1], ...], 1=>[[8, 7], ...]] +// return [0=>[[1, 1], ...], 1=>[[8, 7], ...]] or [0=>['Label1' => [1, 1], 'Label3' => [1, 2], ...], 1=>['Label2' => [8, 7], ...]] ``` ### Initialization methods diff --git a/src/Clustering/KMeans.php b/src/Clustering/KMeans.php index 86ad754b..1aff1c4e 100644 --- a/src/Clustering/KMeans.php +++ b/src/Clustering/KMeans.php @@ -35,9 +35,9 @@ public function __construct(int $clustersNumber, int $initialization = self::INI public function cluster(array $samples): array { - $space = new Space(count($samples[0])); - foreach ($samples as $sample) { - $space->addPoint($sample); + $space = new Space(count(reset($samples))); + foreach ($samples as $key => $sample) { + $space->addPoint($sample, $key); } $clusters = []; diff --git a/src/Clustering/KMeans/Cluster.php b/src/Clustering/KMeans/Cluster.php index 89369263..731d79c4 100644 --- a/src/Clustering/KMeans/Cluster.php +++ b/src/Clustering/KMeans/Cluster.php @@ -32,7 +32,11 @@ public function getPoints(): array { $points = []; foreach ($this->points as $point) { - $points[] = $point->toArray(); + if (!empty($point->label)) { + $points[$point->label] = $point->toArray(); + } else { + $points[] = $point->toArray(); + } } return $points; diff --git a/src/Clustering/KMeans/Point.php b/src/Clustering/KMeans/Point.php index 8c918a74..7d41093c 100644 --- a/src/Clustering/KMeans/Point.php +++ b/src/Clustering/KMeans/Point.php @@ -18,10 +18,16 @@ class Point implements ArrayAccess */ protected $coordinates = []; - public function __construct(array $coordinates) + /** + * @var mixed + */ + protected $label; + + public function __construct(array $coordinates, $label = null) { $this->dimension = count($coordinates); $this->coordinates = $coordinates; + $this->label = $label; } public function toArray(): array diff --git a/src/Clustering/KMeans/Space.php b/src/Clustering/KMeans/Space.php index b85b329f..8d80dc06 100644 --- a/src/Clustering/KMeans/Space.php +++ b/src/Clustering/KMeans/Space.php @@ -35,21 +35,21 @@ public function toArray(): array return ['points' => $points]; } - public function newPoint(array $coordinates): Point + public function newPoint(array $coordinates, $label = null): Point { if (count($coordinates) != $this->dimension) { throw new LogicException('('.implode(',', $coordinates).') is not a point of this space'); } - return new Point($coordinates); + return new Point($coordinates, $label); } /** * @param null $data */ - public function addPoint(array $coordinates, $data = null): void + public function addPoint(array $coordinates, $label = null, $data = null): void { - $this->attach($this->newPoint($coordinates), $data); + $this->attach($this->newPoint($coordinates, $label), $data); } /** diff --git a/tests/Clustering/KMeansTest.php b/tests/Clustering/KMeansTest.php index 032c8048..ba36bc63 100644 --- a/tests/Clustering/KMeansTest.php +++ b/tests/Clustering/KMeansTest.php @@ -28,6 +28,32 @@ public function testKMeansSamplesClustering(): void $this->assertCount(0, $samples); } + public function testKMeansSamplesLabeledClustering(): void + { + $samples = [ + '555' => [1, 1], + '666' => [8, 7], + 'ABC' => [1, 2], + 'DEF' => [7, 8], + 668 => [2, 1], + [8, 9], + ]; + + $kmeans = new KMeans(2); + $clusters = $kmeans->cluster($samples); + + $this->assertCount(2, $clusters); + + foreach ($samples as $index => $sample) { + if (in_array($sample, $clusters[0], true) || in_array($sample, $clusters[1], true)) { + $this->assertArrayHasKey($index, $clusters[0] + $clusters[1]); + unset($samples[$index]); + } + } + + $this->assertCount(0, $samples); + } + public function testKMeansInitializationMethods(): void { $samples = [ From a36fe086d3d4cbdac625740c703fc51530ab58d9 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sat, 10 Mar 2018 21:48:16 +0100 Subject: [PATCH 273/328] Add performance test for LeastSquares (#263) * Install phpbench :rocket: * Add first benchmark for LeastSquares * Update README and CONTRIBUTING guide * Fix typo --- .gitignore | 1 + CONTRIBUTING.md | 24 +- README.md | 18 +- composer.json | 1 + composer.lock | 406 +++++++++++++++++- docs/index.md | 14 +- phpbench.json | 17 + tests/Performance/Data/.gitkeep | 0 .../Regression/LeastSquaresBench.php | 40 ++ tests/Performance/bootstrap.php | 20 + 10 files changed, 526 insertions(+), 15 deletions(-) create mode 100644 phpbench.json create mode 100644 tests/Performance/Data/.gitkeep create mode 100644 tests/Performance/Regression/LeastSquaresBench.php create mode 100644 tests/Performance/bootstrap.php diff --git a/.gitignore b/.gitignore index 3ffb1da1..5fb4f2b0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /vendor/ .php_cs.cache /build +/tests/Performance/Data/*.csv diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b2d5f9e3..68dd849e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ code base clean, unified and future proof. You should only open pull requests against the `master` branch. -## Unit-Tests +## Unit Tests Please try to add a test for your pull-request. You can run the unit-tests by calling: @@ -16,6 +16,22 @@ Please try to add a test for your pull-request. You can run the unit-tests by ca vendor/bin/phpunit ``` +## Performance Tests + +Before first run bootstrap script will download all necessary datasets from public repository `php-ai/php-ml-datasets`. + +Time performance tests: + +```bash +vendor/bin/phpbench run --report=time +``` + +Memory performance tests: + +```bash +vendor/bin/phpbench run --report=memory +``` + ## Travis GitHub automatically run your pull request through Travis CI. @@ -23,21 +39,21 @@ If you break the tests, I cannot merge your code, so please make sure that your ## Merge -Please allow me time to review your pull requests. I will give my best to review everything as fast as possible, but cannot always live up to my own expectations. +Please give me time to review your pull requests. I will give my best to review everything as fast as possible, but cannot always live up to my own expectations. ## Coding Standards & Static Analysis When contributing code to PHP-ML, you must follow its coding standards. To do that, just run: ```bash -vendor/bin/ecs check src tests --fix +composer fix-cs ``` [More about EasyCodingStandard](https://github.com/Symplify/EasyCodingStandard) Code has to also pass static analysis by [PHPStan](https://github.com/phpstan/phpstan): ```bash -vendor/bin/phpstan.phar analyse src tests --level max --configuration phpstan.neon +composer phpstan ``` diff --git a/README.md b/README.md index ddca60a1..192195ef 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,9 @@ [![Coverage Status](https://coveralls.io/repos/github/php-ai/php-ml/badge.svg?branch=master)](https://coveralls.io/github/php-ai/php-ml?branch=master) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/php-ai/php-ml/?branch=master) - - - -![PHP-ML - Machine Learning library for PHP](docs/assets/php-ml-logo.png) +

+ +

Fresh approach to Machine Learning in PHP. Algorithms, Cross Validation, Neural Network, Preprocessing, Feature Extraction and much more in one library. @@ -35,6 +33,11 @@ echo $classifier->predict([3, 2]); // return 'b' ``` +## Awards + + + + ## Documentation To find out how to use PHP-ML follow [Documentation](http://php-ml.readthedocs.org/). @@ -51,6 +54,10 @@ composer require php-ai/php-ml Example scripts are available in a separate repository [php-ai/php-ml-examples](https://github.com/php-ai/php-ml-examples). +## Datasets + +Public datasets are available in a separate repository [php-ai/php-ml-datasets](https://github.com/php-ai/php-ml-datasets). + ## Features * Association rule learning @@ -120,6 +127,7 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( ## Contribute +- [Guide: CONTRIBUTING.md](https://github.com/php-ai/php-ml/blob/master/CONTRIBUTING.md) - [Issue Tracker: github.com/php-ai/php-ml](https://github.com/php-ai/php-ml/issues) - [Source Code: github.com/php-ai/php-ml](https://github.com/php-ai/php-ml) diff --git a/composer.json b/composer.json index c8652d9b..c809822d 100644 --- a/composer.json +++ b/composer.json @@ -23,6 +23,7 @@ "php": "^7.1" }, "require-dev": { + "phpbench/phpbench": "^0.14.0", "phpstan/phpstan-phpunit": "^0.9.4", "phpstan/phpstan-shim": "^0.9", "phpstan/phpstan-strict-rules": "^0.9.0", diff --git a/composer.lock b/composer.lock index 0462e945..0eeb6935 100644 --- a/composer.lock +++ b/composer.lock @@ -4,9 +4,64 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "9135b3dece8c6938922f757123274e95", + "content-hash": "fc88078118714e9fb6b13a02dba0bd00", "packages": [], "packages-dev": [ + { + "name": "beberlei/assert", + "version": "v2.9.2", + "source": { + "type": "git", + "url": "https://github.com/beberlei/assert.git", + "reference": "2d555f72f3f4ff9e72a7bc17cb8a53c86737c8a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/beberlei/assert/zipball/2d555f72f3f4ff9e72a7bc17cb8a53c86737c8a0", + "reference": "2d555f72f3f4ff9e72a7bc17cb8a53c86737c8a0", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.1.1", + "phpunit/phpunit": "^4.8.35|^5.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Assert\\": "lib/Assert" + }, + "files": [ + "lib/Assert/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de", + "role": "Lead Developer" + }, + { + "name": "Richard Quadling", + "email": "rquadling@gmail.com", + "role": "Collaborator" + } + ], + "description": "Thin assertion library for input validation in business models.", + "keywords": [ + "assert", + "assertion", + "validation" + ], + "time": "2018-01-25T13:33:16+00:00" + }, { "name": "composer/semver", "version": "1.4.2", @@ -375,6 +430,144 @@ ], "time": "2017-08-23T07:46:41+00:00" }, + { + "name": "lstrojny/functional-php", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/lstrojny/functional-php.git", + "reference": "7c2091ddea572e012aa980e5d19d242d3a06ad5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lstrojny/functional-php/zipball/7c2091ddea572e012aa980e5d19d242d3a06ad5b", + "reference": "7c2091ddea572e012aa980e5d19d242d3a06ad5b", + "shasum": "" + }, + "require": { + "php": "~7" + }, + "require-dev": { + "phpunit/phpunit": "~6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "Functional\\": "src/Functional" + }, + "files": [ + "src/Functional/Average.php", + "src/Functional/Capture.php", + "src/Functional/ConstFunction.php", + "src/Functional/CompareOn.php", + "src/Functional/CompareObjectHashOn.php", + "src/Functional/Compose.php", + "src/Functional/Concat.php", + "src/Functional/Contains.php", + "src/Functional/Curry.php", + "src/Functional/CurryN.php", + "src/Functional/Difference.php", + "src/Functional/DropFirst.php", + "src/Functional/DropLast.php", + "src/Functional/Each.php", + "src/Functional/Equal.php", + "src/Functional/ErrorToException.php", + "src/Functional/Every.php", + "src/Functional/False.php", + "src/Functional/Falsy.php", + "src/Functional/Filter.php", + "src/Functional/First.php", + "src/Functional/FirstIndexOf.php", + "src/Functional/FlatMap.php", + "src/Functional/Flatten.php", + "src/Functional/Flip.php", + "src/Functional/GreaterThan.php", + "src/Functional/GreaterThanOrEqual.php", + "src/Functional/Group.php", + "src/Functional/Head.php", + "src/Functional/Id.php", + "src/Functional/IfElse.php", + "src/Functional/Identical.php", + "src/Functional/IndexesOf.php", + "src/Functional/Intersperse.php", + "src/Functional/Invoke.php", + "src/Functional/InvokeFirst.php", + "src/Functional/InvokeIf.php", + "src/Functional/InvokeLast.php", + "src/Functional/Invoker.php", + "src/Functional/Last.php", + "src/Functional/LastIndexOf.php", + "src/Functional/LessThan.php", + "src/Functional/LessThanOrEqual.php", + "src/Functional/LexicographicCompare.php", + "src/Functional/Map.php", + "src/Functional/Match.php", + "src/Functional/Maximum.php", + "src/Functional/Memoize.php", + "src/Functional/Minimum.php", + "src/Functional/None.php", + "src/Functional/Not.php", + "src/Functional/PartialAny.php", + "src/Functional/PartialLeft.php", + "src/Functional/PartialMethod.php", + "src/Functional/PartialRight.php", + "src/Functional/Partition.php", + "src/Functional/Pick.php", + "src/Functional/Pluck.php", + "src/Functional/Poll.php", + "src/Functional/Product.php", + "src/Functional/Ratio.php", + "src/Functional/ReduceLeft.php", + "src/Functional/ReduceRight.php", + "src/Functional/Reindex.php", + "src/Functional/Reject.php", + "src/Functional/Retry.php", + "src/Functional/Select.php", + "src/Functional/SelectKeys.php", + "src/Functional/SequenceConstant.php", + "src/Functional/SequenceExponential.php", + "src/Functional/SequenceLinear.php", + "src/Functional/Some.php", + "src/Functional/Sort.php", + "src/Functional/Sum.php", + "src/Functional/SuppressError.php", + "src/Functional/Tap.php", + "src/Functional/Tail.php", + "src/Functional/TailRecursion.php", + "src/Functional/True.php", + "src/Functional/Truthy.php", + "src/Functional/Unique.php", + "src/Functional/With.php", + "src/Functional/Zip.php", + "src/Functional/ZipAll.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Strojny", + "email": "lstrojny@php.net", + "homepage": "http://usrportage.de" + }, + { + "name": "Max Beutel", + "email": "nash12@gmail.com" + } + ], + "description": "Functional primitives for PHP", + "keywords": [ + "functional" + ], + "time": "2018-01-03T10:08:50+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.7.0", @@ -1072,6 +1265,168 @@ ], "time": "2017-10-19T09:58:18+00:00" }, + { + "name": "phpbench/container", + "version": "1.2", + "source": { + "type": "git", + "url": "https://github.com/phpbench/container.git", + "reference": "c0e3cbf1cd8f867c70b029cb6d1b0b39fe6d409d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/container/zipball/c0e3cbf1cd8f867c70b029cb6d1b0b39fe6d409d", + "reference": "c0e3cbf1cd8f867c70b029cb6d1b0b39fe6d409d", + "shasum": "" + }, + "require": { + "psr/container": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpBench\\DependencyInjection\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "Simple, configurable, service container.", + "time": "2018-02-12T08:08:59+00:00" + }, + { + "name": "phpbench/dom", + "version": "0.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpbench/dom.git", + "reference": "b135378dd0004c05ba5446aeddaf0b83339c1c4c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/dom/zipball/b135378dd0004c05ba5446aeddaf0b83339c1c4c", + "reference": "b135378dd0004c05ba5446aeddaf0b83339c1c4c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": "^5.4|^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpBench\\Dom\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "DOM wrapper to simplify working with the PHP DOM implementation", + "time": "2016-02-27T12:15:56+00:00" + }, + { + "name": "phpbench/phpbench", + "version": "0.14.0", + "source": { + "type": "git", + "url": "https://github.com/phpbench/phpbench.git", + "reference": "ea2c7ca1cdbfa952b8d50c4f36fc233dbfabe3c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/phpbench/zipball/ea2c7ca1cdbfa952b8d50c4f36fc233dbfabe3c9", + "reference": "ea2c7ca1cdbfa952b8d50c4f36fc233dbfabe3c9", + "shasum": "" + }, + "require": { + "beberlei/assert": "^2.4", + "doctrine/annotations": "^1.2.7", + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "lstrojny/functional-php": "1.0|^1.2.3", + "php": "^7.0", + "phpbench/container": "~1.0", + "phpbench/dom": "~0.2.0", + "seld/jsonlint": "^1.0", + "symfony/console": "^2.6|^3.0|^4.0", + "symfony/debug": "^2.4|^3.0|^4.0", + "symfony/filesystem": "^2.4|^3.0|^4.0", + "symfony/finder": "^2.4|^3.0|^4.0", + "symfony/options-resolver": "^2.6|^3.0|^4.0", + "symfony/process": "^2.1|^3.0|^4.0" + }, + "require-dev": { + "doctrine/dbal": "^2.4", + "liip/rmt": "^1.2", + "padraic/phar-updater": "^1.0", + "phpstan/phpstan": "^0.8.5", + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-xdebug": "For XDebug profiling extension." + }, + "bin": [ + "bin/phpbench" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpBench\\": "lib/", + "PhpBench\\Extensions\\Dbal\\": "extensions/dbal/lib/", + "PhpBench\\Extensions\\XDebug\\": "extensions/xdebug/lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "PHP Benchmarking Framework", + "time": "2017-12-05T15:55:57+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "1.0.1", @@ -2457,6 +2812,55 @@ "homepage": "https://github.com/sebastianbergmann/version", "time": "2016-10-03T07:35:21+00:00" }, + { + "name": "seld/jsonlint", + "version": "1.7.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "d15f59a67ff805a44c50ea0516d2341740f81a38" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/d15f59a67ff805a44c50ea0516d2341740f81a38", + "reference": "d15f59a67ff805a44c50ea0516d2341740f81a38", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "bin": [ + "bin/jsonlint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "time": "2018-01-24T12:46:19+00:00" + }, { "name": "slevomat/coding-standard", "version": "4.2.1", diff --git a/docs/index.md b/docs/index.md index 2e204e87..4ba9d563 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,11 +9,9 @@ [![Coverage Status](https://coveralls.io/repos/github/php-ai/php-ml/badge.svg?branch=master)](https://coveralls.io/github/php-ai/php-ml?branch=master) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/php-ai/php-ml/?branch=master) - - - -![PHP-ML - Machine Learning library for PHP](assets/php-ml-logo.png) +

+ +

Fresh approach to Machine Learning in PHP. Algorithms, Cross Validation, Neural Network, Preprocessing, Feature Extraction and much more in one library. @@ -35,6 +33,11 @@ $classifier->predict([3, 2]); // return 'b' ``` +## Awards + + + + ## Documentation To find out how to use PHP-ML follow [Documentation](http://php-ml.readthedocs.org/). @@ -105,6 +108,7 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( ## Contribute +- Guide: [CONTRIBUTING.md](https://github.com/php-ai/php-ml/blob/master/CONTRIBUTING.md) - Issue Tracker: [github.com/php-ai/php-ml/issues](https://github.com/php-ai/php-ml/issues) - Source Code: [github.com/php-ai/php-ml](https://github.com/php-ai/php-ml) diff --git a/phpbench.json b/phpbench.json new file mode 100644 index 00000000..ea802f71 --- /dev/null +++ b/phpbench.json @@ -0,0 +1,17 @@ +{ + "bootstrap": "tests/Performance/bootstrap.php", + "path": "tests/Performance", + "reports": { + "time": { + "extends": "aggregate", + "title": "The Consumation of Time", + "cols": [ "subject", "mode", "mean", "rstdev", "diff"] + }, + "memory": { + "extends": "aggregate", + "title": "The Memory Usage", + "cols": [ "subject", "mem_real", "mem_final", "mem_peak", "diff"], + "diff_col": "mem_peak" + } + } +} \ No newline at end of file diff --git a/tests/Performance/Data/.gitkeep b/tests/Performance/Data/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/Performance/Regression/LeastSquaresBench.php b/tests/Performance/Regression/LeastSquaresBench.php new file mode 100644 index 00000000..d6dba2fb --- /dev/null +++ b/tests/Performance/Regression/LeastSquaresBench.php @@ -0,0 +1,40 @@ +dataset = new CsvDataset(__DIR__.'/../Data/bike-sharing-hour.csv', 14); + } + + /** + * @Revs(1) + * @Iterations(5) + */ + public function benchLeastSquaresTrain(): void + { + $leastSqueares = new LeastSquares(); + $leastSqueares->train($this->dataset->getSamples(), $this->dataset->getTargets()); + } +} diff --git a/tests/Performance/bootstrap.php b/tests/Performance/bootstrap.php new file mode 100644 index 00000000..a358903e --- /dev/null +++ b/tests/Performance/bootstrap.php @@ -0,0 +1,20 @@ + Date: Wed, 21 Mar 2018 01:25:25 +0900 Subject: [PATCH 274/328] Fix SVR documentation (#265) --- docs/machine-learning/regression/svr.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/machine-learning/regression/svr.md b/docs/machine-learning/regression/svr.md index ba6bd74d..1678f5fc 100644 --- a/docs/machine-learning/regression/svr.md +++ b/docs/machine-learning/regression/svr.md @@ -4,7 +4,7 @@ Class implementing Epsilon-Support Vector Regression based on libsvm. ### Constructor Parameters -* $kernel (int) - kernel type to be used in the algorithm (default Kernel::LINEAR) +* $kernel (int) - kernel type to be used in the algorithm (default Kernel::RBF) * $degree (int) - degree of the Kernel::POLYNOMIAL function (default 3) * $epsilon (float) - epsilon in loss function of epsilon-SVR (default 0.1) * $cost (float) - parameter C of C-SVC (default 1.0) From 59e69fdb6353bcac99137be5d557d6463807e684 Mon Sep 17 00:00:00 2001 From: Yuji Uchiyama Date: Wed, 28 Mar 2018 14:38:22 +0900 Subject: [PATCH 275/328] Update CHANGELOG.md (#269) --- CHANGELOG.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61ecd632..ec7b3aaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,18 @@ CHANGELOG This changelog references the relevant changes done in PHP-ML library. +* Unreleased + * feature [Clustering] added KMeans associative clustering (#262) + * feature [Dataset] added removeColumns function to ArrayDataset (#249) + * feature [Dataset] added a SvmDataset class for SVM-Light (or LibSVM) format files (#237) + * feature [Optimizer] removed $initialTheta property and renamed setInitialTheta method to setTheta (#252) + * enhancement Add performance test for LeastSquares (#263) + * enhancement Micro optimization for matrix multiplication (#255) + * enhancement Throw proper exception (#259, #251) + * fix ensure DataTransformer::testSet samples array is not empty (#204) + * fix optimizer initial theta randomization (#239) + * typo, tests, code styles and documentation fixes (#265, #261, #254, #253, #251, #250, #248, #245, #243) + * 0.6.2 (2018-02-22) * Fix Apriori array keys (#238) @@ -80,7 +92,7 @@ This changelog references the relevant changes done in PHP-ML library. * bug [Metric] - division by zero * 0.2.0 (2016-08-14) - * feature [NeuralNetwork] - MultilayerPerceptron and Backpropagation training + * feature [NeuralNetwork] - MultilayerPerceptron and Backpropagation training * 0.1.2 (2016-07-24) * feature [Dataset] - FilesDataset - load dataset from files (folder names as targets) @@ -90,7 +102,7 @@ This changelog references the relevant changes done in PHP-ML library. * 0.1.1 (2016-07-12) * feature [Cross Validation] Stratified Random Split - equal distribution for targets in split - * feature [General] Documentation - add missing pages (Pipeline, ConfusionMatrix and TfIdfTransformer) and fix links + * feature [General] Documentation - add missing pages (Pipeline, ConfusionMatrix and TfIdfTransformer) and fix links * 0.1.0 (2016-07-08) * first develop release From 31604ce7927a68448ac842392de618f675b8d332 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Thu, 14 Jun 2018 07:53:33 +0200 Subject: [PATCH 276/328] Update osx build for travis (#281) * Update osx build for travis * Try something more with this os * This gonna be hard ... * Cleanup travis build even more --- .travis.yml | 2 -- CHANGELOG.md | 1 + bin/handle_brew_pkg.sh | 31 ------------------------------- bin/prepare_osx_env.sh | 14 +++----------- 4 files changed, 4 insertions(+), 44 deletions(-) delete mode 100644 bin/handle_brew_pkg.sh diff --git a/.travis.yml b/.travis.yml index bc647cb9..58ccbc60 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,6 @@ matrix: language: generic env: - _OSX=10.11 - - _PHP: php71 cache: directories: @@ -28,7 +27,6 @@ before_install: - if [[ $DISABLE_XDEBUG == "true" ]]; then phpenv config-rm xdebug.ini; fi install: - - if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then /usr/bin/env bash bin/handle_brew_pkg.sh "${_PHP}" ; fi - curl -s http://getcomposer.org/installer | php - php composer.phar install --no-interaction --ignore-platform-reqs diff --git a/CHANGELOG.md b/CHANGELOG.md index ec7b3aaa..efd2174b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This changelog references the relevant changes done in PHP-ML library. * enhancement Throw proper exception (#259, #251) * fix ensure DataTransformer::testSet samples array is not empty (#204) * fix optimizer initial theta randomization (#239) + * fix travis build on osx (#281) * typo, tests, code styles and documentation fixes (#265, #261, #254, #253, #251, #250, #248, #245, #243) * 0.6.2 (2018-02-22) diff --git a/bin/handle_brew_pkg.sh b/bin/handle_brew_pkg.sh deleted file mode 100644 index 3e501e04..00000000 --- a/bin/handle_brew_pkg.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bash - -if [[ "$#" -eq 1 ]]; then - echo "Handling \"$1\" brew package..." -else - echo "Brew failed - invalid $0 call" - exit 1; -fi - -if [[ $(brew ls --versions "$1") ]]; then - if brew outdated "$1"; then - echo "Package upgrade is not required, skipping" - else - echo "Updating package..."; - brew upgrade "$1" - if [ $? -ne 0 ]; then - echo "Upgrade failed" - exit 1 - fi - fi -else - echo "Package not available - installing..." - brew install "$1" - if [ $? -ne 0 ]; then - echo "Install failed" - exit 1 - fi -fi - -echo "Linking installed package..." -brew link "$1" \ No newline at end of file diff --git a/bin/prepare_osx_env.sh b/bin/prepare_osx_env.sh index 93303ee1..e185bd3c 100644 --- a/bin/prepare_osx_env.sh +++ b/bin/prepare_osx_env.sh @@ -6,14 +6,6 @@ brew --version echo "Updating brew..." brew update - -if [[ "${_PHP}" == "hhvm" ]]; then - echo "Adding brew HHVM dependencies..." - brew tap hhvm/hhvm - -else - echo "Adding brew PHP dependencies..." - brew tap homebrew/dupes - brew tap homebrew/versions - brew tap homebrew/homebrew-php -fi \ No newline at end of file +brew install php@7.1 +brew upgrade php@7.1 +brew link php@7.1 --overwrite --force \ No newline at end of file From 46fa2c2ccaf64658240634c207abda7e5e200aed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Votruba?= Date: Fri, 15 Jun 2018 07:57:45 +0200 Subject: [PATCH 277/328] Update to EasyCodingStandard 4 (#273) * update ECS config to v4 * composer: require Symplify 4 * apply coding-standard: use constants over functions, protected setUp() in tests, array indentation * ecs: add false positive case * composer: update lock * bump to ECS 4.4 * update composer.lock * shorten ECS config name * ecs: ignore assignments in while() * fix cs --- composer.json | 4 +- composer.lock | 1325 +++++++++-------- easy-coding-standard.neon | 65 - ecs.yml | 68 + src/Classification/DecisionTree.php | 4 +- src/Classification/Linear/Perceptron.php | 3 +- src/Classification/NaiveBayes.php | 2 +- src/Clustering/KMeans/Space.php | 4 +- src/CrossValidation/StratifiedRandomSplit.php | 4 +- src/Dataset/Dataset.php | 6 - src/Dataset/SvmDataset.php | 4 +- src/Estimator.php | 6 - src/Helper/Predictable.php | 4 - src/Helper/Trainable.php | 4 - src/IncrementalEstimator.php | 5 - src/Math/Distance.php | 4 - .../LinearAlgebra/EigenvalueDecomposition.php | 8 +- src/Math/Statistic/Correlation.php | 4 +- src/Math/Statistic/Gaussian.php | 2 +- src/Metric/ClassificationReport.php | 3 +- src/Metric/ConfusionMatrix.php | 5 +- src/NeuralNetwork/Training.php | 4 - src/Preprocessing/Imputer/Strategy.php | 2 - tests/Math/Distance/ChebyshevTest.php | 2 +- tests/Math/Distance/EuclideanTest.php | 2 +- tests/Math/Distance/ManhattanTest.php | 2 +- tests/Math/Distance/MinkowskiTest.php | 2 +- tests/Math/MatrixTest.php | 8 +- .../Tokenization/WhitespaceTokenizerTest.php | 8 +- tests/Tokenization/WordTokenizerTest.php | 8 +- 30 files changed, 814 insertions(+), 758 deletions(-) delete mode 100644 easy-coding-standard.neon create mode 100644 ecs.yml diff --git a/composer.json b/composer.json index c809822d..664eeebb 100644 --- a/composer.json +++ b/composer.json @@ -28,8 +28,8 @@ "phpstan/phpstan-shim": "^0.9", "phpstan/phpstan-strict-rules": "^0.9.0", "phpunit/phpunit": "^7.0.0", - "symplify/coding-standard": "^3.1", - "symplify/easy-coding-standard": "^3.1" + "symplify/coding-standard": "^4.4", + "symplify/easy-coding-standard": "^4.4" }, "config": { "preferred-install": "dist", diff --git a/composer.lock b/composer.lock index 0eeb6935..5a886975 100644 --- a/composer.lock +++ b/composer.lock @@ -4,21 +4,21 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "fc88078118714e9fb6b13a02dba0bd00", + "content-hash": "46e6ba23009cf16bec8046ed302395b3", "packages": [], "packages-dev": [ { "name": "beberlei/assert", - "version": "v2.9.2", + "version": "v2.9.6", "source": { "type": "git", "url": "https://github.com/beberlei/assert.git", - "reference": "2d555f72f3f4ff9e72a7bc17cb8a53c86737c8a0" + "reference": "ec9e4cf0b63890edce844ee3922e2b95a526e936" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/beberlei/assert/zipball/2d555f72f3f4ff9e72a7bc17cb8a53c86737c8a0", - "reference": "2d555f72f3f4ff9e72a7bc17cb8a53c86737c8a0", + "url": "https://api.github.com/repos/beberlei/assert/zipball/ec9e4cf0b63890edce844ee3922e2b95a526e936", + "reference": "ec9e4cf0b63890edce844ee3922e2b95a526e936", "shasum": "" }, "require": { @@ -60,7 +60,7 @@ "assertion", "validation" ], - "time": "2018-01-25T13:33:16+00:00" + "time": "2018-06-11T17:15:25+00:00" }, { "name": "composer/semver", @@ -124,6 +124,50 @@ ], "time": "2016-08-30T16:08:34+00:00" }, + { + "name": "composer/xdebug-handler", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "c919dc6c62e221fc6406f861ea13433c0aa24f08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/c919dc6c62e221fc6406f861ea13433c0aa24f08", + "reference": "c919dc6c62e221fc6406f861ea13433c0aa24f08", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0", + "psr/log": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "time": "2018-04-11T15:42:36+00:00" + }, { "name": "doctrine/annotations", "version": "v1.6.0", @@ -302,26 +346,26 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v2.9.0", + "version": "v2.12.1", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "454ddbe65da6a9297446f442bad244e8a99a9a38" + "reference": "beef6cbe6dec7205edcd143842a49f9a691859a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/454ddbe65da6a9297446f442bad244e8a99a9a38", - "reference": "454ddbe65da6a9297446f442bad244e8a99a9a38", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/beef6cbe6dec7205edcd143842a49f9a691859a6", + "reference": "beef6cbe6dec7205edcd143842a49f9a691859a6", "shasum": "" }, "require": { "composer/semver": "^1.4", + "composer/xdebug-handler": "^1.0", "doctrine/annotations": "^1.2", "ext-json": "*", "ext-tokenizer": "*", - "gecko-packages/gecko-php-unit": "^2.0 || ^3.0", "php": "^5.6 || >=7.0 <7.3", - "php-cs-fixer/diff": "^1.2", + "php-cs-fixer/diff": "^1.3", "symfony/console": "^3.2 || ^4.0", "symfony/event-dispatcher": "^3.0 || ^4.0", "symfony/filesystem": "^3.0 || ^4.0", @@ -336,16 +380,22 @@ "hhvm": "*" }, "require-dev": { - "johnkary/phpunit-speedtrap": "^1.1 || ^2.0@dev", + "johnkary/phpunit-speedtrap": "^1.1 || ^2.0 || ^3.0", "justinrainbow/json-schema": "^5.0", + "keradus/cli-executor": "^1.1", "mikey179/vfsstream": "^1.6", - "php-coveralls/php-coveralls": "^2.0", + "php-coveralls/php-coveralls": "^2.1", "php-cs-fixer/accessible-object": "^1.0", - "phpunit/phpunit": "^5.7.23 || ^6.4.3", - "symfony/phpunit-bridge": "^3.2.2 || ^4.0" + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.0.1", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.0.1", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1", + "phpunitgoodpractices/traits": "^1.5", + "symfony/phpunit-bridge": "^4.0" }, "suggest": { "ext-mbstring": "For handling non-UTF8 characters in cache signature.", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "For IsIdenticalString constraint.", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "For XmlMatchesXsd constraint.", "symfony/polyfill-mbstring": "When enabling `ext-mbstring` is not possible." }, "bin": [ @@ -357,11 +407,15 @@ "PhpCsFixer\\": "src/" }, "classmap": [ - "tests/Test/Assert/AssertTokensTrait.php", "tests/Test/AbstractFixerTestCase.php", + "tests/Test/AbstractIntegrationCaseFactory.php", "tests/Test/AbstractIntegrationTestCase.php", + "tests/Test/Assert/AssertTokensTrait.php", "tests/Test/IntegrationCase.php", - "tests/Test/IntegrationCaseFactory.php" + "tests/Test/IntegrationCaseFactory.php", + "tests/Test/IntegrationCaseFactoryInterface.php", + "tests/Test/InternalIntegrationCaseFactory.php", + "tests/TestCase.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -379,69 +433,71 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2017-12-08T16:36:20+00:00" + "time": "2018-06-10T08:26:56+00:00" }, { - "name": "gecko-packages/gecko-php-unit", - "version": "v3.0", + "name": "jean85/pretty-package-versions", + "version": "1.2", "source": { "type": "git", - "url": "https://github.com/GeckoPackages/GeckoPHPUnit.git", - "reference": "6a866551dffc2154c1b091bae3a7877d39c25ca3" + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "75c7effcf3f77501d0e0caa75111aff4daa0dd48" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GeckoPackages/GeckoPHPUnit/zipball/6a866551dffc2154c1b091bae3a7877d39c25ca3", - "reference": "6a866551dffc2154c1b091bae3a7877d39c25ca3", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/75c7effcf3f77501d0e0caa75111aff4daa0dd48", + "reference": "75c7effcf3f77501d0e0caa75111aff4daa0dd48", "shasum": "" }, "require": { + "ocramius/package-versions": "^1.2.0", "php": "^7.0" }, "require-dev": { "phpunit/phpunit": "^6.0" }, - "suggest": { - "ext-dom": "When testing with xml.", - "ext-libxml": "When testing with xml.", - "phpunit/phpunit": "This is an extension for it so make sure you have it some way." - }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "1.x-dev" } }, "autoload": { "psr-4": { - "GeckoPackages\\PHPUnit\\": "src/PHPUnit" + "Jean85\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Additional PHPUnit asserts and constraints.", - "homepage": "https://github.com/GeckoPackages", + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], + "description": "A wrapper for ocramius/package-versions to get pretty versions strings", "keywords": [ - "extension", - "filesystem", - "phpunit" + "composer", + "package", + "release", + "versions" ], - "time": "2017-08-23T07:46:41+00:00" + "time": "2018-06-13T13:22:40+00:00" }, { "name": "lstrojny/functional-php", - "version": "1.7.0", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/lstrojny/functional-php.git", - "reference": "7c2091ddea572e012aa980e5d19d242d3a06ad5b" + "reference": "7d677bbc1dbf8338946cd3b31f0d5c2beb2b5a26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lstrojny/functional-php/zipball/7c2091ddea572e012aa980e5d19d242d3a06ad5b", - "reference": "7c2091ddea572e012aa980e5d19d242d3a06ad5b", + "url": "https://api.github.com/repos/lstrojny/functional-php/zipball/7d677bbc1dbf8338946cd3b31f0d5c2beb2b5a26", + "reference": "7d677bbc1dbf8338946cd3b31f0d5c2beb2b5a26", "shasum": "" }, "require": { @@ -462,6 +518,7 @@ }, "files": [ "src/Functional/Average.php", + "src/Functional/ButLast.php", "src/Functional/Capture.php", "src/Functional/ConstFunction.php", "src/Functional/CompareOn.php", @@ -566,29 +623,32 @@ "keywords": [ "functional" ], - "time": "2018-01-03T10:08:50+00:00" + "time": "2018-03-19T16:14:14+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.7.0", + "version": "1.8.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", + "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" }, "require-dev": { "doctrine/collections": "^1.0", "doctrine/common": "^2.6", - "phpunit/phpunit": "^4.1" + "phpunit/phpunit": "^7.1" }, "type": "library", "autoload": { @@ -611,145 +671,7 @@ "object", "object graph" ], - "time": "2017-10-19T19:58:43+00:00" - }, - { - "name": "nette/caching", - "version": "v2.5.6", - "source": { - "type": "git", - "url": "https://github.com/nette/caching.git", - "reference": "1231735b5135ca02bd381b70482c052d2a90bdc9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/caching/zipball/1231735b5135ca02bd381b70482c052d2a90bdc9", - "reference": "1231735b5135ca02bd381b70482c052d2a90bdc9", - "shasum": "" - }, - "require": { - "nette/finder": "^2.2 || ~3.0.0", - "nette/utils": "^2.4 || ~3.0.0", - "php": ">=5.6.0" - }, - "conflict": { - "nette/nette": "<2.2" - }, - "require-dev": { - "latte/latte": "^2.4", - "nette/di": "^2.4 || ~3.0.0", - "nette/tester": "^2.0", - "tracy/tracy": "^2.4" - }, - "suggest": { - "ext-pdo_sqlite": "to use SQLiteStorage or SQLiteJournal" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.5-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0", - "GPL-3.0" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "⏱ Nette Caching: library with easy-to-use API and many cache backends.", - "homepage": "https://nette.org", - "keywords": [ - "cache", - "journal", - "memcached", - "nette", - "sqlite" - ], - "time": "2017-08-30T12:12:25+00:00" - }, - { - "name": "nette/di", - "version": "v2.4.10", - "source": { - "type": "git", - "url": "https://github.com/nette/di.git", - "reference": "a4b3be935b755f23aebea1ce33d7e3c832cdff98" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/di/zipball/a4b3be935b755f23aebea1ce33d7e3c832cdff98", - "reference": "a4b3be935b755f23aebea1ce33d7e3c832cdff98", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "nette/neon": "^2.3.3 || ~3.0.0", - "nette/php-generator": "^2.6.1 || ~3.0.0", - "nette/utils": "^2.4.3 || ~3.0.0", - "php": ">=5.6.0" - }, - "conflict": { - "nette/bootstrap": "<2.4", - "nette/nette": "<2.2" - }, - "require-dev": { - "nette/tester": "^2.0", - "tracy/tracy": "^2.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.4-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0", - "GPL-3.0" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "💎 Nette Dependency Injection Container: Flexible, compiled and full-featured DIC with perfectly usable autowiring and support for all new PHP 7.1 features.", - "homepage": "https://nette.org", - "keywords": [ - "compiled", - "di", - "dic", - "factory", - "ioc", - "nette", - "static" - ], - "time": "2017-08-31T22:42:00+00:00" + "time": "2018-06-11T23:09:50+00:00" }, { "name": "nette/finder", @@ -807,134 +729,18 @@ "homepage": "https://nette.org", "time": "2017-07-10T23:47:08+00:00" }, - { - "name": "nette/neon", - "version": "v2.4.2", - "source": { - "type": "git", - "url": "https://github.com/nette/neon.git", - "reference": "9eacd50553b26b53a3977bfb2fea2166d4331622" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/neon/zipball/9eacd50553b26b53a3977bfb2fea2166d4331622", - "reference": "9eacd50553b26b53a3977bfb2fea2166d4331622", - "shasum": "" - }, - "require": { - "ext-iconv": "*", - "ext-json": "*", - "php": ">=5.6.0" - }, - "require-dev": { - "nette/tester": "~2.0", - "tracy/tracy": "^2.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.4-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0", - "GPL-3.0" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "Nette NEON: parser & generator for Nette Object Notation", - "homepage": "http://ne-on.org", - "time": "2017-07-11T18:29:08+00:00" - }, - { - "name": "nette/php-generator", - "version": "v3.0.1", - "source": { - "type": "git", - "url": "https://github.com/nette/php-generator.git", - "reference": "eb2dbc9c3409e9db40568109ca4994d51373b60c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/php-generator/zipball/eb2dbc9c3409e9db40568109ca4994d51373b60c", - "reference": "eb2dbc9c3409e9db40568109ca4994d51373b60c", - "shasum": "" - }, - "require": { - "nette/utils": "^2.4.2 || ~3.0.0", - "php": ">=7.0" - }, - "conflict": { - "nette/nette": "<2.2" - }, - "require-dev": { - "nette/tester": "^2.0", - "tracy/tracy": "^2.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0", - "GPL-3.0" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 7.1 features.", - "homepage": "https://nette.org", - "keywords": [ - "code", - "nette", - "php", - "scaffolding" - ], - "time": "2017-07-11T19:07:13+00:00" - }, { "name": "nette/robot-loader", - "version": "v3.0.2", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/nette/robot-loader.git", - "reference": "b703b4f5955831b0bcaacbd2f6af76021b056826" + "reference": "92d4b40b49d5e2d9e37fc736bbcebe6da55fa44a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/robot-loader/zipball/b703b4f5955831b0bcaacbd2f6af76021b056826", - "reference": "b703b4f5955831b0bcaacbd2f6af76021b056826", + "url": "https://api.github.com/repos/nette/robot-loader/zipball/92d4b40b49d5e2d9e37fc736bbcebe6da55fa44a", + "reference": "92d4b40b49d5e2d9e37fc736bbcebe6da55fa44a", "shasum": "" }, "require": { @@ -986,20 +792,20 @@ "nette", "trait" ], - "time": "2017-07-18T00:09:56+00:00" + "time": "2017-09-26T13:42:21+00:00" }, { "name": "nette/utils", - "version": "v2.4.8", + "version": "v2.5.2", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "f1584033b5af945b470533b466b81a789d532034" + "reference": "183069866dc477fcfbac393ed486aaa6d93d19a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/f1584033b5af945b470533b466b81a789d532034", - "reference": "f1584033b5af945b470533b466b81a789d532034", + "url": "https://api.github.com/repos/nette/utils/zipball/183069866dc477fcfbac393ed486aaa6d93d19a5", + "reference": "183069866dc477fcfbac393ed486aaa6d93d19a5", "shasum": "" }, "require": { @@ -1023,12 +829,15 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.4-dev" + "dev-master": "2.5-dev" } }, "autoload": { "classmap": [ "src/" + ], + "files": [ + "src/loader.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -1065,20 +874,69 @@ "utility", "validation" ], - "time": "2017-08-20T17:32:29+00:00" + "time": "2018-05-02T17:16:08+00:00" + }, + { + "name": "ocramius/package-versions", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/Ocramius/PackageVersions.git", + "reference": "4489d5002c49d55576fa0ba786f42dbb009be46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/4489d5002c49d55576fa0ba786f42dbb009be46f", + "reference": "4489d5002c49d55576fa0ba786f42dbb009be46f", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0.0", + "php": "^7.1.0" + }, + "require-dev": { + "composer/composer": "^1.6.3", + "ext-zip": "*", + "infection/infection": "^0.7.1", + "phpunit/phpunit": "^7.0.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "time": "2018-02-05T13:05:30+00:00" }, { "name": "paragonie/random_compat", - "version": "v2.0.11", + "version": "v2.0.15", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8" + "reference": "10bcb46e8f3d365170f6de9d05245aa066b81f09" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/5da4d3c796c275c55f057af5a643ae297d96b4d8", - "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/10bcb46e8f3d365170f6de9d05245aa066b81f09", + "reference": "10bcb46e8f3d365170f6de9d05245aa066b81f09", "shasum": "" }, "require": { @@ -1110,10 +968,11 @@ "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", "keywords": [ "csprng", + "polyfill", "pseudorandom", "random" ], - "time": "2017-09-27T21:40:39+00:00" + "time": "2018-06-08T15:26:40+00:00" }, { "name": "phar-io/manifest", @@ -1219,23 +1078,23 @@ }, { "name": "php-cs-fixer/diff", - "version": "v1.2.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/diff.git", - "reference": "f0ef6133d674137e902fdf8a6f2e8e97e14a087b" + "reference": "78bb099e9c16361126c86ce82ec4405ebab8e756" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/f0ef6133d674137e902fdf8a6f2e8e97e14a087b", - "reference": "f0ef6133d674137e902fdf8a6f2e8e97e14a087b", + "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/78bb099e9c16361126c86ce82ec4405ebab8e756", + "reference": "78bb099e9c16361126c86ce82ec4405ebab8e756", "shasum": "" }, "require": { "php": "^5.6 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.4.3", + "phpunit/phpunit": "^5.7.23 || ^6.4.3", "symfony/process": "^3.3" }, "type": "library", @@ -1245,6 +1104,9 @@ ] }, "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Kore Nordmann", @@ -1263,7 +1125,7 @@ "keywords": [ "diff" ], - "time": "2017-10-19T09:58:18+00:00" + "time": "2018-02-15T16:58:55+00:00" }, { "name": "phpbench/container", @@ -1483,16 +1345,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "4.2.0", + "version": "4.3.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "66465776cfc249844bde6d117abff1d22e06c2da" + "reference": "94fd0001232e47129dd3504189fa1c7225010d08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/66465776cfc249844bde6d117abff1d22e06c2da", - "reference": "66465776cfc249844bde6d117abff1d22e06c2da", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", + "reference": "94fd0001232e47129dd3504189fa1c7225010d08", "shasum": "" }, "require": { @@ -1530,7 +1392,7 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2017-11-27T17:38:31+00:00" + "time": "2017-11-30T07:14:17+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -1581,28 +1443,28 @@ }, { "name": "phpspec/prophecy", - "version": "1.7.3", + "version": "1.7.6", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf" + "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf", - "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/33a7e3c4fda54e912ff6338c48823bd5c0f0b712", + "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", - "sebastian/comparator": "^1.1|^2.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", "sebastian/recursion-context": "^1.0|^2.0|^3.0" }, "require-dev": { "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" }, "type": "library", "extra": { @@ -1640,7 +1502,52 @@ "spy", "stub" ], - "time": "2017-11-24T13:59:53+00:00" + "time": "2018-04-18T13:57:24+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "02f909f134fe06f0cd4790d8627ee24efbe84d6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/02f909f134fe06f0cd4790d8627ee24efbe84d6a", + "reference": "02f909f134fe06f0cd4790d8627ee24efbe84d6a", + "shasum": "" + }, + "require": { + "php": "~7.0" + }, + "require-dev": { + "consistence/coding-standard": "^2.0.0", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/phpstan": "^0.9", + "phpunit/phpunit": "^6.3", + "slevomat/coding-standard": "^3.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.1-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "time": "2018-01-13T18:19:41+00:00" }, { "name": "phpstan/phpstan-phpunit", @@ -1689,16 +1596,16 @@ }, { "name": "phpstan/phpstan-shim", - "version": "0.9.1", + "version": "0.9.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-shim.git", - "reference": "e3bea4f40f14316cf76390e7fd58181dca840977" + "reference": "e4720fb2916be05de02869780072253e7e0e8a75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-shim/zipball/e3bea4f40f14316cf76390e7fd58181dca840977", - "reference": "e3bea4f40f14316cf76390e7fd58181dca840977", + "url": "https://api.github.com/repos/phpstan/phpstan-shim/zipball/e4720fb2916be05de02869780072253e7e0e8a75", + "reference": "e4720fb2916be05de02869780072253e7e0e8a75", "shasum": "" }, "require": { @@ -1722,7 +1629,7 @@ "MIT" ], "description": "PHPStan Phar distribution", - "time": "2017-12-02T20:14:45+00:00" + "time": "2018-01-28T14:29:27+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -1770,27 +1677,27 @@ }, { "name": "phpunit/php-code-coverage", - "version": "6.0.1", + "version": "6.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f8ca4b604baf23dab89d87773c28cc07405189ba" + "reference": "865662550c384bc1db7e51d29aeda1c2c161d69a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f8ca4b604baf23dab89d87773c28cc07405189ba", - "reference": "f8ca4b604baf23dab89d87773c28cc07405189ba", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/865662550c384bc1db7e51d29aeda1c2c161d69a", + "reference": "865662550c384bc1db7e51d29aeda1c2c161d69a", "shasum": "" }, "require": { "ext-dom": "*", "ext-xmlwriter": "*", "php": "^7.1", - "phpunit/php-file-iterator": "^1.4.2", + "phpunit/php-file-iterator": "^2.0", "phpunit/php-text-template": "^1.2.1", "phpunit/php-token-stream": "^3.0", "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^3.0", + "sebastian/environment": "^3.1", "sebastian/version": "^2.0.1", "theseer/tokenizer": "^1.1" }, @@ -1829,29 +1736,29 @@ "testing", "xunit" ], - "time": "2018-02-02T07:01:41+00:00" + "time": "2018-06-01T07:51:50+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "1.4.5", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + "reference": "cecbc684605bb0cc288828eb5d65d93d5c676d3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cecbc684605bb0cc288828eb5d65d93d5c676d3c", + "reference": "cecbc684605bb0cc288828eb5d65d93d5c676d3c", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -1866,7 +1773,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -1876,7 +1783,7 @@ "filesystem", "iterator" ], - "time": "2017-11-27T13:52:08+00:00" + "time": "2018-06-11T11:44:00+00:00" }, { "name": "phpunit/php-text-template", @@ -2019,35 +1926,35 @@ }, { "name": "phpunit/phpunit", - "version": "7.0.0", + "version": "7.2.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "9b3373439fdf2f3e9d1578f5e408a3a0d161c3bc" + "reference": "00bc0b93f0ff4f557e9ea766557fde96da9a03dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9b3373439fdf2f3e9d1578f5e408a3a0d161c3bc", - "reference": "9b3373439fdf2f3e9d1578f5e408a3a0d161c3bc", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/00bc0b93f0ff4f557e9ea766557fde96da9a03dd", + "reference": "00bc0b93f0ff4f557e9ea766557fde96da9a03dd", "shasum": "" }, "require": { + "doctrine/instantiator": "^1.1", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", - "myclabs/deep-copy": "^1.6.1", + "myclabs/deep-copy": "^1.7", "phar-io/manifest": "^1.0.1", "phar-io/version": "^1.0", "php": "^7.1", "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^6.0", - "phpunit/php-file-iterator": "^1.4.3", + "phpunit/php-code-coverage": "^6.0.7", + "phpunit/php-file-iterator": "^2.0", "phpunit/php-text-template": "^1.2.1", "phpunit/php-timer": "^2.0", - "phpunit/phpunit-mock-objects": "^6.0", - "sebastian/comparator": "^2.1", + "sebastian/comparator": "^3.0", "sebastian/diff": "^3.0", "sebastian/environment": "^3.1", "sebastian/exporter": "^3.1", @@ -2056,10 +1963,14 @@ "sebastian/resource-operations": "^1.0", "sebastian/version": "^2.0.1" }, + "conflict": { + "phpunit/phpunit-mock-objects": "*" + }, "require-dev": { "ext-pdo": "*" }, "suggest": { + "ext-soap": "*", "ext-xdebug": "*", "phpunit/php-invoker": "^2.0" }, @@ -2069,7 +1980,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.0-dev" + "dev-master": "7.2-dev" } }, "autoload": { @@ -2095,63 +2006,53 @@ "testing", "xunit" ], - "time": "2018-02-02T05:04:08+00:00" + "time": "2018-06-05T03:40:05+00:00" }, { - "name": "phpunit/phpunit-mock-objects", - "version": "6.0.0", + "name": "psr/cache", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "e495e5d3660321b62c294d8c0e954d02d6ce2573" + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/e495e5d3660321b62c294d8c0e954d02d6ce2573", - "reference": "e495e5d3660321b62c294d8c0e954d02d6ce2573", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.0.5", - "php": "^7.1", - "phpunit/php-text-template": "^1.2.1", - "sebastian/exporter": "^3.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.0" - }, - "suggest": { - "ext-soap": "*" + "php": ">=5.3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "6.0.x-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Psr\\Cache\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" } ], - "description": "Mock Object library for PHPUnit", - "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "description": "Common interface for caching libraries", "keywords": [ - "mock", - "xunit" + "cache", + "psr", + "psr-6" ], - "time": "2018-02-01T13:11:13+00:00" + "time": "2016-08-06T20:24:11+00:00" }, { "name": "psr/container", @@ -2249,6 +2150,54 @@ ], "time": "2016-10-10T12:19:37+00:00" }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "time": "2017-10-23T01:57:42+00:00" + }, { "name": "sebastian/code-unit-reverse-lookup", "version": "1.0.1", @@ -2296,30 +2245,30 @@ }, { "name": "sebastian/comparator", - "version": "2.1.3", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" + "reference": "ed5fd2281113729f1ebcc64d101ad66028aeb3d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", - "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/ed5fd2281113729f1ebcc64d101ad66028aeb3d5", + "reference": "ed5fd2281113729f1ebcc64d101ad66028aeb3d5", "shasum": "" }, "require": { - "php": "^7.0", - "sebastian/diff": "^2.0 || ^3.0", + "php": "^7.1", + "sebastian/diff": "^3.0", "sebastian/exporter": "^3.1" }, "require-dev": { - "phpunit/phpunit": "^6.4" + "phpunit/phpunit": "^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2356,20 +2305,20 @@ "compare", "equality" ], - "time": "2018-02-01T13:46:46+00:00" + "time": "2018-04-18T13:33:00+00:00" }, { "name": "sebastian/diff", - "version": "3.0.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "e09160918c66281713f1c324c1f4c4c3037ba1e8" + "reference": "366541b989927187c4ca70490a35615d3fef2dce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/e09160918c66281713f1c324c1f4c4c3037ba1e8", - "reference": "e09160918c66281713f1c324c1f4c4c3037ba1e8", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/366541b989927187c4ca70490a35615d3fef2dce", + "reference": "366541b989927187c4ca70490a35615d3fef2dce", "shasum": "" }, "require": { @@ -2412,7 +2361,7 @@ "unidiff", "unified diff" ], - "time": "2018-02-01T13:45:15+00:00" + "time": "2018-06-10T07:54:39+00:00" }, { "name": "sebastian/environment", @@ -2863,27 +2812,29 @@ }, { "name": "slevomat/coding-standard", - "version": "4.2.1", + "version": "4.6.2", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "998b5e96ce36a55d7821d17f39d296a17c05b481" + "reference": "d43b9a627cdcb7ce837a1d85a79b52645cdf44bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/998b5e96ce36a55d7821d17f39d296a17c05b481", - "reference": "998b5e96ce36a55d7821d17f39d296a17c05b481", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/d43b9a627cdcb7ce837a1d85a79b52645cdf44bc", + "reference": "d43b9a627cdcb7ce837a1d85a79b52645cdf44bc", "shasum": "" }, "require": { "php": "^7.1", - "squizlabs/php_codesniffer": "^3.0.2" + "squizlabs/php_codesniffer": "^3.2.3" }, "require-dev": { - "jakub-onderka/php-parallel-lint": "0.9.2", - "phing/phing": "2.16", - "phpstan/phpstan": "0.9.1", - "phpunit/phpunit": "6.5.5" + "jakub-onderka/php-parallel-lint": "1.0.0", + "phing/phing": "2.16.1", + "phpstan/phpstan": "0.9.2", + "phpstan/phpstan-phpunit": "0.9.4", + "phpstan/phpstan-strict-rules": "0.9", + "phpunit/phpunit": "7.2.4" }, "type": "phpcodesniffer-standard", "autoload": { @@ -2896,20 +2847,20 @@ "MIT" ], "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "time": "2018-01-04T14:00:21+00:00" + "time": "2018-06-12T21:23:15+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.2.2", + "version": "3.3.0", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "d7c00c3000ac0ce79c96fcbfef86b49a71158cd1" + "reference": "d86873af43b4aa9d1f39a3601cc0cfcf02b25266" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/d7c00c3000ac0ce79c96fcbfef86b49a71158cd1", - "reference": "d7c00c3000ac0ce79c96fcbfef86b49a71158cd1", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/d86873af43b4aa9d1f39a3601cc0cfcf02b25266", + "reference": "d86873af43b4aa9d1f39a3601cc0cfcf02b25266", "shasum": "" }, "require": { @@ -2919,7 +2870,7 @@ "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0" + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, "bin": [ "bin/phpcs", @@ -2947,30 +2898,102 @@ "phpcs", "standards" ], - "time": "2017-12-19T21:44:46+00:00" + "time": "2018-06-06T23:58:19+00:00" + }, + { + "name": "symfony/cache", + "version": "v4.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "4986efce97c002e58380e8c0474acbf72eda9339" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/4986efce97c002e58380e8c0474acbf72eda9339", + "reference": "4986efce97c002e58380e8c0474acbf72eda9339", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "psr/cache": "~1.0", + "psr/log": "~1.0", + "psr/simple-cache": "^1.0" + }, + "conflict": { + "symfony/var-dumper": "<3.4" + }, + "provide": { + "psr/cache-implementation": "1.0", + "psr/simple-cache-implementation": "1.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/cache": "~1.6", + "doctrine/dbal": "~2.4", + "predis/predis": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Cache component with PSR-6, PSR-16, and tags", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "time": "2018-05-16T14:33:22+00:00" }, { "name": "symfony/config", - "version": "v4.0.3", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "0e86d267db0851cf55f339c97df00d693fe8592f" + "reference": "5ceefc256caecc3e25147c4e5b933de71d0020c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/0e86d267db0851cf55f339c97df00d693fe8592f", - "reference": "0e86d267db0851cf55f339c97df00d693fe8592f", + "url": "https://api.github.com/repos/symfony/config/zipball/5ceefc256caecc3e25147c4e5b933de71d0020c4", + "reference": "5ceefc256caecc3e25147c4e5b933de71d0020c4", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/filesystem": "~3.4|~4.0" + "symfony/filesystem": "~3.4|~4.0", + "symfony/polyfill-ctype": "~1.8" }, "conflict": { "symfony/finder": "<3.4" }, "require-dev": { + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~3.4|~4.0", "symfony/finder": "~3.4|~4.0", "symfony/yaml": "~3.4|~4.0" }, @@ -2980,7 +3003,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3007,20 +3030,20 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2018-01-03T07:38:00+00:00" + "time": "2018-05-16T14:33:22+00:00" }, { "name": "symfony/console", - "version": "v4.0.3", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "fe0e69d7162cba0885791cf7eea5f0d7bc0f897e" + "reference": "2d5d973bf9933d46802b01010bd25c800c87c242" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/fe0e69d7162cba0885791cf7eea5f0d7bc0f897e", - "reference": "fe0e69d7162cba0885791cf7eea5f0d7bc0f897e", + "url": "https://api.github.com/repos/symfony/console/zipball/2d5d973bf9933d46802b01010bd25c800c87c242", + "reference": "2d5d973bf9933d46802b01010bd25c800c87c242", "shasum": "" }, "require": { @@ -3040,7 +3063,7 @@ "symfony/process": "~3.4|~4.0" }, "suggest": { - "psr/log": "For using the console logger", + "psr/log-implementation": "For using the console logger", "symfony/event-dispatcher": "", "symfony/lock": "", "symfony/process": "" @@ -3048,7 +3071,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3075,20 +3098,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-01-03T07:38:00+00:00" + "time": "2018-05-30T07:26:09+00:00" }, { "name": "symfony/debug", - "version": "v4.0.3", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "9ae4223a661b56a9abdce144de4886cca37f198f" + "reference": "449f8b00b28ab6e6912c3e6b920406143b27193b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/9ae4223a661b56a9abdce144de4886cca37f198f", - "reference": "9ae4223a661b56a9abdce144de4886cca37f198f", + "url": "https://api.github.com/repos/symfony/debug/zipball/449f8b00b28ab6e6912c3e6b920406143b27193b", + "reference": "449f8b00b28ab6e6912c3e6b920406143b27193b", "shasum": "" }, "require": { @@ -3104,7 +3127,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3131,20 +3154,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2018-01-03T17:15:19+00:00" + "time": "2018-05-16T14:33:22+00:00" }, { "name": "symfony/dependency-injection", - "version": "v4.0.3", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "67bf5e4f4da85624f30a5e43b7f43225c8b71959" + "reference": "f2a3f0dc640a28b8aedd51b47ad6e6c5cebb3c00" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/67bf5e4f4da85624f30a5e43b7f43225c8b71959", - "reference": "67bf5e4f4da85624f30a5e43b7f43225c8b71959", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f2a3f0dc640a28b8aedd51b47ad6e6c5cebb3c00", + "reference": "f2a3f0dc640a28b8aedd51b47ad6e6c5cebb3c00", "shasum": "" }, "require": { @@ -3152,7 +3175,7 @@ "psr/container": "^1.0" }, "conflict": { - "symfony/config": "<3.4", + "symfony/config": "<4.1", "symfony/finder": "<3.4", "symfony/proxy-manager-bridge": "<3.4", "symfony/yaml": "<3.4" @@ -3161,7 +3184,7 @@ "psr/container-implementation": "1.0" }, "require-dev": { - "symfony/config": "~3.4|~4.0", + "symfony/config": "~4.1", "symfony/expression-language": "~3.4|~4.0", "symfony/yaml": "~3.4|~4.0" }, @@ -3175,7 +3198,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3202,20 +3225,20 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2018-01-04T15:52:56+00:00" + "time": "2018-05-25T14:55:38+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.0.3", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "74d33aac36208c4d6757807d9f598f0133a3a4eb" + "reference": "2391ed210a239868e7256eb6921b1bd83f3087b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/74d33aac36208c4d6757807d9f598f0133a3a4eb", - "reference": "74d33aac36208c4d6757807d9f598f0133a3a4eb", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/2391ed210a239868e7256eb6921b1bd83f3087b5", + "reference": "2391ed210a239868e7256eb6921b1bd83f3087b5", "shasum": "" }, "require": { @@ -3238,7 +3261,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3265,29 +3288,30 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2018-01-03T07:38:00+00:00" + "time": "2018-04-06T07:35:57+00:00" }, { "name": "symfony/filesystem", - "version": "v4.0.3", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "760e47a4ee64b4c48f4b30017011e09d4c0f05ed" + "reference": "562bf7005b55fd80d26b582d28e3e10f2dd5ae9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/760e47a4ee64b4c48f4b30017011e09d4c0f05ed", - "reference": "760e47a4ee64b4c48f4b30017011e09d4c0f05ed", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/562bf7005b55fd80d26b582d28e3e10f2dd5ae9c", + "reference": "562bf7005b55fd80d26b582d28e3e10f2dd5ae9c", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3314,20 +3338,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-01-03T07:38:00+00:00" + "time": "2018-05-30T07:26:09+00:00" }, { "name": "symfony/finder", - "version": "v4.0.3", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "8b08180f2b7ccb41062366b9ad91fbc4f1af8601" + "reference": "087e2ee0d74464a4c6baac4e90417db7477dc238" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/8b08180f2b7ccb41062366b9ad91fbc4f1af8601", - "reference": "8b08180f2b7ccb41062366b9ad91fbc4f1af8601", + "url": "https://api.github.com/repos/symfony/finder/zipball/087e2ee0d74464a4c6baac4e90417db7477dc238", + "reference": "087e2ee0d74464a4c6baac4e90417db7477dc238", "shasum": "" }, "require": { @@ -3336,7 +3360,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3363,20 +3387,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-01-03T07:38:00+00:00" + "time": "2018-05-16T14:33:22+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.0.3", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "03fe5171e35966f43453e2e5c15d7fe65f7fb23b" + "reference": "a916c88390fb861ee21f12a92b107d51bb68af99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/03fe5171e35966f43453e2e5c15d7fe65f7fb23b", - "reference": "03fe5171e35966f43453e2e5c15d7fe65f7fb23b", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/a916c88390fb861ee21f12a92b107d51bb68af99", + "reference": "a916c88390fb861ee21f12a92b107d51bb68af99", "shasum": "" }, "require": { @@ -3384,12 +3408,13 @@ "symfony/polyfill-mbstring": "~1.1" }, "require-dev": { + "predis/predis": "~1.0", "symfony/expression-language": "~3.4|~4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3416,33 +3441,34 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2018-01-03T17:15:19+00:00" + "time": "2018-05-25T14:55:38+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.0.3", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "f707ed09d3b5799a26c985de480d48b48540d41a" + "reference": "b5ab9d4cdbfd369083744b6b5dfbf454e31e5f90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f707ed09d3b5799a26c985de480d48b48540d41a", - "reference": "f707ed09d3b5799a26c985de480d48b48540d41a", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b5ab9d4cdbfd369083744b6b5dfbf454e31e5f90", + "reference": "b5ab9d4cdbfd369083744b6b5dfbf454e31e5f90", "shasum": "" }, "require": { "php": "^7.1.3", "psr/log": "~1.0", "symfony/debug": "~3.4|~4.0", - "symfony/event-dispatcher": "~3.4|~4.0", - "symfony/http-foundation": "~3.4|~4.0" + "symfony/event-dispatcher": "~4.1", + "symfony/http-foundation": "~4.1", + "symfony/polyfill-ctype": "~1.8" }, "conflict": { "symfony/config": "<3.4", - "symfony/dependency-injection": "<3.4", - "symfony/var-dumper": "<3.4", + "symfony/dependency-injection": "<4.1", + "symfony/var-dumper": "<4.1", "twig/twig": "<1.34|<2.4,>=2" }, "provide": { @@ -3454,7 +3480,7 @@ "symfony/config": "~3.4|~4.0", "symfony/console": "~3.4|~4.0", "symfony/css-selector": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", + "symfony/dependency-injection": "^4.1", "symfony/dom-crawler": "~3.4|~4.0", "symfony/expression-language": "~3.4|~4.0", "symfony/finder": "~3.4|~4.0", @@ -3463,7 +3489,7 @@ "symfony/stopwatch": "~3.4|~4.0", "symfony/templating": "~3.4|~4.0", "symfony/translation": "~3.4|~4.0", - "symfony/var-dumper": "~3.4|~4.0" + "symfony/var-dumper": "~4.1" }, "suggest": { "symfony/browser-kit": "", @@ -3475,7 +3501,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3502,20 +3528,20 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2018-01-05T08:54:25+00:00" + "time": "2018-05-30T12:52:34+00:00" }, { "name": "symfony/options-resolver", - "version": "v4.0.3", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "30d9240b30696a69e893534c9fc4a5c72ab6689b" + "reference": "9b9ab6043c57c8c5571bc846e6ebfd27dff3b589" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/30d9240b30696a69e893534c9fc4a5c72ab6689b", - "reference": "30d9240b30696a69e893534c9fc4a5c72ab6689b", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/9b9ab6043c57c8c5571bc846e6ebfd27dff3b589", + "reference": "9b9ab6043c57c8c5571bc846e6ebfd27dff3b589", "shasum": "" }, "require": { @@ -3524,7 +3550,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3556,20 +3582,75 @@ "configuration", "options" ], - "time": "2018-01-03T07:38:00+00:00" + "time": "2018-05-30T07:26:09+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/7cc359f1b7b80fc25ed7796be7d96adc9b354bae", + "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2018-04-30T19:57:29+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.6.0", + "version": "v1.8.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296" + "reference": "3296adf6a6454a050679cde90f95350ad604b171" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", - "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/3296adf6a6454a050679cde90f95350ad604b171", + "reference": "3296adf6a6454a050679cde90f95350ad604b171", "shasum": "" }, "require": { @@ -3581,7 +3662,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.6-dev" + "dev-master": "1.8-dev" } }, "autoload": { @@ -3615,20 +3696,20 @@ "portable", "shim" ], - "time": "2017-10-11T12:05:26+00:00" + "time": "2018-04-26T10:06:28+00:00" }, { "name": "symfony/polyfill-php70", - "version": "v1.6.0", + "version": "v1.8.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "0442b9c0596610bd24ae7b5f0a6cdbbc16d9fcff" + "reference": "77454693d8f10dd23bb24955cffd2d82db1007a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/0442b9c0596610bd24ae7b5f0a6cdbbc16d9fcff", - "reference": "0442b9c0596610bd24ae7b5f0a6cdbbc16d9fcff", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/77454693d8f10dd23bb24955cffd2d82db1007a6", + "reference": "77454693d8f10dd23bb24955cffd2d82db1007a6", "shasum": "" }, "require": { @@ -3638,7 +3719,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.6-dev" + "dev-master": "1.8-dev" } }, "autoload": { @@ -3674,20 +3755,20 @@ "portable", "shim" ], - "time": "2017-10-11T12:05:26+00:00" + "time": "2018-04-26T10:06:28+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.6.0", + "version": "v1.8.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "6de4f4884b97abbbed9f0a84a95ff2ff77254254" + "reference": "a4576e282d782ad82397f3e4ec1df8e0f0cafb46" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/6de4f4884b97abbbed9f0a84a95ff2ff77254254", - "reference": "6de4f4884b97abbbed9f0a84a95ff2ff77254254", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/a4576e282d782ad82397f3e4ec1df8e0f0cafb46", + "reference": "a4576e282d782ad82397f3e4ec1df8e0f0cafb46", "shasum": "" }, "require": { @@ -3696,7 +3777,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.6-dev" + "dev-master": "1.8-dev" } }, "autoload": { @@ -3729,20 +3810,20 @@ "portable", "shim" ], - "time": "2017-10-11T12:05:26+00:00" + "time": "2018-04-26T10:06:28+00:00" }, { "name": "symfony/process", - "version": "v4.0.3", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "2145b3e8137e463b1051b79440a59b38220944f0" + "reference": "73445bd33b0d337c060eef9652b94df72b6b3434" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/2145b3e8137e463b1051b79440a59b38220944f0", - "reference": "2145b3e8137e463b1051b79440a59b38220944f0", + "url": "https://api.github.com/repos/symfony/process/zipball/73445bd33b0d337c060eef9652b94df72b6b3434", + "reference": "73445bd33b0d337c060eef9652b94df72b6b3434", "shasum": "" }, "require": { @@ -3751,7 +3832,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3778,20 +3859,20 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-01-03T07:38:00+00:00" + "time": "2018-05-30T07:26:09+00:00" }, { "name": "symfony/stopwatch", - "version": "v4.0.3", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "d52321f0e2b596bd03b5d1dd6eebe71caa925704" + "reference": "07463bbbbbfe119045a24c4a516f92ebd2752784" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/d52321f0e2b596bd03b5d1dd6eebe71caa925704", - "reference": "d52321f0e2b596bd03b5d1dd6eebe71caa925704", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/07463bbbbbfe119045a24c4a516f92ebd2752784", + "reference": "07463bbbbbfe119045a24c4a516f92ebd2752784", "shasum": "" }, "require": { @@ -3800,7 +3881,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3827,24 +3908,25 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2018-01-03T07:38:00+00:00" + "time": "2018-02-19T16:51:42+00:00" }, { "name": "symfony/yaml", - "version": "v4.0.3", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "b84f646b9490d2101e2c25ddeec77ceefbda2eee" + "reference": "80e4bfa9685fc4a09acc4a857ec16974a9cd944e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/b84f646b9490d2101e2c25ddeec77ceefbda2eee", - "reference": "b84f646b9490d2101e2c25ddeec77ceefbda2eee", + "url": "https://api.github.com/repos/symfony/yaml/zipball/80e4bfa9685fc4a09acc4a857ec16974a9cd944e", + "reference": "80e4bfa9685fc4a09acc4a857ec16974a9cd944e", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" }, "conflict": { "symfony/console": "<3.4" @@ -3858,7 +3940,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3885,81 +3967,81 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2018-01-03T07:38:00+00:00" + "time": "2018-05-30T07:26:09+00:00" }, { - "name": "symplify/better-reflection-docblock", - "version": "v3.1.2", + "name": "symplify/better-phpdoc-parser", + "version": "v4.4.2", "source": { "type": "git", - "url": "https://github.com/Symplify/BetterReflectionDocBlock.git", - "reference": "7746ed526ffedfb4907a7ff83606a9e0f1e55c56" + "url": "https://github.com/Symplify/BetterPhpDocParser.git", + "reference": "73d5fbe4b5b4546e841b67ecd626d1ac85ca12df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/BetterReflectionDocBlock/zipball/7746ed526ffedfb4907a7ff83606a9e0f1e55c56", - "reference": "7746ed526ffedfb4907a7ff83606a9e0f1e55c56", + "url": "https://api.github.com/repos/Symplify/BetterPhpDocParser/zipball/73d5fbe4b5b4546e841b67ecd626d1ac85ca12df", + "reference": "73d5fbe4b5b4546e841b67ecd626d1ac85ca12df", "shasum": "" }, "require": { + "nette/utils": "^2.5", "php": "^7.1", - "phpdocumentor/reflection-docblock": "4.2", - "symplify/package-builder": "^3.1" + "phpstan/phpdoc-parser": "^0.2", + "symplify/package-builder": "^4.4.2" }, "require-dev": { - "phpunit/phpunit": "^6.5" + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "4.5-dev" } }, "autoload": { "psr-4": { - "Symplify\\BetterReflectionDocBlock\\": "src" + "Symplify\\BetterPhpDocParser\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Slim wrapper around phpdocumentor/reflection-docblock with better DX and simpler API.", - "time": "2018-01-02T22:35:18+00:00" + "description": "Slim wrapper around phpstan/phpdoc-parser with format preserving printer", + "time": "2018-06-09T23:03:09+00:00" }, { "name": "symplify/coding-standard", - "version": "v3.1.2", + "version": "v4.4.2", "source": { "type": "git", "url": "https://github.com/Symplify/CodingStandard.git", - "reference": "0985870bd373d65c69747c2ae854761497f96aac" + "reference": "6ec1f676202863f495c8b08e347e8575c270d3b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/CodingStandard/zipball/0985870bd373d65c69747c2ae854761497f96aac", - "reference": "0985870bd373d65c69747c2ae854761497f96aac", + "url": "https://api.github.com/repos/Symplify/CodingStandard/zipball/6ec1f676202863f495c8b08e347e8575c270d3b1", + "reference": "6ec1f676202863f495c8b08e347e8575c270d3b1", "shasum": "" }, "require": { - "friendsofphp/php-cs-fixer": "^2.9", + "friendsofphp/php-cs-fixer": "^2.12", "nette/finder": "^2.4", - "nette/utils": "^2.4", + "nette/utils": "^2.5", "php": "^7.1", - "phpdocumentor/reflection-docblock": "4.2", - "squizlabs/php_codesniffer": "^3.2", - "symplify/token-runner": "^3.1" + "squizlabs/php_codesniffer": "^3.3", + "symplify/token-runner": "^4.4.2" }, "require-dev": { "nette/application": "^2.4", - "phpunit/phpunit": "^6.5", - "symplify/easy-coding-standard": "^3.1", - "symplify/package-builder": "^3.1" + "phpunit/phpunit": "^7.0", + "symplify/easy-coding-standard-tester": "^4.4.2", + "symplify/package-builder": "^4.4.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "4.5-dev" } }, "autoload": { @@ -3972,51 +4054,56 @@ "MIT" ], "description": "Set of Symplify rules for PHP_CodeSniffer.", - "time": "2018-01-03T00:42:03+00:00" + "time": "2018-06-09T22:58:55+00:00" }, { "name": "symplify/easy-coding-standard", - "version": "v3.1.2", + "version": "v4.4.2", "source": { "type": "git", "url": "https://github.com/Symplify/EasyCodingStandard.git", - "reference": "0018936e9acecfa6df0919e2e05923d0b3677435" + "reference": "1a18f72e888b2b20e2f8ebad3058c3adc5b71b38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/EasyCodingStandard/zipball/0018936e9acecfa6df0919e2e05923d0b3677435", - "reference": "0018936e9acecfa6df0919e2e05923d0b3677435", + "url": "https://api.github.com/repos/Symplify/EasyCodingStandard/zipball/1a18f72e888b2b20e2f8ebad3058c3adc5b71b38", + "reference": "1a18f72e888b2b20e2f8ebad3058c3adc5b71b38", "shasum": "" }, "require": { - "friendsofphp/php-cs-fixer": "^2.9", - "nette/caching": "^2.4", - "nette/di": "^2.4", - "nette/neon": "^2.4", - "nette/robot-loader": "^2.4|^3.0.1", - "nette/utils": "^2.4", + "friendsofphp/php-cs-fixer": "^2.12", + "jean85/pretty-package-versions": "^1.1", + "nette/robot-loader": "^3.0.3", + "nette/utils": "^2.5", + "ocramius/package-versions": "^1.3", "php": "^7.1", - "slevomat/coding-standard": "^4.1", - "squizlabs/php_codesniffer": "^3.2", - "symfony/config": "^4.0", - "symfony/console": "^4.0", - "symfony/dependency-injection": "^4.0", - "symfony/finder": "^4.0", - "symfony/http-kernel": "^4.0", - "symfony/yaml": "^4.0", - "symplify/coding-standard": "^3.1", - "symplify/package-builder": "^3.1", - "symplify/token-runner": "^3.1" + "slevomat/coding-standard": "^4.5", + "squizlabs/php_codesniffer": "^3.3", + "symfony/cache": "^3.4|^4.0", + "symfony/config": "^3.4|^4.0", + "symfony/console": "^3.4|^4.0", + "symfony/dependency-injection": "^3.4|^4.0", + "symfony/finder": "^3.4|^4.0", + "symfony/http-kernel": "^3.4|^4.0", + "symfony/yaml": "^3.4|^4.0", + "symplify/coding-standard": "^4.4.2", + "symplify/package-builder": "^4.4.2", + "symplify/token-runner": "^4.4.2" }, "require-dev": { - "phpunit/phpunit": "^6.4" + "phpunit/phpunit": "^7.0", + "symplify/easy-coding-standard-tester": "^4.4.2" }, "bin": [ - "bin/easy-coding-standard", "bin/ecs", - "bin/easy-coding-standard.php" + "bin/ecs-container.php" ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.5-dev" + } + }, "autoload": { "psr-4": { "Symplify\\EasyCodingStandard\\": "src", @@ -4032,38 +4119,42 @@ "MIT" ], "description": "Use Coding Standard with 0-knowledge of PHP-CS-Fixer and PHP_CodeSniffer.", - "time": "2018-01-03T00:41:52+00:00" + "time": "2018-06-09T22:55:51+00:00" }, { "name": "symplify/package-builder", - "version": "v3.1.2", + "version": "v4.4.2", "source": { "type": "git", "url": "https://github.com/Symplify/PackageBuilder.git", - "reference": "0149e25615b98df5cdb25a155a1f10002cf1958a" + "reference": "1a5e54b3a9aae1652e2c83d675bf0132b5763ef0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/PackageBuilder/zipball/0149e25615b98df5cdb25a155a1f10002cf1958a", - "reference": "0149e25615b98df5cdb25a155a1f10002cf1958a", + "url": "https://api.github.com/repos/Symplify/PackageBuilder/zipball/1a5e54b3a9aae1652e2c83d675bf0132b5763ef0", + "reference": "1a5e54b3a9aae1652e2c83d675bf0132b5763ef0", "shasum": "" }, "require": { - "nette/di": "^2.4", - "nette/neon": "^2.4", + "nette/utils": "^2.5", "php": "^7.1", - "symfony/config": "^4.0", - "symfony/console": "^4.0", - "symfony/dependency-injection": "^4.0", - "symfony/finder": "^4.0", - "symfony/http-kernel": "^4.0", - "symfony/yaml": "^4.0" + "symfony/config": "^3.4|^4.0", + "symfony/console": "^3.4|^4.0", + "symfony/debug": "^3.4|^4.0", + "symfony/dependency-injection": "^3.4|^4.0", + "symfony/finder": "^3.4|^4.0", + "symfony/http-kernel": "^3.4|^4.0", + "symfony/yaml": "^3.4|^4.0" }, "require-dev": { - "phpunit/phpunit": "^6.5", - "tracy/tracy": "^2.4" + "phpunit/phpunit": "^7.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.5-dev" + } + }, "autoload": { "psr-4": { "Symplify\\PackageBuilder\\": "src" @@ -4074,36 +4165,40 @@ "MIT" ], "description": "Dependency Injection, Console and Kernel toolkit for Symplify packages.", - "time": "2018-01-02T22:35:18+00:00" + "time": "2018-06-09T00:55:06+00:00" }, { "name": "symplify/token-runner", - "version": "v3.1.2", + "version": "v4.4.2", "source": { "type": "git", "url": "https://github.com/Symplify/TokenRunner.git", - "reference": "5c4cc4f24507b6cbdb33026dfad5b46660c5b3ec" + "reference": "dadab58102d4ac853c1fbe6b49d51e1c0b7540fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/TokenRunner/zipball/5c4cc4f24507b6cbdb33026dfad5b46660c5b3ec", - "reference": "5c4cc4f24507b6cbdb33026dfad5b46660c5b3ec", + "url": "https://api.github.com/repos/Symplify/TokenRunner/zipball/dadab58102d4ac853c1fbe6b49d51e1c0b7540fc", + "reference": "dadab58102d4ac853c1fbe6b49d51e1c0b7540fc", "shasum": "" }, "require": { - "friendsofphp/php-cs-fixer": "^2.9", + "friendsofphp/php-cs-fixer": "^2.12", "nette/finder": "^2.4", - "nette/utils": "^2.4", + "nette/utils": "^2.5", "php": "^7.1", - "phpdocumentor/reflection-docblock": "^4.2", - "squizlabs/php_codesniffer": "^3.2", - "symplify/better-reflection-docblock": "^3.1", - "symplify/package-builder": "^3.1" + "squizlabs/php_codesniffer": "^3.3", + "symplify/better-phpdoc-parser": "^4.4.2", + "symplify/package-builder": "^4.4.2" }, "require-dev": { - "phpunit/phpunit": "^6.5" + "phpunit/phpunit": "^7.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.5-dev" + } + }, "autoload": { "psr-4": { "Symplify\\TokenRunner\\": "src" @@ -4114,7 +4209,7 @@ "MIT" ], "description": "Set of utils for PHP_CodeSniffer and PHP CS Fixer.", - "time": "2018-01-02T22:35:18+00:00" + "time": "2018-06-09T23:04:45+00:00" }, { "name": "theseer/tokenizer", diff --git a/easy-coding-standard.neon b/easy-coding-standard.neon deleted file mode 100644 index 028fe9e7..00000000 --- a/easy-coding-standard.neon +++ /dev/null @@ -1,65 +0,0 @@ -includes: - - vendor/symplify/easy-coding-standard/config/psr2.neon - - vendor/symplify/easy-coding-standard/config/php71.neon - - vendor/symplify/easy-coding-standard/config/clean-code.neon - - vendor/symplify/easy-coding-standard/config/common.neon - -checkers: - # spacing - - PhpCsFixer\Fixer\PhpTag\BlankLineAfterOpeningTagFixer - - PhpCsFixer\Fixer\Whitespace\BlankLineBeforeStatementFixer - - PhpCsFixer\Fixer\CastNotation\CastSpacesFixer - PhpCsFixer\Fixer\Operator\ConcatSpaceFixer: - spacing: none - - PhpCsFixer\Fixer\ClassNotation\MethodSeparationFixer - - PhpCsFixer\Fixer\ClassNotation\NoBlankLinesAfterClassOpeningFixer - PhpCsFixer\Fixer\Whitespace\NoSpacesAroundOffsetFixer: - positions: ['inside', 'outside'] - PhpCsFixer\Fixer\Operator\BinaryOperatorSpacesFixer: - align_double_arrow: false - align_equals: false - - # phpdoc - - PhpCsFixer\Fixer\Phpdoc\PhpdocSeparationFixer - - PhpCsFixer\Fixer\Phpdoc\PhpdocAlignFixer - - # Symplify - - Symplify\CodingStandard\Fixer\Import\ImportNamespacedNameFixer - - Symplify\CodingStandard\Fixer\Php\ClassStringToClassConstantFixer - - Symplify\CodingStandard\Fixer\Property\ArrayPropertyDefaultValueFixer - - Symplify\CodingStandard\Fixer\ArrayNotation\StandaloneLineInMultilineArrayFixer - -parameters: - exclude_checkers: - # from strict.neon - - PhpCsFixer\Fixer\PhpUnit\PhpUnitStrictFixer - - PhpCsFixer\Fixer\Strict\StrictComparisonFixer - # personal prefference - - PhpCsFixer\Fixer\Operator\NotOperatorWithSuccessorSpaceFixer - - skip: - PhpCsFixer\Fixer\Alias\RandomApiMigrationFixer: - # random_int() breaks code - - src/CrossValidation/RandomSplit.php - SlevomatCodingStandard\Sniffs\Classes\UnusedPrivateElementsSniff: - # magic calls - - src/Preprocessing/Normalizer.php - PhpCsFixer\Fixer\StringNotation\ExplicitStringVariableFixer: - # bugged - - src/Classification/DecisionTree/DecisionTreeLeaf.php - Symplify\CodingStandard\Fixer\Commenting\RemoveUselessDocBlockFixer: - # bug in fixer - - src/Math/LinearAlgebra/LUDecomposition.php - PhpCsFixer\Fixer\FunctionNotation\VoidReturnFixer: - # covariant return types - - src/Classification/Linear/Perceptron.php - - skip_codes: - # missing typehints - - SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingParameterTypeHint - - SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingTraversableParameterTypeHintSpecification - - SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingReturnTypeHint - - SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingTraversableReturnTypeHintSpecification - - SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingPropertyTypeHint - - SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingTraversablePropertyTypeHintSpecification - - PHP_CodeSniffer\Standards\Generic\Sniffs\CodeAnalysis\AssignmentInConditionSniff.Found diff --git a/ecs.yml b/ecs.yml new file mode 100644 index 00000000..405ef369 --- /dev/null +++ b/ecs.yml @@ -0,0 +1,68 @@ +imports: + - { resource: 'vendor/symplify/easy-coding-standard/config/psr2.yml' } + - { resource: 'vendor/symplify/easy-coding-standard/config/php71.yml' } + - { resource: 'vendor/symplify/easy-coding-standard/config/clean-code.yml' } + - { resource: 'vendor/symplify/easy-coding-standard/config/common.yml' } + +services: + # spacing + PhpCsFixer\Fixer\PhpTag\BlankLineAfterOpeningTagFixer: ~ + PhpCsFixer\Fixer\Whitespace\BlankLineBeforeStatementFixer: ~ + PhpCsFixer\Fixer\CastNotation\CastSpacesFixer: ~ + PhpCsFixer\Fixer\Operator\ConcatSpaceFixer: + spacing: none + PhpCsFixer\Fixer\ClassNotation\MethodSeparationFixer: ~ + PhpCsFixer\Fixer\ClassNotation\NoBlankLinesAfterClassOpeningFixer: ~ + PhpCsFixer\Fixer\Whitespace\NoSpacesAroundOffsetFixer: + positions: ['inside', 'outside'] + PhpCsFixer\Fixer\Operator\BinaryOperatorSpacesFixer: + align_double_arrow: false + align_equals: false + + # phpdoc + PhpCsFixer\Fixer\Phpdoc\PhpdocSeparationFixer: ~ + PhpCsFixer\Fixer\Phpdoc\PhpdocAlignFixer: ~ + + # Symplify + Symplify\CodingStandard\Fixer\Import\ImportNamespacedNameFixer: ~ + Symplify\CodingStandard\Fixer\Php\ClassStringToClassConstantFixer: ~ + Symplify\CodingStandard\Fixer\Property\ArrayPropertyDefaultValueFixer: ~ + Symplify\CodingStandard\Fixer\ArrayNotation\StandaloneLineInMultilineArrayFixer: ~ + +parameters: + exclude_checkers: + # from strict.neon + - 'PhpCsFixer\Fixer\PhpUnit\PhpUnitStrictFixer' + - 'PhpCsFixer\Fixer\Strict\StrictComparisonFixer' + # personal prefference + - 'PhpCsFixer\Fixer\Operator\NotOperatorWithSuccessorSpaceFixer' + + skip: + PhpCsFixer\Fixer\Alias\RandomApiMigrationFixer: + # random_int() breaks code + - 'src/CrossValidation/RandomSplit.php' + SlevomatCodingStandard\Sniffs\Classes\UnusedPrivateElementsSniff: + # magic calls + - 'src/Preprocessing/Normalizer.php' + PhpCsFixer\Fixer\StringNotation\ExplicitStringVariableFixer: + # bugged + - 'src/Classification/DecisionTree/DecisionTreeLeaf.php' + Symplify\CodingStandard\Fixer\Commenting\RemoveUselessDocBlockFixer: + # false positive - already fixed in master + - 'src/Helper/OneVsRest.php' + # bug in fixer + - 'src/Math/LinearAlgebra/LUDecomposition.php' + PhpCsFixer\Fixer\FunctionNotation\VoidReturnFixer: + # covariant return types + - 'src/Classification/Linear/Perceptron.php' + + # missing typehints + SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingParameterTypeHint: ~ + SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingTraversableParameterTypeHintSpecification: ~ + SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingReturnTypeHint: ~ + SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingTraversableReturnTypeHintSpecification: ~ + SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingPropertyTypeHint: ~ + SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingTraversablePropertyTypeHintSpecification: ~ + + # assignment in "while ($var = ...)" are ok + PHP_CodeSniffer\Standards\Generic\Sniffs\CodeAnalysis\AssignmentInConditionSniff.FoundInWhileCondition: \ No newline at end of file diff --git a/src/Classification/DecisionTree.php b/src/Classification/DecisionTree.php index 690f79c8..0f428c59 100644 --- a/src/Classification/DecisionTree.php +++ b/src/Classification/DecisionTree.php @@ -452,9 +452,7 @@ protected function getSplitNodesByColumn(int $column, DecisionTreeLeaf $node): a $rNodes = $this->getSplitNodesByColumn($column, $node->rightLeaf); } - $nodes = array_merge($nodes, $lNodes, $rNodes); - - return $nodes; + return array_merge($nodes, $lNodes, $rNodes); } /** diff --git a/src/Classification/Linear/Perceptron.php b/src/Classification/Linear/Perceptron.php index 038b4c88..ea49eeb7 100644 --- a/src/Classification/Linear/Perceptron.php +++ b/src/Classification/Linear/Perceptron.php @@ -9,6 +9,7 @@ use Phpml\Exception\InvalidArgumentException; use Phpml\Helper\OneVsRest; use Phpml\Helper\Optimizer\GD; +use Phpml\Helper\Optimizer\Optimizer; use Phpml\Helper\Optimizer\StochasticGD; use Phpml\Helper\Predictable; use Phpml\IncrementalEstimator; @@ -19,7 +20,7 @@ class Perceptron implements Classifier, IncrementalEstimator use Predictable, OneVsRest; /** - * @var \Phpml\Helper\Optimizer\Optimizer|GD|StochasticGD|null + * @var Optimizer|GD|StochasticGD|null */ protected $optimizer; diff --git a/src/Classification/NaiveBayes.php b/src/Classification/NaiveBayes.php index 8f092575..90425473 100644 --- a/src/Classification/NaiveBayes.php +++ b/src/Classification/NaiveBayes.php @@ -155,7 +155,7 @@ private function sampleProbability(array $sample, int $feature, string $label): // some libraries adopt taking log of calculations such as // scikit-learn did. // (See : https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/naive_bayes.py) - $pdf = -0.5 * log(2.0 * pi() * $std * $std); + $pdf = -0.5 * log(2.0 * M_PI * $std * $std); $pdf -= 0.5 * pow($value - $mean, 2) / ($std * $std); return $pdf; diff --git a/src/Clustering/KMeans/Space.php b/src/Clustering/KMeans/Space.php index 8d80dc06..aa60eb3c 100644 --- a/src/Clustering/KMeans/Space.php +++ b/src/Clustering/KMeans/Space.php @@ -197,7 +197,9 @@ protected function initializeKMPPClusters(int $clustersNumber): array $sum = random_int(0, (int) $sum); foreach ($this as $point) { - if (($sum -= $distances[$point]) > 0) { + $sum -= $distances[$point]; + + if ($sum > 0) { continue; } diff --git a/src/CrossValidation/StratifiedRandomSplit.php b/src/CrossValidation/StratifiedRandomSplit.php index d4508422..85dd5d13 100644 --- a/src/CrossValidation/StratifiedRandomSplit.php +++ b/src/CrossValidation/StratifiedRandomSplit.php @@ -33,9 +33,7 @@ private function splitByTarget(Dataset $dataset): array $split[$targets[$key]][] = $sample; } - $datasets = $this->createDatasets($uniqueTargets, $split); - - return $datasets; + return $this->createDatasets($uniqueTargets, $split); } private function createDatasets(array $uniqueTargets, array $split): array diff --git a/src/Dataset/Dataset.php b/src/Dataset/Dataset.php index f851d852..0c775a95 100644 --- a/src/Dataset/Dataset.php +++ b/src/Dataset/Dataset.php @@ -6,13 +6,7 @@ interface Dataset { - /** - * @return array - */ public function getSamples(): array; - /** - * @return array - */ public function getTargets(): array; } diff --git a/src/Dataset/SvmDataset.php b/src/Dataset/SvmDataset.php index c1e261b2..824fcff4 100644 --- a/src/Dataset/SvmDataset.php +++ b/src/Dataset/SvmDataset.php @@ -79,9 +79,7 @@ private static function parseLine(string $line): array $line = rtrim($line); $line = str_replace("\t", ' ', $line); - $columns = explode(' ', $line); - - return $columns; + return explode(' ', $line); } private static function parseTargetColumn(string $column): float diff --git a/src/Estimator.php b/src/Estimator.php index 8b98bb63..b4268896 100644 --- a/src/Estimator.php +++ b/src/Estimator.php @@ -6,15 +6,9 @@ interface Estimator { - /** - * @param array $samples - * @param array $targets - */ public function train(array $samples, array $targets); /** - * @param array $samples - * * @return mixed */ public function predict(array $samples); diff --git a/src/Helper/Predictable.php b/src/Helper/Predictable.php index 2ef90177..74d1cc07 100644 --- a/src/Helper/Predictable.php +++ b/src/Helper/Predictable.php @@ -7,8 +7,6 @@ trait Predictable { /** - * @param array $samples - * * @return mixed */ public function predict(array $samples) @@ -26,8 +24,6 @@ public function predict(array $samples) } /** - * @param array $sample - * * @return mixed */ abstract protected function predictSample(array $sample); diff --git a/src/Helper/Trainable.php b/src/Helper/Trainable.php index 86ffaf12..13887607 100644 --- a/src/Helper/Trainable.php +++ b/src/Helper/Trainable.php @@ -16,10 +16,6 @@ trait Trainable */ private $targets = []; - /** - * @param array $samples - * @param array $targets - */ public function train(array $samples, array $targets): void { $this->samples = array_merge($this->samples, $samples); diff --git a/src/IncrementalEstimator.php b/src/IncrementalEstimator.php index 4a0d1ccb..e356be01 100644 --- a/src/IncrementalEstimator.php +++ b/src/IncrementalEstimator.php @@ -6,10 +6,5 @@ interface IncrementalEstimator { - /** - * @param array $samples - * @param array $targets - * @param array $labels - */ public function partialTrain(array $samples, array $targets, array $labels = []); } diff --git a/src/Math/Distance.php b/src/Math/Distance.php index 9faa8e09..f49bd331 100644 --- a/src/Math/Distance.php +++ b/src/Math/Distance.php @@ -6,9 +6,5 @@ interface Distance { - /** - * @param array $a - * @param array $b - */ public function distance(array $a, array $b): float; } diff --git a/src/Math/LinearAlgebra/EigenvalueDecomposition.php b/src/Math/LinearAlgebra/EigenvalueDecomposition.php index 19f3c433..56e5b8ae 100644 --- a/src/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -18,7 +18,7 @@ * conditioned, or even singular, so the validity of the equation * A = V*D*inverse(V) depends upon V.cond(). * - * @author Paul Meagher + * @author Paul Meagher * @license PHP v3.0 * * @version 1.1 @@ -344,7 +344,7 @@ private function tql2(): void $iter = 0; do { // Could check iteration count here. - $iter += 1; + ++$iter; // Compute implicit shift $g = $this->d[$l]; $p = ($this->d[$l + 1] - $g) / (2.0 * $this->e[$l]); @@ -598,7 +598,7 @@ private function hqr2(): void $this->e[$n] = 0.0; --$n; $iter = 0; - // Two roots found + // Two roots found } elseif ($l == $n - 1) { $w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n]; $p = ($this->H[$n - 1][$n - 1] - $this->H[$n][$n]) / 2.0; @@ -661,7 +661,7 @@ private function hqr2(): void $n = $n - 2; $iter = 0; - // No convergence yet + // No convergence yet } else { // Form shift $x = $this->H[$n][$n]; diff --git a/src/Math/Statistic/Correlation.php b/src/Math/Statistic/Correlation.php index ce52f3b6..7039e39b 100644 --- a/src/Math/Statistic/Correlation.php +++ b/src/Math/Statistic/Correlation.php @@ -36,8 +36,6 @@ public static function pearson(array $x, array $y): float $b2 += pow($b, 2); } - $corr = $axb / sqrt((float) ($a2 * $b2)); - - return $corr; + return $axb / sqrt((float) ($a2 * $b2)); } } diff --git a/src/Math/Statistic/Gaussian.php b/src/Math/Statistic/Gaussian.php index 24aaeea6..ff8470ca 100644 --- a/src/Math/Statistic/Gaussian.php +++ b/src/Math/Statistic/Gaussian.php @@ -34,7 +34,7 @@ public function pdf(float $value) $std2 = $this->std ** 2; $mean = $this->mean; - return exp(-(($value - $mean) ** 2) / (2 * $std2)) / sqrt(2 * $std2 * pi()); + return exp(-(($value - $mean) ** 2) / (2 * $std2)) / sqrt(2 * $std2 * M_PI); } /** diff --git a/src/Metric/ClassificationReport.php b/src/Metric/ClassificationReport.php index 4409474d..969dcc6d 100644 --- a/src/Metric/ClassificationReport.php +++ b/src/Metric/ClassificationReport.php @@ -226,8 +226,7 @@ private static function getLabelIndexedArray(array $actualLabels, array $predict { $labels = array_values(array_unique(array_merge($actualLabels, $predictedLabels))); sort($labels); - $labels = array_combine($labels, array_fill(0, count($labels), 0)); - return $labels; + return array_combine($labels, array_fill(0, count($labels), 0)); } } diff --git a/src/Metric/ConfusionMatrix.php b/src/Metric/ConfusionMatrix.php index a1f49ce4..5fd3ac59 100644 --- a/src/Metric/ConfusionMatrix.php +++ b/src/Metric/ConfusionMatrix.php @@ -25,7 +25,7 @@ public static function compute(array $actualLabels, array $predictedLabels, arra $column = $labels[$predicted]; } - $matrix[$row][$column] += 1; + ++$matrix[$row][$column]; } return $matrix; @@ -47,8 +47,7 @@ private static function getUniqueLabels(array $labels): array { $labels = array_values(array_unique($labels)); sort($labels); - $labels = array_flip($labels); - return $labels; + return array_flip($labels); } } diff --git a/src/NeuralNetwork/Training.php b/src/NeuralNetwork/Training.php index fcb6d73c..e699c470 100644 --- a/src/NeuralNetwork/Training.php +++ b/src/NeuralNetwork/Training.php @@ -6,9 +6,5 @@ interface Training { - /** - * @param array $samples - * @param array $targets - */ public function train(array $samples, array $targets); } diff --git a/src/Preprocessing/Imputer/Strategy.php b/src/Preprocessing/Imputer/Strategy.php index 9125e06f..96397c12 100644 --- a/src/Preprocessing/Imputer/Strategy.php +++ b/src/Preprocessing/Imputer/Strategy.php @@ -7,8 +7,6 @@ interface Strategy { /** - * @param array $currentAxis - * * @return mixed */ public function replaceValue(array $currentAxis); diff --git a/tests/Math/Distance/ChebyshevTest.php b/tests/Math/Distance/ChebyshevTest.php index 262927b6..1a437314 100644 --- a/tests/Math/Distance/ChebyshevTest.php +++ b/tests/Math/Distance/ChebyshevTest.php @@ -15,7 +15,7 @@ class ChebyshevTest extends TestCase */ private $distanceMetric; - public function setUp(): void + protected function setUp(): void { $this->distanceMetric = new Chebyshev(); } diff --git a/tests/Math/Distance/EuclideanTest.php b/tests/Math/Distance/EuclideanTest.php index 734bbd2c..4be96d31 100644 --- a/tests/Math/Distance/EuclideanTest.php +++ b/tests/Math/Distance/EuclideanTest.php @@ -15,7 +15,7 @@ class EuclideanTest extends TestCase */ private $distanceMetric; - public function setUp(): void + protected function setUp(): void { $this->distanceMetric = new Euclidean(); } diff --git a/tests/Math/Distance/ManhattanTest.php b/tests/Math/Distance/ManhattanTest.php index 2eb9f06d..1dd5e46a 100644 --- a/tests/Math/Distance/ManhattanTest.php +++ b/tests/Math/Distance/ManhattanTest.php @@ -15,7 +15,7 @@ class ManhattanTest extends TestCase */ private $distanceMetric; - public function setUp(): void + protected function setUp(): void { $this->distanceMetric = new Manhattan(); } diff --git a/tests/Math/Distance/MinkowskiTest.php b/tests/Math/Distance/MinkowskiTest.php index 6c7b8975..558c31f4 100644 --- a/tests/Math/Distance/MinkowskiTest.php +++ b/tests/Math/Distance/MinkowskiTest.php @@ -15,7 +15,7 @@ class MinkowskiTest extends TestCase */ private $distanceMetric; - public function setUp(): void + protected function setUp(): void { $this->distanceMetric = new Minkowski(); } diff --git a/tests/Math/MatrixTest.php b/tests/Math/MatrixTest.php index 50cabacb..7adde6ce 100644 --- a/tests/Math/MatrixTest.php +++ b/tests/Math/MatrixTest.php @@ -136,10 +136,10 @@ public function testThrowExceptionWhenInverseIfMatrixIsSingular(): void { $this->expectException(MatrixException::class); $matrix = new Matrix([ - [0, 0, 0], - [0, 0, 0], - [0, 0, 0], - ]); + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + ]); $matrix->inverse(); } diff --git a/tests/Tokenization/WhitespaceTokenizerTest.php b/tests/Tokenization/WhitespaceTokenizerTest.php index b9e40c0d..03e8f7ea 100644 --- a/tests/Tokenization/WhitespaceTokenizerTest.php +++ b/tests/Tokenization/WhitespaceTokenizerTest.php @@ -18,8 +18,8 @@ public function testTokenizationOnAscii(): void Nulla vitae congue lorem.'; $tokens = ['Lorem', 'ipsum', 'dolor', 'sit', 'amet,', 'consectetur', 'adipiscing', 'elit.', - 'Cras', 'consectetur,', 'dui', 'et', 'lobortis', 'auctor.', - 'Nulla', 'vitae', 'congue', 'lorem.', ]; + 'Cras', 'consectetur,', 'dui', 'et', 'lobortis', 'auctor.', + 'Nulla', 'vitae', 'congue', 'lorem.', ]; $this->assertEquals($tokens, $tokenizer->tokenize($text)); } @@ -33,8 +33,8 @@ public function testTokenizationOnUtf8(): void 殍涾烰 齞齝囃 蹅輶 鄜, 孻憵 擙樲橚 藒襓謥 岯岪弨 蒮 廞徲 孻憵懥 趡趛踠 槏'; $tokens = ['鋍鞎', '鳼', '鞮鞢騉', '袟袘觕,', '炟砏', '蒮', '謺貙蹖', '偢偣唲', '蒛', '箷箯緷', '鑴鱱爧', '覮轀,', - '剆坲', '煘煓瑐', '鬐鶤鶐', '飹勫嫢', '銪', '餀', '枲柊氠', '鍎鞚韕', '焲犈,', - '殍涾烰', '齞齝囃', '蹅輶', '鄜,', '孻憵', '擙樲橚', '藒襓謥', '岯岪弨', '蒮', '廞徲', '孻憵懥', '趡趛踠', '槏', ]; + '剆坲', '煘煓瑐', '鬐鶤鶐', '飹勫嫢', '銪', '餀', '枲柊氠', '鍎鞚韕', '焲犈,', + '殍涾烰', '齞齝囃', '蹅輶', '鄜,', '孻憵', '擙樲橚', '藒襓謥', '岯岪弨', '蒮', '廞徲', '孻憵懥', '趡趛踠', '槏', ]; $this->assertEquals($tokens, $tokenizer->tokenize($text)); } diff --git a/tests/Tokenization/WordTokenizerTest.php b/tests/Tokenization/WordTokenizerTest.php index d18edb6e..387e0f8b 100644 --- a/tests/Tokenization/WordTokenizerTest.php +++ b/tests/Tokenization/WordTokenizerTest.php @@ -18,8 +18,8 @@ public function testTokenizationOnAscii(): void Nulla vitae ,.,/ congue lorem.'; $tokens = ['Lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur', 'adipiscing', 'elit', - 'Cras', 'consectetur', 'dui', 'et', 'lobortis', 'auctor', - 'Nulla', 'vitae', 'congue', 'lorem', ]; + 'Cras', 'consectetur', 'dui', 'et', 'lobortis', 'auctor', + 'Nulla', 'vitae', 'congue', 'lorem', ]; $this->assertEquals($tokens, $tokenizer->tokenize($text)); } @@ -33,8 +33,8 @@ public function testTokenizationOnUtf8(): void 殍涾烰 齞齝囃 蹅輶 鄜, 孻憵 擙樲橚 藒襓謥 岯岪弨 蒮 廞徲 孻憵懥 趡趛踠 槏'; $tokens = ['鋍鞎', '鞮鞢騉', '袟袘觕', '炟砏', '謺貙蹖', '偢偣唲', '箷箯緷', '鑴鱱爧', '覮轀', - '剆坲', '煘煓瑐', '鬐鶤鶐', '飹勫嫢', '枲柊氠', '鍎鞚韕', '焲犈', - '殍涾烰', '齞齝囃', '蹅輶', '孻憵', '擙樲橚', '藒襓謥', '岯岪弨', '廞徲', '孻憵懥', '趡趛踠', ]; + '剆坲', '煘煓瑐', '鬐鶤鶐', '飹勫嫢', '枲柊氠', '鍎鞚韕', '焲犈', + '殍涾烰', '齞齝囃', '蹅輶', '孻憵', '擙樲橚', '藒襓謥', '岯岪弨', '廞徲', '孻憵懥', '趡趛踠', ]; $this->assertEquals($tokens, $tokenizer->tokenize($text)); } From ab22cc5b68c5aaf1f9d7150ac41d03bd831eef59 Mon Sep 17 00:00:00 2001 From: Yuji Uchiyama Date: Thu, 21 Jun 2018 06:28:11 +0900 Subject: [PATCH 278/328] Change the default kernel type in SVC to Kernel::RBF (#267) * Change the default kernel type in SVC to Kernel::RBF * Update CHANGELOG.md --- CHANGELOG.md | 1 + docs/machine-learning/classification/svc.md | 2 +- src/Classification/SVC.php | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efd2174b..13bae105 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG This changelog references the relevant changes done in PHP-ML library. * Unreleased + * feature [Dataset] changed the default kernel type in SVC to Kernel::RBF (#267) * feature [Clustering] added KMeans associative clustering (#262) * feature [Dataset] added removeColumns function to ArrayDataset (#249) * feature [Dataset] added a SvmDataset class for SVM-Light (or LibSVM) format files (#237) diff --git a/docs/machine-learning/classification/svc.md b/docs/machine-learning/classification/svc.md index da0511c8..99b4da01 100644 --- a/docs/machine-learning/classification/svc.md +++ b/docs/machine-learning/classification/svc.md @@ -4,7 +4,7 @@ Classifier implementing Support Vector Machine based on libsvm. ### Constructor Parameters -* $kernel (int) - kernel type to be used in the algorithm (default Kernel::LINEAR) +* $kernel (int) - kernel type to be used in the algorithm (default Kernel::RBF) * $cost (float) - parameter C of C-SVC (default 1.0) * $degree (int) - degree of the Kernel::POLYNOMIAL function (default 3) * $gamma (float) - kernel coefficient for ‘Kernel::RBF’, ‘Kernel::POLYNOMIAL’ and ‘Kernel::SIGMOID’. If gamma is ‘null’ then 1/features will be used instead. diff --git a/src/Classification/SVC.php b/src/Classification/SVC.php index de71bbe6..fbc47bac 100644 --- a/src/Classification/SVC.php +++ b/src/Classification/SVC.php @@ -11,7 +11,7 @@ class SVC extends SupportVectorMachine implements Classifier { public function __construct( - int $kernel = Kernel::LINEAR, + int $kernel = Kernel::RBF, float $cost = 1.0, int $degree = 3, ?float $gamma = null, From 4a3194fd9060465d281d2ed90338d39b66b75e63 Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Mon, 25 Jun 2018 23:19:13 +0200 Subject: [PATCH 279/328] Add .gitattributes (#287) --- .gitattributes | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..93f6619e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,14 @@ +* text=auto + +/docs export-ignore +/tests export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.travis.yml export-ignore +/ecs.yml export-ignore +/CHANGELOG.md export-ignore +/CONTRIBUTING.md export-ignore +/mkdocs.yml export-ignore +/phpbench.json export-ignore +/phpstan.neon export-ignore +/phpunit.xml export-ignore From 8fdb3d11fcac42e12aebd7f86d5058fd4921e003 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 4 Jul 2018 23:42:22 +0200 Subject: [PATCH 280/328] Make SVM non-locale aware (#288) --- CHANGELOG.md | 1 + src/SupportVectorMachine/DataTransformer.php | 2 +- .../SupportVectorMachine.php | 2 +- tests/Classification/SVCTest.php | 23 +++++++++++++++++-- .../DataTransformerTest.php | 16 ++++++------- 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13bae105..59902421 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ This changelog references the relevant changes done in PHP-ML library. * fix ensure DataTransformer::testSet samples array is not empty (#204) * fix optimizer initial theta randomization (#239) * fix travis build on osx (#281) + * fix SVM locale (non-locale aware) (#288) * typo, tests, code styles and documentation fixes (#265, #261, #254, #253, #251, #250, #248, #245, #243) * 0.6.2 (2018-02-22) diff --git a/src/SupportVectorMachine/DataTransformer.php b/src/SupportVectorMachine/DataTransformer.php index 06272e20..fcc18f64 100644 --- a/src/SupportVectorMachine/DataTransformer.php +++ b/src/SupportVectorMachine/DataTransformer.php @@ -104,7 +104,7 @@ private static function sampleRow(array $sample): string { $row = []; foreach ($sample as $index => $feature) { - $row[] = sprintf('%s:%s', $index + 1, $feature); + $row[] = sprintf('%s:%F', $index + 1, $feature); } return implode(' ', $row); diff --git a/src/SupportVectorMachine/SupportVectorMachine.php b/src/SupportVectorMachine/SupportVectorMachine.php index be16ff4f..4c2f87b9 100644 --- a/src/SupportVectorMachine/SupportVectorMachine.php +++ b/src/SupportVectorMachine/SupportVectorMachine.php @@ -269,7 +269,7 @@ private function getOSExtension(): string private function buildTrainCommand(string $trainingSetFileName, string $modelFileName): string { return sprintf( - '%ssvm-train%s -s %s -t %s -c %s -n %s -d %s%s -r %s -p %s -m %s -e %s -h %d -b %d %s %s', + '%ssvm-train%s -s %s -t %s -c %s -n %F -d %s%s -r %s -p %F -m %F -e %F -h %d -b %d %s %s', $this->binPath, $this->getOSExtension(), $this->type, diff --git a/tests/Classification/SVCTest.php b/tests/Classification/SVCTest.php index 0709e047..1ec15418 100644 --- a/tests/Classification/SVCTest.php +++ b/tests/Classification/SVCTest.php @@ -57,13 +57,32 @@ public function testSaveAndRestore(): void $classifier->train($trainSamples, $trainLabels); $predicted = $classifier->predict($testSamples); - $filename = 'svc-test-'.random_int(100, 999).'-'.uniqid(); - $filepath = tempnam(sys_get_temp_dir(), $filename); + $filepath = tempnam(sys_get_temp_dir(), uniqid('svc-test', true)); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); $restoredClassifier = $modelManager->restoreFromFile($filepath); $this->assertEquals($classifier, $restoredClassifier); $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + $this->assertEquals($predicted, $testLabels); + } + + public function testWithNonDotDecimalLocale(): void + { + $currentLocale = setlocale(LC_NUMERIC, '0'); + setlocale(LC_NUMERIC, 'pl_PL.utf8'); + + $trainSamples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; + $trainLabels = ['a', 'a', 'a', 'b', 'b', 'b']; + + $testSamples = [[3, 2], [5, 1], [4, 3]]; + $testLabels = ['b', 'b', 'b']; + + $classifier = new SVC(Kernel::LINEAR, $cost = 1000); + $classifier->train($trainSamples, $trainLabels); + + $this->assertEquals($classifier->predict($testSamples), $testLabels); + + setlocale(LC_NUMERIC, $currentLocale); } } diff --git a/tests/SupportVectorMachine/DataTransformerTest.php b/tests/SupportVectorMachine/DataTransformerTest.php index 75c23d6c..df298062 100644 --- a/tests/SupportVectorMachine/DataTransformerTest.php +++ b/tests/SupportVectorMachine/DataTransformerTest.php @@ -16,10 +16,10 @@ public function testTransformDatasetToTrainingSet(): void $labels = ['a', 'a', 'b', 'b']; $trainingSet = - '0 1:1 2:1 '.PHP_EOL. - '0 1:2 2:1 '.PHP_EOL. - '1 1:3 2:2 '.PHP_EOL. - '1 1:4 2:5 '.PHP_EOL + '0 1:1.000000 2:1.000000 '.PHP_EOL. + '0 1:2.000000 2:1.000000 '.PHP_EOL. + '1 1:3.000000 2:2.000000 '.PHP_EOL. + '1 1:4.000000 2:5.000000 '.PHP_EOL ; $this->assertEquals($trainingSet, DataTransformer::trainingSet($samples, $labels)); @@ -30,10 +30,10 @@ public function testTransformSamplesToTestSet(): void $samples = [[1, 1], [2, 1], [3, 2], [4, 5]]; $testSet = - '0 1:1 2:1 '.PHP_EOL. - '0 1:2 2:1 '.PHP_EOL. - '0 1:3 2:2 '.PHP_EOL. - '0 1:4 2:5 '.PHP_EOL + '0 1:1.000000 2:1.000000 '.PHP_EOL. + '0 1:2.000000 2:1.000000 '.PHP_EOL. + '0 1:3.000000 2:2.000000 '.PHP_EOL. + '0 1:4.000000 2:5.000000 '.PHP_EOL ; $this->assertEquals($testSet, DataTransformer::testSet($samples)); From 15adf9e25272cbde8fe2880a8d12e22ca178f0e9 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 31 Jul 2018 23:28:07 +0200 Subject: [PATCH 281/328] Update build status badge from travis-ci --- README.md | 4 ++-- docs/index.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 192195ef..d93996f7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.1-8892BF.svg)](https://php.net/) [![Latest Stable Version](https://img.shields.io/packagist/v/php-ai/php-ml.svg)](https://packagist.org/packages/php-ai/php-ml) -[![Build Status](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/build.png?b=master)](https://scrutinizer-ci.com/g/php-ai/php-ml/build-status/master) +[![Build Status](https://travis-ci.org/php-ai/php-ml.svg?branch=master)](https://travis-ci.org/php-ai/php-ml) [![Documentation Status](https://readthedocs.org/projects/php-ml/badge/?version=master)](http://php-ml.readthedocs.org/) [![Total Downloads](https://poser.pugx.org/php-ai/php-ml/downloads.svg)](https://packagist.org/packages/php-ai/php-ml) [![License](https://poser.pugx.org/php-ai/php-ml/license.svg)](https://packagist.org/packages/php-ai/php-ml) @@ -10,7 +10,7 @@ [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/php-ai/php-ml/?branch=master)

- +

Fresh approach to Machine Learning in PHP. Algorithms, Cross Validation, Neural Network, Preprocessing, Feature Extraction and much more in one library. diff --git a/docs/index.md b/docs/index.md index 4ba9d563..12cbbd5f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,7 +2,7 @@ [![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.1-8892BF.svg)](https://php.net/) [![Latest Stable Version](https://img.shields.io/packagist/v/php-ai/php-ml.svg)](https://packagist.org/packages/php-ai/php-ml) -[![Build Status](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/build.png?b=master)](https://scrutinizer-ci.com/g/php-ai/php-ml/build-status/master) +[![Build Status](https://travis-ci.org/php-ai/php-ml.svg?branch=master)](https://travis-ci.org/php-ai/php-ml) [![Documentation Status](https://readthedocs.org/projects/php-ml/badge/?version=master)](http://php-ml.readthedocs.org/) [![Total Downloads](https://poser.pugx.org/php-ai/php-ml/downloads.svg)](https://packagist.org/packages/php-ai/php-ml) [![License](https://poser.pugx.org/php-ai/php-ml/license.svg)](https://packagist.org/packages/php-ai/php-ml) @@ -10,7 +10,7 @@ [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/php-ai/php-ml/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/php-ai/php-ml/?branch=master)

- +

Fresh approach to Machine Learning in PHP. Algorithms, Cross Validation, Neural Network, Preprocessing, Feature Extraction and much more in one library. From e255369636abdd5e3b3c5b156024418912bf9968 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 10 Oct 2018 21:36:18 +0200 Subject: [PATCH 282/328] Fix Imputer docs and check if train data was set (#314) * Update docs for Imputer class * Throw exception when trying to transform imputer without train data * Update changelog --- CHANGELOG.md | 1 + .../imputation-missing-values.md | 19 +++++++++++++++++++ src/Preprocessing/Imputer.php | 5 +++++ tests/Preprocessing/ImputerTest.php | 15 +++++++++++++++ 4 files changed, 40 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59902421..3c44b94b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ This changelog references the relevant changes done in PHP-ML library. * feature [Dataset] added removeColumns function to ArrayDataset (#249) * feature [Dataset] added a SvmDataset class for SVM-Light (or LibSVM) format files (#237) * feature [Optimizer] removed $initialTheta property and renamed setInitialTheta method to setTheta (#252) + * change [Imputer] Throw exception when trying to transform without train data (#314) * enhancement Add performance test for LeastSquares (#263) * enhancement Micro optimization for matrix multiplication (#255) * enhancement Throw proper exception (#259, #251) diff --git a/docs/machine-learning/preprocessing/imputation-missing-values.md b/docs/machine-learning/preprocessing/imputation-missing-values.md index 48a5b3a4..219db22c 100644 --- a/docs/machine-learning/preprocessing/imputation-missing-values.md +++ b/docs/machine-learning/preprocessing/imputation-missing-values.md @@ -8,6 +8,7 @@ To solve this problem you can use the `Imputer` class. * $missingValue (mixed) - this value will be replaced (default null) * $strategy (Strategy) - imputation strategy (read to use: MeanStrategy, MedianStrategy, MostFrequentStrategy) * $axis (int) - axis for strategy, Imputer::AXIS_COLUMN or Imputer::AXIS_ROW +* $samples (array) - array of samples to train ``` $imputer = new Imputer(null, new MeanStrategy(), Imputer::AXIS_COLUMN); @@ -34,6 +35,7 @@ $data = [ ]; $imputer = new Imputer(null, new MeanStrategy(), Imputer::AXIS_COLUMN); +$imputer->fit($data); $imputer->transform($data); /* @@ -46,3 +48,20 @@ $data = [ */ ``` + +You can also use `$samples` constructer parameter instead of `fit` method: + +``` +use Phpml\Preprocessing\Imputer; +use Phpml\Preprocessing\Imputer\Strategy\MeanStrategy; + +$data = [ + [1, null, 3, 4], + [4, 3, 2, 1], + [null, 6, 7, 8], + [8, 7, null, 5], +]; + +$imputer = new Imputer(null, new MeanStrategy(), Imputer::AXIS_COLUMN, $data); +$imputer->transform($data); +``` diff --git a/src/Preprocessing/Imputer.php b/src/Preprocessing/Imputer.php index fdce6664..e5b5af84 100644 --- a/src/Preprocessing/Imputer.php +++ b/src/Preprocessing/Imputer.php @@ -4,6 +4,7 @@ namespace Phpml\Preprocessing; +use Phpml\Exception\InvalidOperationException; use Phpml\Preprocessing\Imputer\Strategy; class Imputer implements Preprocessor @@ -50,6 +51,10 @@ public function fit(array $samples, ?array $targets = null): void public function transform(array &$samples): void { + if ($this->samples === []) { + throw new InvalidOperationException('Missing training samples for Imputer.'); + } + foreach ($samples as &$sample) { $this->preprocessSample($sample); } diff --git a/tests/Preprocessing/ImputerTest.php b/tests/Preprocessing/ImputerTest.php index c229c151..1078e547 100644 --- a/tests/Preprocessing/ImputerTest.php +++ b/tests/Preprocessing/ImputerTest.php @@ -4,6 +4,7 @@ namespace Phpml\Tests\Preprocessing; +use Phpml\Exception\InvalidOperationException; use Phpml\Preprocessing\Imputer; use Phpml\Preprocessing\Imputer\Strategy\MeanStrategy; use Phpml\Preprocessing\Imputer\Strategy\MedianStrategy; @@ -173,4 +174,18 @@ public function testImputerWorksOnFitSamples(): void $this->assertEquals($imputeData, $data, '', $delta = 0.01); } + + public function testThrowExceptionWhenTryingToTransformWithoutTrainSamples(): void + { + $this->expectException(InvalidOperationException::class); + + $data = [ + [1, 3, null], + [6, null, 8], + [null, 7, 5], + ]; + + $imputer = new Imputer(null, new MeanStrategy(), Imputer::AXIS_COLUMN); + $imputer->transform($data); + } } From d29c5906df6c736f1e8775b0c23b0b3b2afafa6b Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Mon, 15 Oct 2018 19:47:42 +0200 Subject: [PATCH 283/328] 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 13963f3d..35678d5b 100644 --- a/src/Classification/MLPClassifier.php +++ b/src/Classification/MLPClassifier.php @@ -41,7 +41,7 @@ protected function predictSample(array $sample) } } - return $this->classes[$predictedClass]; + return $predictedClass; } /** @@ -49,9 +49,8 @@ protected function predictSample(array $sample) */ 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 1c681f89..1c67c041 100644 --- a/src/NeuralNetwork/Layer.php +++ b/src/NeuralNetwork/Layer.php @@ -41,12 +41,9 @@ public function getNodes(): array 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 4f053988..03bfef53 100644 --- a/src/NeuralNetwork/Network/LayeredNetwork.php +++ b/src/NeuralNetwork/Network/LayeredNetwork.php @@ -51,8 +51,6 @@ public function getOutput(): array /** * @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 36260639..8ff49bc6 100644 --- a/src/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/NeuralNetwork/Network/MultilayerPerceptron.php @@ -69,6 +69,10 @@ public function __construct(int $inputLayerFeatures, array $hiddenLayers, array 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 @@ public function setLearningRate(float $learningRate): void $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 47d606da..c5376069 100644 --- a/src/NeuralNetwork/Node/Neuron.php +++ b/src/NeuralNetwork/Node/Neuron.php @@ -44,7 +44,7 @@ public function addSynapse(Synapse $synapse): void /** * @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 d3680b60..ae0871dc 100644 --- a/tests/Classification/MLPClassifierTest.php +++ b/tests/Classification/MLPClassifierTest.php @@ -183,7 +183,7 @@ public function testSaveAndRestore(): void $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 @@ public function testSaveAndRestoreWithPartialTraining(): void $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 @@ public function testThrowExceptionOnInvalidClassesNumber(): void 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 006733f5..cea2a2f2 100644 --- a/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php +++ b/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php @@ -4,6 +4,7 @@ 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 @@ 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 */ From 9c9705a32c2e52202f638e5e033b5909638fdb28 Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Tue, 16 Oct 2018 19:35:38 +0200 Subject: [PATCH 284/328] Update changelog (#316) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c44b94b..12a9c3a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ This changelog references the relevant changes done in PHP-ML library. * fix travis build on osx (#281) * fix SVM locale (non-locale aware) (#288) * typo, tests, code styles and documentation fixes (#265, #261, #254, #253, #251, #250, #248, #245, #243) + * change [MLPClassifier] return labels in output (#315) * 0.6.2 (2018-02-22) * Fix Apriori array keys (#238) From 0beb407b16de3dd2a2c13026b3f684fb9fd263e8 Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Tue, 16 Oct 2018 21:42:06 +0200 Subject: [PATCH 285/328] Update easy coding standard to ^5.1 (#317) --- composer.json | 4 +- composer.lock | 665 +++++++++++------- src/Association/Apriori.php | 3 +- src/Classification/DecisionTree.php | 10 +- src/Classification/Ensemble/AdaBoost.php | 3 +- src/Classification/Ensemble/Bagging.php | 3 +- src/Classification/Ensemble/RandomForest.php | 15 +- src/Classification/KNearestNeighbors.php | 5 +- src/Classification/Linear/DecisionStump.php | 3 +- .../Linear/LogisticRegression.php | 18 +- src/Classification/Linear/Perceptron.php | 3 +- src/Classification/NaiveBayes.php | 3 +- src/Dataset/ArrayDataset.php | 2 +- src/Dataset/SvmDataset.php | 2 +- src/DimensionReduction/PCA.php | 2 +- .../TokenCountVectorizer.php | 6 +- .../UnivariateLinearRegression.php | 10 +- .../LinearAlgebra/EigenvalueDecomposition.php | 84 +-- src/Math/LinearAlgebra/LUDecomposition.php | 2 +- src/Math/Matrix.php | 2 +- src/Math/Statistic/ANOVA.php | 2 +- src/Preprocessing/Normalizer.php | 12 +- .../Classification/KNearestNeighborsTest.php | 3 +- tests/Classification/Linear/AdalineTest.php | 6 +- .../Linear/LogisticRegressionTest.php | 12 +- .../Classification/Linear/PerceptronTest.php | 8 +- tests/Classification/NaiveBayesTest.php | 6 +- tests/Dataset/SvmDatasetTest.php | 38 +- tests/DimensionReduction/KernelPCATest.php | 9 +- tests/DimensionReduction/LDATest.php | 13 +- tests/DimensionReduction/PCATest.php | 13 +- tests/Metric/ClassificationReportTest.php | 2 +- .../SupportVectorMachineTest.php | 2 +- 33 files changed, 591 insertions(+), 380 deletions(-) diff --git a/composer.json b/composer.json index 664eeebb..7b1cbb65 100644 --- a/composer.json +++ b/composer.json @@ -28,8 +28,8 @@ "phpstan/phpstan-shim": "^0.9", "phpstan/phpstan-strict-rules": "^0.9.0", "phpunit/phpunit": "^7.0.0", - "symplify/coding-standard": "^4.4", - "symplify/easy-coding-standard": "^4.4" + "symplify/coding-standard": "^5.1", + "symplify/easy-coding-standard": "^5.1" }, "config": { "preferred-install": "dist", diff --git a/composer.lock b/composer.lock index 5a886975..7a2beb9a 100644 --- a/composer.lock +++ b/composer.lock @@ -1,10 +1,10 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "46e6ba23009cf16bec8046ed302395b3", + "content-hash": "cb4240c977f956be78a7fa686c77d0f2", "packages": [], "packages-dev": [ { @@ -126,16 +126,16 @@ }, { "name": "composer/xdebug-handler", - "version": "1.1.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "c919dc6c62e221fc6406f861ea13433c0aa24f08" + "reference": "b8e9745fb9b06ea6664d8872c4505fb16df4611c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/c919dc6c62e221fc6406f861ea13433c0aa24f08", - "reference": "c919dc6c62e221fc6406f861ea13433c0aa24f08", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/b8e9745fb9b06ea6664d8872c4505fb16df4611c", + "reference": "b8e9745fb9b06ea6664d8872c4505fb16df4611c", "shasum": "" }, "require": { @@ -166,7 +166,7 @@ "Xdebug", "performance" ], - "time": "2018-04-11T15:42:36+00:00" + "time": "2018-08-31T19:07:57+00:00" }, { "name": "doctrine/annotations", @@ -346,21 +346,21 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v2.12.1", + "version": "v2.13.0", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "beef6cbe6dec7205edcd143842a49f9a691859a6" + "reference": "7136aa4e0c5f912e8af82383775460d906168a10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/beef6cbe6dec7205edcd143842a49f9a691859a6", - "reference": "beef6cbe6dec7205edcd143842a49f9a691859a6", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/7136aa4e0c5f912e8af82383775460d906168a10", + "reference": "7136aa4e0c5f912e8af82383775460d906168a10", "shasum": "" }, "require": { "composer/semver": "^1.4", - "composer/xdebug-handler": "^1.0", + "composer/xdebug-handler": "^1.2", "doctrine/annotations": "^1.2", "ext-json": "*", "ext-tokenizer": "*", @@ -389,7 +389,7 @@ "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.0.1", "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.0.1", "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1", - "phpunitgoodpractices/traits": "^1.5", + "phpunitgoodpractices/traits": "^1.5.1", "symfony/phpunit-bridge": "^4.0" }, "suggest": { @@ -402,6 +402,11 @@ "php-cs-fixer" ], "type": "application", + "extra": { + "branch-alias": { + "dev-master": "2.13-dev" + } + }, "autoload": { "psr-4": { "PhpCsFixer\\": "src/" @@ -433,7 +438,7 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2018-06-10T08:26:56+00:00" + "time": "2018-08-23T13:15:44+00:00" }, { "name": "jean85/pretty-package-versions", @@ -675,27 +680,27 @@ }, { "name": "nette/finder", - "version": "v2.4.1", + "version": "v2.4.2", "source": { "type": "git", "url": "https://github.com/nette/finder.git", - "reference": "4d43a66d072c57d585bf08a3ef68d3587f7e9547" + "reference": "ee951a656cb8ac622e5dd33474a01fd2470505a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/finder/zipball/4d43a66d072c57d585bf08a3ef68d3587f7e9547", - "reference": "4d43a66d072c57d585bf08a3ef68d3587f7e9547", + "url": "https://api.github.com/repos/nette/finder/zipball/ee951a656cb8ac622e5dd33474a01fd2470505a0", + "reference": "ee951a656cb8ac622e5dd33474a01fd2470505a0", "shasum": "" }, "require": { - "nette/utils": "^2.4 || ~3.0.0", + "nette/utils": "~2.4", "php": ">=5.6.0" }, "conflict": { "nette/nette": "<2.2" }, "require-dev": { - "nette/tester": "^2.0", + "nette/tester": "~2.0", "tracy/tracy": "^2.3" }, "type": "library", @@ -725,22 +730,28 @@ "homepage": "https://nette.org/contributors" } ], - "description": "Nette Finder: Files Searching", + "description": "🔍 Nette Finder: find files and directories with an intuitive API.", "homepage": "https://nette.org", - "time": "2017-07-10T23:47:08+00:00" + "keywords": [ + "filesystem", + "glob", + "iterator", + "nette" + ], + "time": "2018-06-28T11:49:23+00:00" }, { "name": "nette/robot-loader", - "version": "v3.0.3", + "version": "v3.1.0", "source": { "type": "git", "url": "https://github.com/nette/robot-loader.git", - "reference": "92d4b40b49d5e2d9e37fc736bbcebe6da55fa44a" + "reference": "fc76c70e740b10f091e502b2e393d0be912f38d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/robot-loader/zipball/92d4b40b49d5e2d9e37fc736bbcebe6da55fa44a", - "reference": "92d4b40b49d5e2d9e37fc736bbcebe6da55fa44a", + "url": "https://api.github.com/repos/nette/robot-loader/zipball/fc76c70e740b10f091e502b2e393d0be912f38d4", + "reference": "fc76c70e740b10f091e502b2e393d0be912f38d4", "shasum": "" }, "require": { @@ -759,7 +770,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.1-dev" } }, "autoload": { @@ -792,20 +803,20 @@ "nette", "trait" ], - "time": "2017-09-26T13:42:21+00:00" + "time": "2018-08-13T14:19:06+00:00" }, { "name": "nette/utils", - "version": "v2.5.2", + "version": "v2.5.3", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "183069866dc477fcfbac393ed486aaa6d93d19a5" + "reference": "17b9f76f2abd0c943adfb556e56f2165460b15ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/183069866dc477fcfbac393ed486aaa6d93d19a5", - "reference": "183069866dc477fcfbac393ed486aaa6d93d19a5", + "url": "https://api.github.com/repos/nette/utils/zipball/17b9f76f2abd0c943adfb556e56f2165460b15ce", + "reference": "17b9f76f2abd0c943adfb556e56f2165460b15ce", "shasum": "" }, "require": { @@ -874,7 +885,7 @@ "utility", "validation" ], - "time": "2018-05-02T17:16:08+00:00" + "time": "2018-09-18T10:22:16+00:00" }, { "name": "ocramius/package-versions", @@ -927,33 +938,29 @@ }, { "name": "paragonie/random_compat", - "version": "v2.0.15", + "version": "v9.99.99", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "10bcb46e8f3d365170f6de9d05245aa066b81f09" + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/10bcb46e8f3d365170f6de9d05245aa066b81f09", - "reference": "10bcb46e8f3d365170f6de9d05245aa066b81f09", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", "shasum": "" }, "require": { - "php": ">=5.2.0" + "php": "^7" }, "require-dev": { - "phpunit/phpunit": "4.*|5.*" + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" }, "suggest": { "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." }, "type": "library", - "autoload": { - "files": [ - "lib/random.php" - ] - }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" @@ -972,7 +979,7 @@ "pseudorandom", "random" ], - "time": "2018-06-08T15:26:40+00:00" + "time": "2018-07-02T15:55:56+00:00" }, { "name": "phar-io/manifest", @@ -1506,33 +1513,34 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "0.2", + "version": "0.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "02f909f134fe06f0cd4790d8627ee24efbe84d6a" + "reference": "ed3223362174b8067729930439e139794e9e514a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/02f909f134fe06f0cd4790d8627ee24efbe84d6a", - "reference": "02f909f134fe06f0cd4790d8627ee24efbe84d6a", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/ed3223362174b8067729930439e139794e9e514a", + "reference": "ed3223362174b8067729930439e139794e9e514a", "shasum": "" }, "require": { - "php": "~7.0" + "php": "~7.1" }, "require-dev": { "consistence/coding-standard": "^2.0.0", "jakub-onderka/php-parallel-lint": "^0.9.2", "phing/phing": "^2.16.0", - "phpstan/phpstan": "^0.9", + "phpstan/phpstan": "^0.10@dev", "phpunit/phpunit": "^6.3", - "slevomat/coding-standard": "^3.3.0" + "slevomat/coding-standard": "^3.3.0", + "symfony/process": "^3.4 || ^4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "0.1-dev" + "dev-master": "0.3-dev" } }, "autoload": { @@ -1547,7 +1555,7 @@ "MIT" ], "description": "PHPDoc parser with support for nullable, intersection and generic types", - "time": "2018-01-13T18:19:41+00:00" + "time": "2018-06-20T17:48:01+00:00" }, { "name": "phpstan/phpstan-phpunit", @@ -2810,23 +2818,70 @@ ], "time": "2018-01-24T12:46:19+00:00" }, + { + "name": "slam/php-cs-fixer-extensions", + "version": "v1.17.0", + "source": { + "type": "git", + "url": "https://github.com/Slamdunk/php-cs-fixer-extensions.git", + "reference": "f2d819ff70cc098f68cd2eefd3279c2f54e0ccad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Slamdunk/php-cs-fixer-extensions/zipball/f2d819ff70cc098f68cd2eefd3279c2f54e0ccad", + "reference": "f2d819ff70cc098f68cd2eefd3279c2f54e0ccad", + "shasum": "" + }, + "require": { + "friendsofphp/php-cs-fixer": "^2.13", + "php": "^7.1" + }, + "require-dev": { + "phpstan/phpstan": "^0.10", + "phpstan/phpstan-phpunit": "^0.10", + "phpunit/phpunit": "^7.3", + "roave/security-advisories": "dev-master", + "slam/php-debug-r": "^1.4", + "slam/phpstan-extensions": "^2.0", + "thecodingmachine/phpstan-strict-rules": "^0.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "SlamCsFixer\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filippo Tessarotto", + "email": "zoeslam@gmail.com", + "role": "Developer" + } + ], + "description": "Slam extension of friendsofphp/php-cs-fixer", + "time": "2018-08-30T15:03:51+00:00" + }, { "name": "slevomat/coding-standard", - "version": "4.6.2", + "version": "4.8.5", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "d43b9a627cdcb7ce837a1d85a79b52645cdf44bc" + "reference": "057f3f154cf4888b60eb4cdffadc509a3ae9dccd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/d43b9a627cdcb7ce837a1d85a79b52645cdf44bc", - "reference": "d43b9a627cdcb7ce837a1d85a79b52645cdf44bc", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/057f3f154cf4888b60eb4cdffadc509a3ae9dccd", + "reference": "057f3f154cf4888b60eb4cdffadc509a3ae9dccd", "shasum": "" }, "require": { "php": "^7.1", - "squizlabs/php_codesniffer": "^3.2.3" + "squizlabs/php_codesniffer": "^3.3.0" }, "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", @@ -2834,7 +2889,7 @@ "phpstan/phpstan": "0.9.2", "phpstan/phpstan-phpunit": "0.9.4", "phpstan/phpstan-strict-rules": "0.9", - "phpunit/phpunit": "7.2.4" + "phpunit/phpunit": "7.3.5" }, "type": "phpcodesniffer-standard", "autoload": { @@ -2847,20 +2902,20 @@ "MIT" ], "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "time": "2018-06-12T21:23:15+00:00" + "time": "2018-10-05T12:10:21+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.3.0", + "version": "3.3.2", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "d86873af43b4aa9d1f39a3601cc0cfcf02b25266" + "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/d86873af43b4aa9d1f39a3601cc0cfcf02b25266", - "reference": "d86873af43b4aa9d1f39a3601cc0cfcf02b25266", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/6ad28354c04b364c3c71a34e4a18b629cc3b231e", + "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e", "shasum": "" }, "require": { @@ -2898,20 +2953,20 @@ "phpcs", "standards" ], - "time": "2018-06-06T23:58:19+00:00" + "time": "2018-09-23T23:08:17+00:00" }, { "name": "symfony/cache", - "version": "v4.1.0", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "4986efce97c002e58380e8c0474acbf72eda9339" + "reference": "05ce0ddc8bc1ffe592105398fc2c725cb3080a38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/4986efce97c002e58380e8c0474acbf72eda9339", - "reference": "4986efce97c002e58380e8c0474acbf72eda9339", + "url": "https://api.github.com/repos/symfony/cache/zipball/05ce0ddc8bc1ffe592105398fc2c725cb3080a38", + "reference": "05ce0ddc8bc1ffe592105398fc2c725cb3080a38", "shasum": "" }, "require": { @@ -2967,20 +3022,20 @@ "caching", "psr6" ], - "time": "2018-05-16T14:33:22+00:00" + "time": "2018-09-30T03:38:13+00:00" }, { "name": "symfony/config", - "version": "v4.1.0", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "5ceefc256caecc3e25147c4e5b933de71d0020c4" + "reference": "b3d4d7b567d7a49e6dfafb6d4760abc921177c96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/5ceefc256caecc3e25147c4e5b933de71d0020c4", - "reference": "5ceefc256caecc3e25147c4e5b933de71d0020c4", + "url": "https://api.github.com/repos/symfony/config/zipball/b3d4d7b567d7a49e6dfafb6d4760abc921177c96", + "reference": "b3d4d7b567d7a49e6dfafb6d4760abc921177c96", "shasum": "" }, "require": { @@ -3030,20 +3085,20 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2018-05-16T14:33:22+00:00" + "time": "2018-09-08T13:24:10+00:00" }, { "name": "symfony/console", - "version": "v4.1.0", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "2d5d973bf9933d46802b01010bd25c800c87c242" + "reference": "dc7122fe5f6113cfaba3b3de575d31112c9aa60b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/2d5d973bf9933d46802b01010bd25c800c87c242", - "reference": "2d5d973bf9933d46802b01010bd25c800c87c242", + "url": "https://api.github.com/repos/symfony/console/zipball/dc7122fe5f6113cfaba3b3de575d31112c9aa60b", + "reference": "dc7122fe5f6113cfaba3b3de575d31112c9aa60b", "shasum": "" }, "require": { @@ -3098,20 +3153,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-05-30T07:26:09+00:00" + "time": "2018-10-03T08:15:46+00:00" }, { "name": "symfony/debug", - "version": "v4.1.0", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "449f8b00b28ab6e6912c3e6b920406143b27193b" + "reference": "e3f76ce6198f81994e019bb2b4e533e9de1b9b90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/449f8b00b28ab6e6912c3e6b920406143b27193b", - "reference": "449f8b00b28ab6e6912c3e6b920406143b27193b", + "url": "https://api.github.com/repos/symfony/debug/zipball/e3f76ce6198f81994e019bb2b4e533e9de1b9b90", + "reference": "e3f76ce6198f81994e019bb2b4e533e9de1b9b90", "shasum": "" }, "require": { @@ -3154,20 +3209,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2018-05-16T14:33:22+00:00" + "time": "2018-10-02T16:36:10+00:00" }, { "name": "symfony/dependency-injection", - "version": "v4.1.0", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "f2a3f0dc640a28b8aedd51b47ad6e6c5cebb3c00" + "reference": "f6b9d893ad28aefd8942dc0469c8397e2216fe30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f2a3f0dc640a28b8aedd51b47ad6e6c5cebb3c00", - "reference": "f2a3f0dc640a28b8aedd51b47ad6e6c5cebb3c00", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f6b9d893ad28aefd8942dc0469c8397e2216fe30", + "reference": "f6b9d893ad28aefd8942dc0469c8397e2216fe30", "shasum": "" }, "require": { @@ -3175,7 +3230,7 @@ "psr/container": "^1.0" }, "conflict": { - "symfony/config": "<4.1", + "symfony/config": "<4.1.1", "symfony/finder": "<3.4", "symfony/proxy-manager-bridge": "<3.4", "symfony/yaml": "<3.4" @@ -3225,20 +3280,20 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2018-05-25T14:55:38+00:00" + "time": "2018-10-02T12:40:59+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.1.0", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "2391ed210a239868e7256eb6921b1bd83f3087b5" + "reference": "bfb30c2ad377615a463ebbc875eba64a99f6aa3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/2391ed210a239868e7256eb6921b1bd83f3087b5", - "reference": "2391ed210a239868e7256eb6921b1bd83f3087b5", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/bfb30c2ad377615a463ebbc875eba64a99f6aa3e", + "reference": "bfb30c2ad377615a463ebbc875eba64a99f6aa3e", "shasum": "" }, "require": { @@ -3288,20 +3343,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2018-04-06T07:35:57+00:00" + "time": "2018-07-26T09:10:45+00:00" }, { "name": "symfony/filesystem", - "version": "v4.1.0", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "562bf7005b55fd80d26b582d28e3e10f2dd5ae9c" + "reference": "596d12b40624055c300c8b619755b748ca5cf0b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/562bf7005b55fd80d26b582d28e3e10f2dd5ae9c", - "reference": "562bf7005b55fd80d26b582d28e3e10f2dd5ae9c", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/596d12b40624055c300c8b619755b748ca5cf0b5", + "reference": "596d12b40624055c300c8b619755b748ca5cf0b5", "shasum": "" }, "require": { @@ -3338,20 +3393,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-05-30T07:26:09+00:00" + "time": "2018-10-02T12:40:59+00:00" }, { "name": "symfony/finder", - "version": "v4.1.0", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "087e2ee0d74464a4c6baac4e90417db7477dc238" + "reference": "1f17195b44543017a9c9b2d437c670627e96ad06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/087e2ee0d74464a4c6baac4e90417db7477dc238", - "reference": "087e2ee0d74464a4c6baac4e90417db7477dc238", + "url": "https://api.github.com/repos/symfony/finder/zipball/1f17195b44543017a9c9b2d437c670627e96ad06", + "reference": "1f17195b44543017a9c9b2d437c670627e96ad06", "shasum": "" }, "require": { @@ -3387,20 +3442,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-05-16T14:33:22+00:00" + "time": "2018-10-03T08:47:56+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.1.0", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "a916c88390fb861ee21f12a92b107d51bb68af99" + "reference": "d528136617ff24f530e70df9605acc1b788b08d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/a916c88390fb861ee21f12a92b107d51bb68af99", - "reference": "a916c88390fb861ee21f12a92b107d51bb68af99", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d528136617ff24f530e70df9605acc1b788b08d4", + "reference": "d528136617ff24f530e70df9605acc1b788b08d4", "shasum": "" }, "require": { @@ -3441,20 +3496,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2018-05-25T14:55:38+00:00" + "time": "2018-10-03T08:48:45+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.1.0", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "b5ab9d4cdbfd369083744b6b5dfbf454e31e5f90" + "reference": "f5e7c15a5d010be0e16ce798594c5960451d4220" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b5ab9d4cdbfd369083744b6b5dfbf454e31e5f90", - "reference": "b5ab9d4cdbfd369083744b6b5dfbf454e31e5f90", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f5e7c15a5d010be0e16ce798594c5960451d4220", + "reference": "f5e7c15a5d010be0e16ce798594c5960451d4220", "shasum": "" }, "require": { @@ -3462,13 +3517,13 @@ "psr/log": "~1.0", "symfony/debug": "~3.4|~4.0", "symfony/event-dispatcher": "~4.1", - "symfony/http-foundation": "~4.1", + "symfony/http-foundation": "^4.1.1", "symfony/polyfill-ctype": "~1.8" }, "conflict": { "symfony/config": "<3.4", "symfony/dependency-injection": "<4.1", - "symfony/var-dumper": "<4.1", + "symfony/var-dumper": "<4.1.1", "twig/twig": "<1.34|<2.4,>=2" }, "provide": { @@ -3489,7 +3544,7 @@ "symfony/stopwatch": "~3.4|~4.0", "symfony/templating": "~3.4|~4.0", "symfony/translation": "~3.4|~4.0", - "symfony/var-dumper": "~4.1" + "symfony/var-dumper": "^4.1.1" }, "suggest": { "symfony/browser-kit": "", @@ -3528,20 +3583,20 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2018-05-30T12:52:34+00:00" + "time": "2018-10-03T12:53:38+00:00" }, { "name": "symfony/options-resolver", - "version": "v4.1.0", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "9b9ab6043c57c8c5571bc846e6ebfd27dff3b589" + "reference": "40f0e40d37c1c8a762334618dea597d64bbb75ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/9b9ab6043c57c8c5571bc846e6ebfd27dff3b589", - "reference": "9b9ab6043c57c8c5571bc846e6ebfd27dff3b589", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/40f0e40d37c1c8a762334618dea597d64bbb75ff", + "reference": "40f0e40d37c1c8a762334618dea597d64bbb75ff", "shasum": "" }, "require": { @@ -3582,29 +3637,32 @@ "configuration", "options" ], - "time": "2018-05-30T07:26:09+00:00" + "time": "2018-09-18T12:45:12+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.8.0", + "version": "v1.9.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae" + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/7cc359f1b7b80fc25ed7796be7d96adc9b354bae", - "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", "shasum": "" }, "require": { "php": ">=5.3.3" }, + "suggest": { + "ext-ctype": "For best performance" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -3637,20 +3695,20 @@ "polyfill", "portable" ], - "time": "2018-04-30T19:57:29+00:00" + "time": "2018-08-06T14:22:27+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.8.0", + "version": "v1.9.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "3296adf6a6454a050679cde90f95350ad604b171" + "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/3296adf6a6454a050679cde90f95350ad604b171", - "reference": "3296adf6a6454a050679cde90f95350ad604b171", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d0cd638f4634c16d8df4508e847f14e9e43168b8", + "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8", "shasum": "" }, "require": { @@ -3662,7 +3720,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -3696,30 +3754,30 @@ "portable", "shim" ], - "time": "2018-04-26T10:06:28+00:00" + "time": "2018-08-06T14:22:27+00:00" }, { "name": "symfony/polyfill-php70", - "version": "v1.8.0", + "version": "v1.9.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "77454693d8f10dd23bb24955cffd2d82db1007a6" + "reference": "1e24b0c4a56d55aaf368763a06c6d1c7d3194934" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/77454693d8f10dd23bb24955cffd2d82db1007a6", - "reference": "77454693d8f10dd23bb24955cffd2d82db1007a6", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/1e24b0c4a56d55aaf368763a06c6d1c7d3194934", + "reference": "1e24b0c4a56d55aaf368763a06c6d1c7d3194934", "shasum": "" }, "require": { - "paragonie/random_compat": "~1.0|~2.0", + "paragonie/random_compat": "~1.0|~2.0|~9.99", "php": ">=5.3.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -3755,20 +3813,20 @@ "portable", "shim" ], - "time": "2018-04-26T10:06:28+00:00" + "time": "2018-08-06T14:22:27+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.8.0", + "version": "v1.9.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "a4576e282d782ad82397f3e4ec1df8e0f0cafb46" + "reference": "95c50420b0baed23852452a7f0c7b527303ed5ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/a4576e282d782ad82397f3e4ec1df8e0f0cafb46", - "reference": "a4576e282d782ad82397f3e4ec1df8e0f0cafb46", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/95c50420b0baed23852452a7f0c7b527303ed5ae", + "reference": "95c50420b0baed23852452a7f0c7b527303ed5ae", "shasum": "" }, "require": { @@ -3777,7 +3835,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -3810,20 +3868,20 @@ "portable", "shim" ], - "time": "2018-04-26T10:06:28+00:00" + "time": "2018-08-06T14:22:27+00:00" }, { "name": "symfony/process", - "version": "v4.1.0", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "73445bd33b0d337c060eef9652b94df72b6b3434" + "reference": "ee33c0322a8fee0855afcc11fff81e6b1011b529" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/73445bd33b0d337c060eef9652b94df72b6b3434", - "reference": "73445bd33b0d337c060eef9652b94df72b6b3434", + "url": "https://api.github.com/repos/symfony/process/zipball/ee33c0322a8fee0855afcc11fff81e6b1011b529", + "reference": "ee33c0322a8fee0855afcc11fff81e6b1011b529", "shasum": "" }, "require": { @@ -3859,20 +3917,20 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-05-30T07:26:09+00:00" + "time": "2018-10-02T12:40:59+00:00" }, { "name": "symfony/stopwatch", - "version": "v4.1.0", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "07463bbbbbfe119045a24c4a516f92ebd2752784" + "reference": "5bfc064125b73ff81229e19381ce1c34d3416f4b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/07463bbbbbfe119045a24c4a516f92ebd2752784", - "reference": "07463bbbbbfe119045a24c4a516f92ebd2752784", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5bfc064125b73ff81229e19381ce1c34d3416f4b", + "reference": "5bfc064125b73ff81229e19381ce1c34d3416f4b", "shasum": "" }, "require": { @@ -3908,20 +3966,20 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2018-02-19T16:51:42+00:00" + "time": "2018-10-02T12:40:59+00:00" }, { "name": "symfony/yaml", - "version": "v4.1.0", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "80e4bfa9685fc4a09acc4a857ec16974a9cd944e" + "reference": "367e689b2fdc19965be435337b50bc8adf2746c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/80e4bfa9685fc4a09acc4a857ec16974a9cd944e", - "reference": "80e4bfa9685fc4a09acc4a857ec16974a9cd944e", + "url": "https://api.github.com/repos/symfony/yaml/zipball/367e689b2fdc19965be435337b50bc8adf2746c9", + "reference": "367e689b2fdc19965be435337b50bc8adf2746c9", "shasum": "" }, "require": { @@ -3967,35 +4025,36 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2018-05-30T07:26:09+00:00" + "time": "2018-10-02T16:36:10+00:00" }, { "name": "symplify/better-phpdoc-parser", - "version": "v4.4.2", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/Symplify/BetterPhpDocParser.git", - "reference": "73d5fbe4b5b4546e841b67ecd626d1ac85ca12df" + "reference": "0f276093a76f45bb07d74c3dfa4b3c9fa4a30805" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/BetterPhpDocParser/zipball/73d5fbe4b5b4546e841b67ecd626d1ac85ca12df", - "reference": "73d5fbe4b5b4546e841b67ecd626d1ac85ca12df", + "url": "https://api.github.com/repos/Symplify/BetterPhpDocParser/zipball/0f276093a76f45bb07d74c3dfa4b3c9fa4a30805", + "reference": "0f276093a76f45bb07d74c3dfa4b3c9fa4a30805", "shasum": "" }, "require": { "nette/utils": "^2.5", "php": "^7.1", - "phpstan/phpdoc-parser": "^0.2", - "symplify/package-builder": "^4.4.2" + "phpstan/phpdoc-parser": "^0.2|^0.3", + "symplify/package-builder": "^5.1", + "thecodingmachine/safe": "^0.1.3" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^7.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.5-dev" + "dev-master": "5.2-dev" } }, "autoload": { @@ -4008,40 +4067,42 @@ "MIT" ], "description": "Slim wrapper around phpstan/phpdoc-parser with format preserving printer", - "time": "2018-06-09T23:03:09+00:00" + "time": "2018-10-11T10:23:49+00:00" }, { "name": "symplify/coding-standard", - "version": "v4.4.2", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/Symplify/CodingStandard.git", - "reference": "6ec1f676202863f495c8b08e347e8575c270d3b1" + "reference": "e293e5038ab95f229e48d7396b09a91c6e0a8d90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/CodingStandard/zipball/6ec1f676202863f495c8b08e347e8575c270d3b1", - "reference": "6ec1f676202863f495c8b08e347e8575c270d3b1", + "url": "https://api.github.com/repos/Symplify/CodingStandard/zipball/e293e5038ab95f229e48d7396b09a91c6e0a8d90", + "reference": "e293e5038ab95f229e48d7396b09a91c6e0a8d90", "shasum": "" }, "require": { - "friendsofphp/php-cs-fixer": "^2.12", + "friendsofphp/php-cs-fixer": "^2.13", "nette/finder": "^2.4", "nette/utils": "^2.5", "php": "^7.1", + "slam/php-cs-fixer-extensions": "^1.17", "squizlabs/php_codesniffer": "^3.3", - "symplify/token-runner": "^4.4.2" + "symplify/token-runner": "^5.1", + "thecodingmachine/safe": "^0.1.3" }, "require-dev": { "nette/application": "^2.4", - "phpunit/phpunit": "^7.0", - "symplify/easy-coding-standard-tester": "^4.4.2", - "symplify/package-builder": "^4.4.2" + "phpunit/phpunit": "^7.3", + "symplify/easy-coding-standard-tester": "^5.1", + "symplify/package-builder": "^5.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.5-dev" + "dev-master": "5.2-dev" } }, "autoload": { @@ -4053,55 +4114,55 @@ "license": [ "MIT" ], - "description": "Set of Symplify rules for PHP_CodeSniffer.", - "time": "2018-06-09T22:58:55+00:00" + "description": "Set of Symplify rules for PHP_CodeSniffer and PHP CS Fixer.", + "time": "2018-10-11T10:23:49+00:00" }, { "name": "symplify/easy-coding-standard", - "version": "v4.4.2", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/Symplify/EasyCodingStandard.git", - "reference": "1a18f72e888b2b20e2f8ebad3058c3adc5b71b38" + "reference": "deb7f7f363491ea58ec2b1780d9b625ddeab2214" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/EasyCodingStandard/zipball/1a18f72e888b2b20e2f8ebad3058c3adc5b71b38", - "reference": "1a18f72e888b2b20e2f8ebad3058c3adc5b71b38", + "url": "https://api.github.com/repos/Symplify/EasyCodingStandard/zipball/deb7f7f363491ea58ec2b1780d9b625ddeab2214", + "reference": "deb7f7f363491ea58ec2b1780d9b625ddeab2214", "shasum": "" }, "require": { - "friendsofphp/php-cs-fixer": "^2.12", - "jean85/pretty-package-versions": "^1.1", - "nette/robot-loader": "^3.0.3", + "friendsofphp/php-cs-fixer": "^2.13", + "jean85/pretty-package-versions": "^1.2", + "nette/robot-loader": "^3.1.0", "nette/utils": "^2.5", "ocramius/package-versions": "^1.3", "php": "^7.1", - "slevomat/coding-standard": "^4.5", + "slevomat/coding-standard": "^4.7", "squizlabs/php_codesniffer": "^3.3", - "symfony/cache": "^3.4|^4.0", - "symfony/config": "^3.4|^4.0", - "symfony/console": "^3.4|^4.0", - "symfony/dependency-injection": "^3.4|^4.0", - "symfony/finder": "^3.4|^4.0", - "symfony/http-kernel": "^3.4|^4.0", - "symfony/yaml": "^3.4|^4.0", - "symplify/coding-standard": "^4.4.2", - "symplify/package-builder": "^4.4.2", - "symplify/token-runner": "^4.4.2" + "symfony/cache": "^3.4|^4.1", + "symfony/config": "^3.4|^4.1", + "symfony/console": "^3.4|^4.1", + "symfony/dependency-injection": "^3.4|^4.1", + "symfony/finder": "^3.4|^4.1", + "symfony/http-kernel": "^3.4|^4.1", + "symfony/yaml": "^3.4|^4.1", + "symplify/coding-standard": "^5.1", + "symplify/package-builder": "^5.1", + "symplify/token-runner": "^5.1", + "thecodingmachine/safe": "^0.1.3" }, "require-dev": { - "phpunit/phpunit": "^7.0", - "symplify/easy-coding-standard-tester": "^4.4.2" + "phpunit/phpunit": "^7.3", + "symplify/easy-coding-standard-tester": "^5.1" }, "bin": [ - "bin/ecs", - "bin/ecs-container.php" + "bin/ecs" ], "type": "library", "extra": { "branch-alias": { - "dev-master": "4.5-dev" + "dev-master": "5.2-dev" } }, "autoload": { @@ -4110,8 +4171,7 @@ "Symplify\\EasyCodingStandard\\ChangedFilesDetector\\": "packages/ChangedFilesDetector/src", "Symplify\\EasyCodingStandard\\Configuration\\": "packages/Configuration/src", "Symplify\\EasyCodingStandard\\FixerRunner\\": "packages/FixerRunner/src", - "Symplify\\EasyCodingStandard\\SniffRunner\\": "packages/SniffRunner/src", - "Symplify\\EasyCodingStandard\\Performance\\": "packages/Performance/src" + "Symplify\\EasyCodingStandard\\SniffRunner\\": "packages/SniffRunner/src" } }, "notification-url": "https://packagist.org/downloads/", @@ -4119,40 +4179,42 @@ "MIT" ], "description": "Use Coding Standard with 0-knowledge of PHP-CS-Fixer and PHP_CodeSniffer.", - "time": "2018-06-09T22:55:51+00:00" + "time": "2018-10-11T10:23:49+00:00" }, { "name": "symplify/package-builder", - "version": "v4.4.2", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/Symplify/PackageBuilder.git", - "reference": "1a5e54b3a9aae1652e2c83d675bf0132b5763ef0" + "reference": "095533bf1dddd1ab1a24b453a76f9f222cbf90e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/PackageBuilder/zipball/1a5e54b3a9aae1652e2c83d675bf0132b5763ef0", - "reference": "1a5e54b3a9aae1652e2c83d675bf0132b5763ef0", + "url": "https://api.github.com/repos/Symplify/PackageBuilder/zipball/095533bf1dddd1ab1a24b453a76f9f222cbf90e9", + "reference": "095533bf1dddd1ab1a24b453a76f9f222cbf90e9", "shasum": "" }, "require": { + "nette/finder": "^2.4", "nette/utils": "^2.5", "php": "^7.1", - "symfony/config": "^3.4|^4.0", - "symfony/console": "^3.4|^4.0", - "symfony/debug": "^3.4|^4.0", - "symfony/dependency-injection": "^3.4|^4.0", - "symfony/finder": "^3.4|^4.0", - "symfony/http-kernel": "^3.4|^4.0", - "symfony/yaml": "^3.4|^4.0" + "symfony/config": "^3.4|^4.1", + "symfony/console": "^3.4|^4.1", + "symfony/debug": "^3.4|^4.1", + "symfony/dependency-injection": "^3.4|^4.1", + "symfony/finder": "^3.4|^4.1", + "symfony/http-kernel": "^3.4|^4.1", + "symfony/yaml": "^3.4|^4.1", + "thecodingmachine/safe": "^0.1.3" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^7.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.5-dev" + "dev-master": "5.2-dev" } }, "autoload": { @@ -4165,38 +4227,39 @@ "MIT" ], "description": "Dependency Injection, Console and Kernel toolkit for Symplify packages.", - "time": "2018-06-09T00:55:06+00:00" + "time": "2018-10-09T10:35:39+00:00" }, { "name": "symplify/token-runner", - "version": "v4.4.2", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/Symplify/TokenRunner.git", - "reference": "dadab58102d4ac853c1fbe6b49d51e1c0b7540fc" + "reference": "995a3127fb98791475f77882a0b3e028f88a11e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/TokenRunner/zipball/dadab58102d4ac853c1fbe6b49d51e1c0b7540fc", - "reference": "dadab58102d4ac853c1fbe6b49d51e1c0b7540fc", + "url": "https://api.github.com/repos/Symplify/TokenRunner/zipball/995a3127fb98791475f77882a0b3e028f88a11e9", + "reference": "995a3127fb98791475f77882a0b3e028f88a11e9", "shasum": "" }, "require": { - "friendsofphp/php-cs-fixer": "^2.12", + "friendsofphp/php-cs-fixer": "^2.13", "nette/finder": "^2.4", "nette/utils": "^2.5", "php": "^7.1", "squizlabs/php_codesniffer": "^3.3", - "symplify/better-phpdoc-parser": "^4.4.2", - "symplify/package-builder": "^4.4.2" + "symplify/better-phpdoc-parser": "^5.1", + "symplify/package-builder": "^5.1", + "thecodingmachine/safe": "^0.1.3" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^7.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.5-dev" + "dev-master": "5.2-dev" } }, "autoload": { @@ -4209,7 +4272,137 @@ "MIT" ], "description": "Set of utils for PHP_CodeSniffer and PHP CS Fixer.", - "time": "2018-06-09T23:04:45+00:00" + "time": "2018-10-11T10:23:49+00:00" + }, + { + "name": "thecodingmachine/safe", + "version": "v0.1.5", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/safe.git", + "reference": "56fcae888155f6ae0070545c5f80505c511598d5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/56fcae888155f6ae0070545c5f80505c511598d5", + "reference": "56fcae888155f6ae0070545c5f80505c511598d5", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpstan/phpstan": "^0.10.3", + "squizlabs/php_codesniffer": "^3.2", + "thecodingmachine/phpstan-strict-rules": "^0.10.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.1-dev" + } + }, + "autoload": { + "psr-4": { + "Safe\\": [ + "lib/", + "generated/" + ] + }, + "files": [ + "generated/apache.php", + "generated/apc.php", + "generated/apcu.php", + "generated/array.php", + "generated/bzip2.php", + "generated/classobj.php", + "generated/com.php", + "generated/cubrid.php", + "generated/curl.php", + "generated/datetime.php", + "generated/dir.php", + "generated/eio.php", + "generated/errorfunc.php", + "generated/exec.php", + "generated/fileinfo.php", + "generated/filesystem.php", + "generated/filter.php", + "generated/fpm.php", + "generated/ftp.php", + "generated/funchand.php", + "generated/gmp.php", + "generated/gnupg.php", + "generated/hash.php", + "generated/ibase.php", + "generated/ibmDb2.php", + "generated/iconv.php", + "generated/image.php", + "generated/imap.php", + "generated/info.php", + "generated/ingres-ii.php", + "generated/inotify.php", + "generated/json.php", + "generated/ldap.php", + "generated/libevent.php", + "generated/libxml.php", + "generated/lzf.php", + "generated/mailparse.php", + "generated/mbstring.php", + "generated/misc.php", + "generated/msql.php", + "generated/mssql.php", + "generated/mysql.php", + "generated/mysqlndMs.php", + "generated/mysqlndQc.php", + "generated/network.php", + "generated/oci8.php", + "generated/opcache.php", + "generated/openssl.php", + "generated/outcontrol.php", + "generated/password.php", + "generated/pcntl.php", + "generated/pcre.php", + "generated/pdf.php", + "generated/pgsql.php", + "generated/posix.php", + "generated/ps.php", + "generated/pspell.php", + "generated/readline.php", + "generated/rrd.php", + "generated/sem.php", + "generated/session.php", + "generated/shmop.php", + "generated/simplexml.php", + "generated/sockets.php", + "generated/sodium.php", + "generated/solr.php", + "generated/spl.php", + "generated/sqlsrv.php", + "generated/ssh2.php", + "generated/stats.php", + "generated/stream.php", + "generated/strings.php", + "generated/swoole.php", + "generated/uodbc.php", + "generated/uopz.php", + "generated/url.php", + "generated/var.php", + "generated/xdiff.php", + "generated/xml.php", + "generated/xmlrpc.php", + "generated/yaml.php", + "generated/yaz.php", + "generated/zip.php", + "generated/zlib.php", + "lib/special_cases.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHP core functions that throw exceptions instead of returning FALSE on error", + "time": "2018-09-24T20:24:12+00:00" }, { "name": "theseer/tokenizer", diff --git a/src/Association/Apriori.php b/src/Association/Apriori.php index abbdf470..2d09dd7b 100644 --- a/src/Association/Apriori.php +++ b/src/Association/Apriori.php @@ -9,7 +9,8 @@ class Apriori implements Associator { - use Trainable, Predictable; + use Trainable; + use Predictable; public const ARRAY_KEY_ANTECEDENT = 'antecedent'; diff --git a/src/Classification/DecisionTree.php b/src/Classification/DecisionTree.php index 0f428c59..13a79754 100644 --- a/src/Classification/DecisionTree.php +++ b/src/Classification/DecisionTree.php @@ -12,7 +12,8 @@ class DecisionTree implements Classifier { - use Trainable, Predictable; + use Trainable; + use Predictable; public const CONTINUOUS = 1; @@ -31,7 +32,7 @@ class DecisionTree implements Classifier /** * @var DecisionTreeLeaf */ - protected $tree = null; + protected $tree; /** * @var int @@ -219,10 +220,9 @@ public function getFeatureImportances(): array // Normalize & sort the importances $total = array_sum($this->featureImportances); if ($total > 0) { - foreach ($this->featureImportances as &$importance) { + array_walk($this->featureImportances, function (&$importance) use ($total): void { $importance /= $total; - } - + }); arsort($this->featureImportances); } diff --git a/src/Classification/Ensemble/AdaBoost.php b/src/Classification/Ensemble/AdaBoost.php index c7b5e75e..3859295a 100644 --- a/src/Classification/Ensemble/AdaBoost.php +++ b/src/Classification/Ensemble/AdaBoost.php @@ -16,7 +16,8 @@ class AdaBoost implements Classifier { - use Predictable, Trainable; + use Predictable; + use Trainable; /** * Actual labels given in the targets array diff --git a/src/Classification/Ensemble/Bagging.php b/src/Classification/Ensemble/Bagging.php index 02e958ba..b73a1d39 100644 --- a/src/Classification/Ensemble/Bagging.php +++ b/src/Classification/Ensemble/Bagging.php @@ -13,7 +13,8 @@ class Bagging implements Classifier { - use Trainable, Predictable; + use Trainable; + use Predictable; /** * @var int diff --git a/src/Classification/Ensemble/RandomForest.php b/src/Classification/Ensemble/RandomForest.php index d871fe2b..c0f0dd87 100644 --- a/src/Classification/Ensemble/RandomForest.php +++ b/src/Classification/Ensemble/RandomForest.php @@ -16,9 +16,9 @@ class RandomForest extends Bagging protected $featureSubsetRatio = 'log'; /** - * @var array + * @var array|null */ - protected $columnNames = null; + protected $columnNames; /** * Initializes RandomForest with the given number of trees. More trees @@ -53,7 +53,7 @@ public function setFeatureSubsetRatio($ratio): self throw new InvalidArgumentException('When a float is given, feature subset ratio should be between 0.1 and 1.0'); } - if (is_string($ratio) && $ratio != 'sqrt' && $ratio != 'log') { + if (is_string($ratio) && $ratio !== 'sqrt' && $ratio !== 'log') { throw new InvalidArgumentException("When a string is given, feature subset ratio can only be 'sqrt' or 'log'"); } @@ -69,7 +69,7 @@ public function setFeatureSubsetRatio($ratio): self */ public function setClassifer(string $classifier, array $classifierOptions = []) { - if ($classifier != DecisionTree::class) { + if ($classifier !== DecisionTree::class) { throw new InvalidArgumentException('RandomForest can only use DecisionTree as base classifier'); } @@ -100,10 +100,9 @@ public function getFeatureImportances(): array // Normalize & sort the importance values $total = array_sum($sum); - foreach ($sum as &$importance) { + array_walk($sum, function (&$importance) use ($total): void { $importance /= $total; - } - + }); arsort($sum); return $sum; @@ -131,7 +130,7 @@ protected function initSingleClassifier(Classifier $classifier): Classifier { if (is_float($this->featureSubsetRatio)) { $featureCount = (int) ($this->featureSubsetRatio * $this->featureCount); - } elseif ($this->featureSubsetRatio == 'sqrt') { + } elseif ($this->featureSubsetRatio === 'sqrt') { $featureCount = (int) sqrt($this->featureCount) + 1; } else { $featureCount = (int) log($this->featureCount, 2) + 1; diff --git a/src/Classification/KNearestNeighbors.php b/src/Classification/KNearestNeighbors.php index 238fc1d9..cac54163 100644 --- a/src/Classification/KNearestNeighbors.php +++ b/src/Classification/KNearestNeighbors.php @@ -11,7 +11,8 @@ class KNearestNeighbors implements Classifier { - use Trainable, Predictable; + use Trainable; + use Predictable; /** * @var int @@ -47,7 +48,7 @@ protected function predictSample(array $sample) $predictions = array_combine(array_values($this->targets), array_fill(0, count($this->targets), 0)); - foreach ($distances as $index => $distance) { + foreach (array_keys($distances) as $index) { ++$predictions[$this->targets[$index]]; } diff --git a/src/Classification/Linear/DecisionStump.php b/src/Classification/Linear/DecisionStump.php index ef6a4589..c83d339e 100644 --- a/src/Classification/Linear/DecisionStump.php +++ b/src/Classification/Linear/DecisionStump.php @@ -13,7 +13,8 @@ class DecisionStump extends WeightedClassifier { - use Predictable, OneVsRest; + use Predictable; + use OneVsRest; public const AUTO_SELECT = -1; diff --git a/src/Classification/Linear/LogisticRegression.php b/src/Classification/Linear/LogisticRegression.php index 0414d594..8ab69394 100644 --- a/src/Classification/Linear/LogisticRegression.php +++ b/src/Classification/Linear/LogisticRegression.php @@ -87,10 +87,8 @@ public function __construct( ); } - if ($penalty != '' && strtoupper($penalty) !== 'L2') { - throw new InvalidArgumentException( - "Logistic regression supports only 'L2' regularization" - ); + if ($penalty !== '' && strtoupper($penalty) !== 'L2') { + throw new InvalidArgumentException('Logistic regression supports only \'L2\' regularization'); } $this->learningRate = 0.001; @@ -174,7 +172,7 @@ protected function runConjugateGradient(array $samples, array $targets, Closure protected function getCostFunction(): Closure { $penalty = 0; - if ($this->penalty == 'L2') { + if ($this->penalty === 'L2') { $penalty = $this->lambda; } @@ -190,7 +188,7 @@ protected function getCostFunction(): Closure * The gradient of the cost function to be used with gradient descent: * ∇J(x) = -(y - h(x)) = (h(x) - y) */ - $callback = function ($weights, $sample, $y) use ($penalty) { + return function ($weights, $sample, $y) use ($penalty) { $this->weights = $weights; $hX = $this->output($sample); @@ -211,9 +209,6 @@ protected function getCostFunction(): Closure return [$error, $gradient, $penalty]; }; - - return $callback; - case 'sse': /* * Sum of squared errors or least squared errors cost function: @@ -225,7 +220,7 @@ protected function getCostFunction(): Closure * The gradient of the cost function: * ∇J(x) = -(h(x) - y) . h(x) . (1 - h(x)) */ - $callback = function ($weights, $sample, $y) use ($penalty) { + return function ($weights, $sample, $y) use ($penalty) { $this->weights = $weights; $hX = $this->output($sample); @@ -236,9 +231,6 @@ protected function getCostFunction(): Closure return [$error, $gradient, $penalty]; }; - - return $callback; - default: // Not reached throw new Exception(sprintf('Logistic regression has invalid cost function: %s.', $this->costFunction)); diff --git a/src/Classification/Linear/Perceptron.php b/src/Classification/Linear/Perceptron.php index ea49eeb7..adc6b36a 100644 --- a/src/Classification/Linear/Perceptron.php +++ b/src/Classification/Linear/Perceptron.php @@ -17,7 +17,8 @@ class Perceptron implements Classifier, IncrementalEstimator { - use Predictable, OneVsRest; + use Predictable; + use OneVsRest; /** * @var Optimizer|GD|StochasticGD|null diff --git a/src/Classification/NaiveBayes.php b/src/Classification/NaiveBayes.php index 90425473..f14ada73 100644 --- a/src/Classification/NaiveBayes.php +++ b/src/Classification/NaiveBayes.php @@ -11,7 +11,8 @@ class NaiveBayes implements Classifier { - use Trainable, Predictable; + use Trainable; + use Predictable; public const CONTINUOS = 1; diff --git a/src/Dataset/ArrayDataset.php b/src/Dataset/ArrayDataset.php index e0e78224..095c5976 100644 --- a/src/Dataset/ArrayDataset.php +++ b/src/Dataset/ArrayDataset.php @@ -23,7 +23,7 @@ class ArrayDataset implements Dataset */ public function __construct(array $samples, array $targets) { - if (count($samples) != count($targets)) { + if (count($samples) !== count($targets)) { throw new InvalidArgumentException('Size of given arrays does not match'); } diff --git a/src/Dataset/SvmDataset.php b/src/Dataset/SvmDataset.php index 824fcff4..e5f2a86e 100644 --- a/src/Dataset/SvmDataset.php +++ b/src/Dataset/SvmDataset.php @@ -94,7 +94,7 @@ private static function parseTargetColumn(string $column): float private static function parseFeatureColumn(string $column): array { $feature = explode(':', $column, 2); - if (count($feature) != 2) { + if (count($feature) !== 2) { throw new DatasetException(sprintf('Invalid value "%s".', $column)); } diff --git a/src/DimensionReduction/PCA.php b/src/DimensionReduction/PCA.php index a3d8a4d0..fa651da9 100644 --- a/src/DimensionReduction/PCA.php +++ b/src/DimensionReduction/PCA.php @@ -120,7 +120,7 @@ protected function normalize(array $data, int $n): array } // Normalize data - foreach ($data as $i => $row) { + foreach (array_keys($data) as $i) { for ($k = 0; $k < $n; ++$k) { $data[$i][$k] -= $this->means[$k]; } diff --git a/src/FeatureExtraction/TokenCountVectorizer.php b/src/FeatureExtraction/TokenCountVectorizer.php index c73836c7..a1e38f44 100644 --- a/src/FeatureExtraction/TokenCountVectorizer.php +++ b/src/FeatureExtraction/TokenCountVectorizer.php @@ -48,9 +48,9 @@ public function fit(array $samples, ?array $targets = null): void public function transform(array &$samples): void { - foreach ($samples as &$sample) { + array_walk($samples, function (string &$sample): void { $this->transformSample($sample); - } + }); $this->checkDocumentFrequency($samples); } @@ -62,7 +62,7 @@ public function getVocabulary(): array private function buildVocabulary(array &$samples): void { - foreach ($samples as $index => $sample) { + foreach ($samples as $sample) { $tokens = $this->tokenizer->tokenize($sample); foreach ($tokens as $token) { $this->addTokenToVocabulary($token); diff --git a/src/FeatureSelection/ScoringFunction/UnivariateLinearRegression.php b/src/FeatureSelection/ScoringFunction/UnivariateLinearRegression.php index 968c4741..18d0ba9b 100644 --- a/src/FeatureSelection/ScoringFunction/UnivariateLinearRegression.php +++ b/src/FeatureSelection/ScoringFunction/UnivariateLinearRegression.php @@ -43,7 +43,7 @@ public function score(array $samples, array $targets): array } $correlations = []; - foreach ($samples[0] as $index => $feature) { + foreach (array_keys($samples[0]) as $index) { $featureColumn = array_column($samples, $index); $correlations[$index] = (Matrix::dot($targets, $featureColumn)[0] / (new Matrix($featureColumn, false))->transpose()->frobeniusNorm()) @@ -57,15 +57,15 @@ public function score(array $samples, array $targets): array }, $correlations); } - private function centerTargets(&$targets): void + private function centerTargets(array &$targets): void { $mean = Mean::arithmetic($targets); - foreach ($targets as &$target) { + array_walk($targets, function (&$target) use ($mean): void { $target -= $mean; - } + }); } - private function centerSamples(&$samples): void + private function centerSamples(array &$samples): void { $means = []; foreach ($samples[0] as $index => $feature) { diff --git a/src/Math/LinearAlgebra/EigenvalueDecomposition.php b/src/Math/LinearAlgebra/EigenvalueDecomposition.php index 56e5b8ae..3d7484dc 100644 --- a/src/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -179,7 +179,7 @@ public function getDiagonalEigenvalues(): array continue; } - $o = ($this->e[$i] > 0) ? $i + 1 : $i - 1; + $o = $this->e[$i] > 0 ? $i + 1 : $i - 1; $D[$i][$o] = $this->e[$i]; } @@ -222,7 +222,7 @@ private function tred2(): void } $this->e[$i] = $scale * $g; - $h = $h - $f * $g; + $h -= $f * $g; $this->d[$i_] = $f - $g; for ($j = 0; $j < $i; ++$j) { @@ -395,7 +395,7 @@ private function tql2(): void } while (abs($this->e[$l]) > $eps * $tst1); } - $this->d[$l] = $this->d[$l] + $f; + $this->d[$l] += $f; $this->e[$l] = 0.0; } @@ -439,7 +439,7 @@ private function orthes(): void // Scale column. $scale = 0.0; for ($i = $m; $i <= $high; ++$i) { - $scale = $scale + abs($this->H[$i][$m - 1]); + $scale += abs($this->H[$i][$m - 1]); } if ($scale != 0.0) { @@ -477,7 +477,7 @@ private function orthes(): void $f += $this->ort[$j] * $this->H[$i][$j]; } - $f = $f / $h; + $f /= $h; for ($j = $m; $j <= $high; ++$j) { $this->H[$i][$j] -= $f * $this->ort[$j]; } @@ -568,7 +568,7 @@ private function hqr2(): void } for ($j = max($i - 1, 0); $j < $nn; ++$j) { - $norm = $norm + abs($this->H[$i][$j]); + $norm += abs($this->H[$i][$j]); } } @@ -593,7 +593,7 @@ private function hqr2(): void // Check for convergence // One root found if ($l == $n) { - $this->H[$n][$n] = $this->H[$n][$n] + $exshift; + $this->H[$n][$n] += $exshift; $this->d[$n] = $this->H[$n][$n]; $this->e[$n] = 0.0; --$n; @@ -604,8 +604,8 @@ private function hqr2(): void $p = ($this->H[$n - 1][$n - 1] - $this->H[$n][$n]) / 2.0; $q = $p * $p + $w; $z = sqrt(abs($q)); - $this->H[$n][$n] = $this->H[$n][$n] + $exshift; - $this->H[$n - 1][$n - 1] = $this->H[$n - 1][$n - 1] + $exshift; + $this->H[$n][$n] += $exshift; + $this->H[$n - 1][$n - 1] += $exshift; $x = $this->H[$n][$n]; // Real pair if ($q >= 0) { @@ -628,8 +628,8 @@ private function hqr2(): void $p = $x / $s; $q = $z / $s; $r = sqrt($p * $p + $q * $q); - $p = $p / $r; - $q = $q / $r; + $p /= $r; + $q /= $r; // Row modification for ($j = $n - 1; $j < $nn; ++$j) { $z = $this->H[$n - 1][$j]; @@ -659,7 +659,7 @@ private function hqr2(): void $this->e[$n] = -$z; } - $n = $n - 2; + $n -= 2; $iter = 0; // No convergence yet } else { @@ -687,7 +687,7 @@ private function hqr2(): void // MATLAB's new ad hoc shift if ($iter == 30) { $s = ($y - $x) / 2.0; - $s = $s * $s + $w; + $s *= $s + $w; if ($s > 0) { $s = sqrt($s); if ($y < $x) { @@ -705,7 +705,7 @@ private function hqr2(): void } // Could check iteration count here. - $iter = $iter + 1; + ++$iter; // Look for two consecutive small sub-diagonal elements $m = $n - 2; while ($m >= $l) { @@ -716,9 +716,9 @@ private function hqr2(): void $q = $this->H[$m + 1][$m + 1] - $z - $r - $s; $r = $this->H[$m + 2][$m + 1]; $s = abs($p) + abs($q) + abs($r); - $p = $p / $s; - $q = $q / $s; - $r = $r / $s; + $p /= $s; + $q /= $s; + $r /= $s; if ($m == $l) { break; } @@ -747,9 +747,9 @@ private function hqr2(): void $r = ($notlast ? $this->H[$k + 2][$k - 1] : 0.0); $x = abs($p) + abs($q) + abs($r); if ($x != 0.0) { - $p = $p / $x; - $q = $q / $x; - $r = $r / $x; + $p /= $x; + $q /= $x; + $r /= $x; } } @@ -769,46 +769,46 @@ private function hqr2(): void $this->H[$k][$k - 1] = -$this->H[$k][$k - 1]; } - $p = $p + $s; + $p += $s; $x = $p / $s; $y = $q / $s; $z = $r / $s; - $q = $q / $p; - $r = $r / $p; + $q /= $p; + $r /= $p; // Row modification for ($j = $k; $j < $nn; ++$j) { $p = $this->H[$k][$j] + $q * $this->H[$k + 1][$j]; if ($notlast) { - $p = $p + $r * $this->H[$k + 2][$j]; - $this->H[$k + 2][$j] = $this->H[$k + 2][$j] - $p * $z; + $p += $r * $this->H[$k + 2][$j]; + $this->H[$k + 2][$j] -= $p * $z; } - $this->H[$k][$j] = $this->H[$k][$j] - $p * $x; - $this->H[$k + 1][$j] = $this->H[$k + 1][$j] - $p * $y; + $this->H[$k][$j] -= $p * $x; + $this->H[$k + 1][$j] -= $p * $y; } // Column modification for ($i = 0; $i <= min($n, $k + 3); ++$i) { $p = $x * $this->H[$i][$k] + $y * $this->H[$i][$k + 1]; if ($notlast) { - $p = $p + $z * $this->H[$i][$k + 2]; - $this->H[$i][$k + 2] = $this->H[$i][$k + 2] - $p * $r; + $p += $z * $this->H[$i][$k + 2]; + $this->H[$i][$k + 2] -= $p * $r; } - $this->H[$i][$k] = $this->H[$i][$k] - $p; - $this->H[$i][$k + 1] = $this->H[$i][$k + 1] - $p * $q; + $this->H[$i][$k] -= $p; + $this->H[$i][$k + 1] -= $p * $q; } // Accumulate transformations for ($i = $low; $i <= $high; ++$i) { $p = $x * $this->V[$i][$k] + $y * $this->V[$i][$k + 1]; if ($notlast) { - $p = $p + $z * $this->V[$i][$k + 2]; - $this->V[$i][$k + 2] = $this->V[$i][$k + 2] - $p * $r; + $p += $z * $this->V[$i][$k + 2]; + $this->V[$i][$k + 2] -= $p * $r; } - $this->V[$i][$k] = $this->V[$i][$k] - $p; - $this->V[$i][$k + 1] = $this->V[$i][$k + 1] - $p * $q; + $this->V[$i][$k] -= $p; + $this->V[$i][$k + 1] -= $p * $q; } } // ($s != 0) } // k loop @@ -831,7 +831,7 @@ private function hqr2(): void $w = $this->H[$i][$i] - $p; $r = 0.0; for ($j = $l; $j <= $n; ++$j) { - $r = $r + $this->H[$i][$j] * $this->H[$j][$n]; + $r += $this->H[$i][$j] * $this->H[$j][$n]; } if ($this->e[$i] < 0.0) { @@ -864,7 +864,7 @@ private function hqr2(): void $t = abs($this->H[$i][$n]); if (($eps * $t) * $t > 1) { for ($j = $i; $j <= $n; ++$j) { - $this->H[$j][$n] = $this->H[$j][$n] / $t; + $this->H[$j][$n] /= $t; } } } @@ -890,8 +890,8 @@ private function hqr2(): void $ra = 0.0; $sa = 0.0; for ($j = $l; $j <= $n; ++$j) { - $ra = $ra + $this->H[$i][$j] * $this->H[$j][$n - 1]; - $sa = $sa + $this->H[$i][$j] * $this->H[$j][$n]; + $ra += $this->H[$i][$j] * $this->H[$j][$n - 1]; + $sa += $this->H[$i][$j] * $this->H[$j][$n]; } $w = $this->H[$i][$i] - $p; @@ -932,8 +932,8 @@ private function hqr2(): void $t = max(abs($this->H[$i][$n - 1]), abs($this->H[$i][$n])); if (($eps * $t) * $t > 1) { for ($j = $i; $j <= $n; ++$j) { - $this->H[$j][$n - 1] = $this->H[$j][$n - 1] / $t; - $this->H[$j][$n] = $this->H[$j][$n] / $t; + $this->H[$j][$n - 1] /= $t; + $this->H[$j][$n] /= $t; } } } // end else @@ -955,7 +955,7 @@ private function hqr2(): void for ($i = $low; $i <= $high; ++$i) { $z = 0.0; for ($k = $low; $k <= min($j, $high); ++$k) { - $z = $z + $this->V[$i][$k] * $this->H[$k][$j]; + $z += $this->V[$i][$k] * $this->H[$k][$j]; } $this->V[$i][$j] = $z; diff --git a/src/Math/LinearAlgebra/LUDecomposition.php b/src/Math/LinearAlgebra/LUDecomposition.php index 44e8a268..f9e7300a 100644 --- a/src/Math/LinearAlgebra/LUDecomposition.php +++ b/src/Math/LinearAlgebra/LUDecomposition.php @@ -133,7 +133,7 @@ public function __construct(Matrix $A) $k = $this->piv[$p]; $this->piv[$p] = $this->piv[$j]; $this->piv[$j] = $k; - $this->pivsign = $this->pivsign * -1; + $this->pivsign *= -1; } // Compute multipliers. diff --git a/src/Math/Matrix.php b/src/Math/Matrix.php index ae89d203..840d7469 100644 --- a/src/Math/Matrix.php +++ b/src/Math/Matrix.php @@ -261,7 +261,7 @@ public function frobeniusNorm(): float $squareSum = 0; for ($i = 0; $i < $this->rows; ++$i) { for ($j = 0; $j < $this->columns; ++$j) { - $squareSum += ($this->matrix[$i][$j]) ** 2; + $squareSum += $this->matrix[$i][$j] ** 2; } } diff --git a/src/Math/Statistic/ANOVA.php b/src/Math/Statistic/ANOVA.php index 2732ba79..0118347e 100644 --- a/src/Math/Statistic/ANOVA.php +++ b/src/Math/Statistic/ANOVA.php @@ -102,7 +102,7 @@ private static function squaresSum(array $sums): array { foreach ($sums as &$row) { foreach ($row as &$sum) { - $sum = $sum ** 2; + $sum **= 2; } } diff --git a/src/Preprocessing/Normalizer.php b/src/Preprocessing/Normalizer.php index 95927ea6..fb14adaf 100644 --- a/src/Preprocessing/Normalizer.php +++ b/src/Preprocessing/Normalizer.php @@ -54,7 +54,7 @@ public function fit(array $samples, ?array $targets = null): void return; } - if ($this->norm == self::NORM_STD) { + if ($this->norm === self::NORM_STD) { $features = range(0, count($samples[0]) - 1); foreach ($features as $i) { $values = array_column($samples, $i); @@ -93,9 +93,9 @@ private function normalizeL1(array &$sample): void $count = count($sample); $sample = array_fill(0, $count, 1.0 / $count); } else { - foreach ($sample as &$feature) { + array_walk($sample, function (&$feature) use ($norm1): void { $feature /= $norm1; - } + }); } } @@ -111,15 +111,15 @@ private function normalizeL2(array &$sample): void if ($norm2 == 0) { $sample = array_fill(0, count($sample), 1); } else { - foreach ($sample as &$feature) { + array_walk($sample, function (&$feature) use ($norm2): void { $feature /= $norm2; - } + }); } } private function normalizeSTD(array &$sample): void { - foreach ($sample as $i => $val) { + foreach (array_keys($sample) as $i) { if ($this->std[$i] != 0) { $sample[$i] = ($sample[$i] - $this->mean[$i]) / $this->std[$i]; } else { diff --git a/tests/Classification/KNearestNeighborsTest.php b/tests/Classification/KNearestNeighborsTest.php index d96152a4..110d49d8 100644 --- a/tests/Classification/KNearestNeighborsTest.php +++ b/tests/Classification/KNearestNeighborsTest.php @@ -66,14 +66,13 @@ public function testSaveAndRestore(): void $trainLabels = ['a', 'a', 'a', 'b', 'b', 'b']; $testSamples = [[3, 2], [5, 1], [4, 3], [4, -5], [2, 3], [1, 2], [1, 5], [3, 10]]; - $testLabels = ['b', 'b', 'b', 'b', 'a', 'a', 'a', 'a']; // Using non-default constructor parameters to check that their values are restored. $classifier = new KNearestNeighbors(3, new Chebyshev()); $classifier->train($trainSamples, $trainLabels); $predicted = $classifier->predict($testSamples); - $filename = 'knearest-neighbors-test-'.random_int(100, 999).'-'.uniqid(); + $filename = 'knearest-neighbors-test-'.random_int(100, 999).'-'.uniqid('', false); $filepath = tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); diff --git a/tests/Classification/Linear/AdalineTest.php b/tests/Classification/Linear/AdalineTest.php index 62224e87..08ad78aa 100644 --- a/tests/Classification/Linear/AdalineTest.php +++ b/tests/Classification/Linear/AdalineTest.php @@ -14,7 +14,9 @@ class AdalineTest extends TestCase public function testAdalineThrowWhenInvalidTrainingType(): void { $this->expectException(InvalidArgumentException::class); - $classifier = new Adaline( + $this->expectExceptionMessage('Adaline can only be trained with batch and online/stochastic gradient descent algorithm'); + + new Adaline( 0.001, 1000, true, @@ -86,7 +88,7 @@ public function testSaveAndRestore(): void $testSamples = [[0, 1], [1, 1], [0.2, 0.1]]; $predicted = $classifier->predict($testSamples); - $filename = 'adaline-test-'.random_int(100, 999).'-'.uniqid(); + $filename = 'adaline-test-'.random_int(100, 999).'-'.uniqid('', false); $filepath = tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); diff --git a/tests/Classification/Linear/LogisticRegressionTest.php b/tests/Classification/Linear/LogisticRegressionTest.php index 37b51828..075e1ead 100644 --- a/tests/Classification/Linear/LogisticRegressionTest.php +++ b/tests/Classification/Linear/LogisticRegressionTest.php @@ -15,8 +15,11 @@ class LogisticRegressionTest extends TestCase public function testConstructorThrowWhenInvalidTrainingType(): void { $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Logistic regression can only be trained with '. + 'batch (gradient descent), online (stochastic gradient descent) '. + 'or conjugate batch (conjugate gradients) algorithms'); - $classifier = new LogisticRegression( + new LogisticRegression( 500, true, -1, @@ -28,8 +31,10 @@ public function testConstructorThrowWhenInvalidTrainingType(): void public function testConstructorThrowWhenInvalidCost(): void { $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("Logistic regression cost function can be one of the following: \n". + "'log' for log-likelihood and 'sse' for sum of squared errors"); - $classifier = new LogisticRegression( + new LogisticRegression( 500, true, LogisticRegression::CONJUGATE_GRAD_TRAINING, @@ -41,8 +46,9 @@ public function testConstructorThrowWhenInvalidCost(): void public function testConstructorThrowWhenInvalidPenalty(): void { $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Logistic regression supports only \'L2\' regularization'); - $classifier = new LogisticRegression( + new LogisticRegression( 500, true, LogisticRegression::CONJUGATE_GRAD_TRAINING, diff --git a/tests/Classification/Linear/PerceptronTest.php b/tests/Classification/Linear/PerceptronTest.php index 731eac13..a3958b80 100644 --- a/tests/Classification/Linear/PerceptronTest.php +++ b/tests/Classification/Linear/PerceptronTest.php @@ -14,13 +14,15 @@ class PerceptronTest extends TestCase public function testPerceptronThrowWhenLearningRateOutOfRange(): void { $this->expectException(InvalidArgumentException::class); - $classifier = new Perceptron(0, 5000); + $this->expectExceptionMessage('Learning rate should be a float value between 0.0(exclusive) and 1.0(inclusive)'); + new Perceptron(0, 5000); } public function testPerceptronThrowWhenMaxIterationsOutOfRange(): void { $this->expectException(InvalidArgumentException::class); - $classifier = new Perceptron(0.001, 0); + $this->expectExceptionMessage('Maximum number of iterations must be an integer greater than 0'); + new Perceptron(0.001, 0); } public function testPredictSingleSample(): void @@ -90,7 +92,7 @@ public function testSaveAndRestore(): void $testSamples = [[0, 1], [1, 1], [0.2, 0.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); diff --git a/tests/Classification/NaiveBayesTest.php b/tests/Classification/NaiveBayesTest.php index 7db8645d..434c9205 100644 --- a/tests/Classification/NaiveBayesTest.php +++ b/tests/Classification/NaiveBayesTest.php @@ -53,13 +53,12 @@ public function testSaveAndRestore(): void $trainLabels = ['a', 'b', 'c']; $testSamples = [[3, 1, 1], [5, 1, 1], [4, 3, 8]]; - $testLabels = ['a', 'a', 'c']; $classifier = new NaiveBayes(); $classifier->train($trainSamples, $trainLabels); $predicted = $classifier->predict($testSamples); - $filename = 'naive-bayes-test-'.random_int(100, 999).'-'.uniqid(); + $filename = 'naive-bayes-test-'.random_int(100, 999).'-'.uniqid('', false); $filepath = tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); @@ -112,13 +111,12 @@ public function testSaveAndRestoreNumericLabels(): void $trainLabels = ['1996', '1997', '1998']; $testSamples = [[3, 1, 1], [5, 1, 1], [4, 3, 8]]; - $testLabels = ['1996', '1996', '1998']; $classifier = new NaiveBayes(); $classifier->train($trainSamples, $trainLabels); $predicted = $classifier->predict($testSamples); - $filename = 'naive-bayes-test-'.random_int(100, 999).'-'.uniqid(); + $filename = 'naive-bayes-test-'.random_int(100, 999).'-'.uniqid('', false); $filepath = tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); diff --git a/tests/Dataset/SvmDatasetTest.php b/tests/Dataset/SvmDatasetTest.php index 884da3a5..700055c8 100644 --- a/tests/Dataset/SvmDatasetTest.php +++ b/tests/Dataset/SvmDatasetTest.php @@ -136,77 +136,77 @@ public function testSvmDatasetTabs(): void public function testSvmDatasetMissingFile(): void { $this->expectException(FileException::class); + $this->expectExceptionMessage('File "err_file_not_exists.svm" missing.'); - $filePath = self::getFilePath('err_file_not_exists.svm'); - $dataset = new SvmDataset($filePath); + new SvmDataset(self::getFilePath('err_file_not_exists.svm')); } public function testSvmDatasetEmptyLine(): void { $this->expectException(DatasetException::class); + $this->expectExceptionMessage('Invalid target "".'); - $filePath = self::getFilePath('err_empty_line.svm'); - $dataset = new SvmDataset($filePath); + new SvmDataset(self::getFilePath('err_empty_line.svm')); } public function testSvmDatasetNoLabels(): void { $this->expectException(DatasetException::class); + $this->expectExceptionMessage('Invalid target "1:2.3".'); - $filePath = self::getFilePath('err_no_labels.svm'); - $dataset = new SvmDataset($filePath); + new SvmDataset(self::getFilePath('err_no_labels.svm')); } public function testSvmDatasetStringLabels(): void { $this->expectException(DatasetException::class); + $this->expectExceptionMessage('Invalid target "A".'); - $filePath = self::getFilePath('err_string_labels.svm'); - $dataset = new SvmDataset($filePath); + new SvmDataset(self::getFilePath('err_string_labels.svm')); } public function testSvmDatasetInvalidSpaces(): void { $this->expectException(DatasetException::class); + $this->expectExceptionMessage('Invalid target "".'); - $filePath = self::getFilePath('err_invalid_spaces.svm'); - $dataset = new SvmDataset($filePath); + new SvmDataset(self::getFilePath('err_invalid_spaces.svm')); } public function testSvmDatasetStringIndex(): void { $this->expectException(DatasetException::class); + $this->expectExceptionMessage('Invalid index "x".'); - $filePath = self::getFilePath('err_string_index.svm'); - $dataset = new SvmDataset($filePath); + new SvmDataset(self::getFilePath('err_string_index.svm')); } public function testSvmDatasetIndexZero(): void { $this->expectException(DatasetException::class); + $this->expectExceptionMessage('Invalid index "0".'); - $filePath = self::getFilePath('err_index_zero.svm'); - $dataset = new SvmDataset($filePath); + new SvmDataset(self::getFilePath('err_index_zero.svm')); } public function testSvmDatasetInvalidValue(): void { $this->expectException(DatasetException::class); + $this->expectExceptionMessage('Invalid value "xyz".'); - $filePath = self::getFilePath('err_invalid_value.svm'); - $dataset = new SvmDataset($filePath); + new SvmDataset(self::getFilePath('err_invalid_value.svm')); } public function testSvmDatasetInvalidFeature(): void { $this->expectException(DatasetException::class); + $this->expectExceptionMessage('Invalid value "12345".'); - $filePath = self::getFilePath('err_invalid_feature.svm'); - $dataset = new SvmDataset($filePath); + new SvmDataset(self::getFilePath('err_invalid_feature.svm')); } private static function getFilePath(string $baseName): string { - return dirname(__FILE__).'/Resources/svm/'.$baseName; + return __DIR__.'/Resources/svm/'.$baseName; } } diff --git a/tests/DimensionReduction/KernelPCATest.php b/tests/DimensionReduction/KernelPCATest.php index dfd8bb93..cca0d537 100644 --- a/tests/DimensionReduction/KernelPCATest.php +++ b/tests/DimensionReduction/KernelPCATest.php @@ -54,16 +54,18 @@ public function testKernelPCA(): void public function testKernelPCAThrowWhenKernelInvalid(): void { $this->expectException(InvalidArgumentException::class); - $kpca = new KernelPCA(0, null, 1, 15); + $this->expectExceptionMessage('KernelPCA can be initialized with the following kernels only: Linear, RBF, Sigmoid and Laplacian'); + new KernelPCA(0, null, 1, 15.); } public function testTransformThrowWhenNotFitted(): void { $samples = [1, 0]; - $kpca = new KernelPCA(KernelPCA::KERNEL_RBF, null, 1, 15); + $kpca = new KernelPCA(KernelPCA::KERNEL_RBF, null, 1, 15.); $this->expectException(InvalidOperationException::class); + $this->expectExceptionMessage('KernelPCA has not been fitted with respect to original dataset, please run KernelPCA::fit() first'); $kpca->transform($samples); } @@ -74,10 +76,11 @@ public function testTransformThrowWhenMultiDimensionalArrayGiven(): void [1, 1], ]; - $kpca = new KernelPCA(KernelPCA::KERNEL_RBF, null, 1, 15); + $kpca = new KernelPCA(KernelPCA::KERNEL_RBF, null, 1, 15.); $kpca->fit($samples); $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('KernelPCA::transform() accepts only one-dimensional arrays'); $kpca->transform($samples); } } diff --git a/tests/DimensionReduction/LDATest.php b/tests/DimensionReduction/LDATest.php index 641c226a..79e925f6 100644 --- a/tests/DimensionReduction/LDATest.php +++ b/tests/DimensionReduction/LDATest.php @@ -68,25 +68,29 @@ public function testLDA(): void public function testLDAThrowWhenTotalVarianceOutOfRange(): void { $this->expectException(InvalidArgumentException::class); - $pca = new LDA(0, null); + $this->expectExceptionMessage('Total variance can be a value between 0.1 and 0.99'); + new LDA(0., null); } public function testLDAThrowWhenNumFeaturesOutOfRange(): void { $this->expectException(InvalidArgumentException::class); - $pca = new LDA(null, 0); + $this->expectExceptionMessage('Number of features to be preserved should be greater than 0'); + new LDA(null, 0); } public function testLDAThrowWhenParameterNotSpecified(): void { $this->expectException(InvalidArgumentException::class); - $pca = new LDA(); + $this->expectExceptionMessage('Either totalVariance or numFeatures should be specified in order to run the algorithm'); + new LDA(); } public function testLDAThrowWhenBothParameterSpecified(): void { $this->expectException(InvalidArgumentException::class); - $pca = new LDA(0.9, 1); + $this->expectExceptionMessage('Either totalVariance or numFeatures should be specified in order to run the algorithm'); + new LDA(0.9, 1); } public function testTransformThrowWhenNotFitted(): void @@ -99,6 +103,7 @@ public function testTransformThrowWhenNotFitted(): void $pca = new LDA(0.9); $this->expectException(InvalidOperationException::class); + $this->expectExceptionMessage('LDA has not been fitted with respect to original dataset, please run LDA::fit() first'); $pca->transform($samples); } } diff --git a/tests/DimensionReduction/PCATest.php b/tests/DimensionReduction/PCATest.php index 5fca2113..ba25268b 100644 --- a/tests/DimensionReduction/PCATest.php +++ b/tests/DimensionReduction/PCATest.php @@ -60,25 +60,29 @@ public function testPCA(): void public function testPCAThrowWhenTotalVarianceOutOfRange(): void { $this->expectException(InvalidArgumentException::class); - $pca = new PCA(0, null); + $this->expectExceptionMessage('Total variance can be a value between 0.1 and 0.99'); + new PCA(0., null); } public function testPCAThrowWhenNumFeaturesOutOfRange(): void { $this->expectException(InvalidArgumentException::class); - $pca = new PCA(null, 0); + $this->expectExceptionMessage('Number of features to be preserved should be greater than 0'); + new PCA(null, 0); } public function testPCAThrowWhenParameterNotSpecified(): void { $this->expectException(InvalidArgumentException::class); - $pca = new PCA(); + $this->expectExceptionMessage('Either totalVariance or numFeatures should be specified in order to run the algorithm'); + new PCA(); } public function testPCAThrowWhenBothParameterSpecified(): void { $this->expectException(InvalidArgumentException::class); - $pca = new PCA(0.9, 1); + $this->expectExceptionMessage('Either totalVariance or numFeatures should be specified in order to run the algorithm'); + new PCA(0.9, 1); } public function testTransformThrowWhenNotFitted(): void @@ -91,6 +95,7 @@ public function testTransformThrowWhenNotFitted(): void $pca = new PCA(0.9); $this->expectException(InvalidOperationException::class); + $this->expectExceptionMessage('PCA has not been fitted with respect to original dataset, please run PCA::fit() first'); $pca->transform($samples); } } diff --git a/tests/Metric/ClassificationReportTest.php b/tests/Metric/ClassificationReportTest.php index 4c4f01fa..fa9080b3 100644 --- a/tests/Metric/ClassificationReportTest.php +++ b/tests/Metric/ClassificationReportTest.php @@ -98,7 +98,7 @@ public function testClassificationReportAverageOutOfRange(): void $predicted = ['cat', 'cat', 'bird', 'bird', 'ant']; $this->expectException(InvalidArgumentException::class); - $report = new ClassificationReport($labels, $predicted, 0); + new ClassificationReport($labels, $predicted, 0); } public function testClassificationReportMicroAverage(): void diff --git a/tests/SupportVectorMachine/SupportVectorMachineTest.php b/tests/SupportVectorMachine/SupportVectorMachineTest.php index 899fa40c..088d37b0 100644 --- a/tests/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/SupportVectorMachine/SupportVectorMachineTest.php @@ -197,7 +197,7 @@ public function testThrowExceptionWhenPredictProbabilityCalledWithoutProperModel $svm = new SupportVectorMachine(Type::C_SVC, Kernel::LINEAR, 100.0); $svm->train($samples, $labels); - $predictions = $svm->predictProbability([ + $svm->predictProbability([ [3, 2], [2, 3], [4, -5], From d9b85e841fecbb6e410c4c39757fb2a2ba69f3ce Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Mon, 22 Oct 2018 09:06:20 +0200 Subject: [PATCH 286/328] Update dependencies (#319) --- composer.lock | 107 ++++++++++++++++++++++++++------------------------ 1 file changed, 55 insertions(+), 52 deletions(-) diff --git a/composer.lock b/composer.lock index 7a2beb9a..fa22470d 100644 --- a/composer.lock +++ b/composer.lock @@ -1,7 +1,7 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], "content-hash": "cb4240c977f956be78a7fa686c77d0f2", @@ -983,22 +983,22 @@ }, { "name": "phar-io/manifest", - "version": "1.0.1", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", - "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", "shasum": "" }, "require": { "ext-dom": "*", "ext-phar": "*", - "phar-io/version": "^1.0.1", + "phar-io/version": "^2.0", "php": "^5.6 || ^7.0" }, "type": "library", @@ -1034,20 +1034,20 @@ } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "time": "2017-03-05T18:14:27+00:00" + "time": "2018-07-08T19:23:20+00:00" }, { "name": "phar-io/version", - "version": "1.0.1", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", - "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", "shasum": "" }, "require": { @@ -1081,7 +1081,7 @@ } ], "description": "Library for handling version information and constraints", - "time": "2017-03-05T17:38:23+00:00" + "time": "2018-07-08T19:19:57+00:00" }, { "name": "php-cs-fixer/diff", @@ -1450,16 +1450,16 @@ }, { "name": "phpspec/prophecy", - "version": "1.7.6", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712" + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/33a7e3c4fda54e912ff6338c48823bd5c0f0b712", - "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", "shasum": "" }, "require": { @@ -1471,12 +1471,12 @@ }, "require-dev": { "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7.x-dev" + "dev-master": "1.8.x-dev" } }, "autoload": { @@ -1509,7 +1509,7 @@ "spy", "stub" ], - "time": "2018-04-18T13:57:24+00:00" + "time": "2018-08-05T17:53:17+00:00" }, { "name": "phpstan/phpdoc-parser", @@ -1685,16 +1685,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "6.0.7", + "version": "6.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "865662550c384bc1db7e51d29aeda1c2c161d69a" + "reference": "0685fb6a43aed1b2e09804d1aaf17144c82861f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/865662550c384bc1db7e51d29aeda1c2c161d69a", - "reference": "865662550c384bc1db7e51d29aeda1c2c161d69a", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/0685fb6a43aed1b2e09804d1aaf17144c82861f8", + "reference": "0685fb6a43aed1b2e09804d1aaf17144c82861f8", "shasum": "" }, "require": { @@ -1718,7 +1718,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.0-dev" + "dev-master": "6.1-dev" } }, "autoload": { @@ -1744,25 +1744,28 @@ "testing", "xunit" ], - "time": "2018-06-01T07:51:50+00:00" + "time": "2018-10-16T05:37:37+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "2.0.1", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "cecbc684605bb0cc288828eb5d65d93d5c676d3c" + "reference": "050bedf145a257b1ff02746c31894800e5122946" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cecbc684605bb0cc288828eb5d65d93d5c676d3c", - "reference": "cecbc684605bb0cc288828eb5d65d93d5c676d3c", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", + "reference": "050bedf145a257b1ff02746c31894800e5122946", "shasum": "" }, "require": { "php": "^7.1" }, + "require-dev": { + "phpunit/phpunit": "^7.1" + }, "type": "library", "extra": { "branch-alias": { @@ -1791,7 +1794,7 @@ "filesystem", "iterator" ], - "time": "2018-06-11T11:44:00+00:00" + "time": "2018-09-13T20:33:42+00:00" }, { "name": "phpunit/php-text-template", @@ -1934,16 +1937,16 @@ }, { "name": "phpunit/phpunit", - "version": "7.2.4", + "version": "7.4.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "00bc0b93f0ff4f557e9ea766557fde96da9a03dd" + "reference": "f3837fa1e07758057ae06e8ddec6d06ba183f126" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/00bc0b93f0ff4f557e9ea766557fde96da9a03dd", - "reference": "00bc0b93f0ff4f557e9ea766557fde96da9a03dd", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f3837fa1e07758057ae06e8ddec6d06ba183f126", + "reference": "f3837fa1e07758057ae06e8ddec6d06ba183f126", "shasum": "" }, "require": { @@ -1954,12 +1957,12 @@ "ext-mbstring": "*", "ext-xml": "*", "myclabs/deep-copy": "^1.7", - "phar-io/manifest": "^1.0.1", - "phar-io/version": "^1.0", + "phar-io/manifest": "^1.0.2", + "phar-io/version": "^2.0", "php": "^7.1", "phpspec/prophecy": "^1.7", "phpunit/php-code-coverage": "^6.0.7", - "phpunit/php-file-iterator": "^2.0", + "phpunit/php-file-iterator": "^2.0.1", "phpunit/php-text-template": "^1.2.1", "phpunit/php-timer": "^2.0", "sebastian/comparator": "^3.0", @@ -1968,7 +1971,7 @@ "sebastian/exporter": "^3.1", "sebastian/global-state": "^2.0", "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^1.0", + "sebastian/resource-operations": "^2.0", "sebastian/version": "^2.0.1" }, "conflict": { @@ -1988,7 +1991,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.2-dev" + "dev-master": "7.4-dev" } }, "autoload": { @@ -2014,7 +2017,7 @@ "testing", "xunit" ], - "time": "2018-06-05T03:40:05+00:00" + "time": "2018-10-05T04:05:24+00:00" }, { "name": "psr/cache", @@ -2253,16 +2256,16 @@ }, { "name": "sebastian/comparator", - "version": "3.0.0", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "ed5fd2281113729f1ebcc64d101ad66028aeb3d5" + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/ed5fd2281113729f1ebcc64d101ad66028aeb3d5", - "reference": "ed5fd2281113729f1ebcc64d101ad66028aeb3d5", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", "shasum": "" }, "require": { @@ -2313,7 +2316,7 @@ "compare", "equality" ], - "time": "2018-04-18T13:33:00+00:00" + "time": "2018-07-12T15:12:46+00:00" }, { "name": "sebastian/diff", @@ -2686,25 +2689,25 @@ }, { "name": "sebastian/resource-operations", - "version": "1.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", "shasum": "" }, "require": { - "php": ">=5.6.0" + "php": "^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -2724,7 +2727,7 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2015-07-28T20:34:47+00:00" + "time": "2018-10-04T04:07:39+00:00" }, { "name": "sebastian/version", From 53c5a6b9e5b7757a1b1faf2dfaa5027b8c8a84e6 Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Sun, 28 Oct 2018 07:44:52 +0100 Subject: [PATCH 287/328] Update phpstan to 0.10.5 (#320) --- CHANGELOG.md | 1 + composer.json | 6 +- composer.lock | 128 +++++++---------- phpstan.neon | 3 +- src/Association/Apriori.php | 12 +- src/Classification/DecisionTree.php | 28 ++-- .../DecisionTree/DecisionTreeLeaf.php | 4 +- src/Classification/Ensemble/AdaBoost.php | 11 +- src/Classification/Ensemble/Bagging.php | 17 +-- src/Classification/KNearestNeighbors.php | 3 +- src/Classification/Linear/Adaline.php | 4 +- src/Classification/Linear/DecisionStump.php | 15 +- src/Classification/Linear/Perceptron.php | 9 +- src/Clustering/FuzzyCMeans.php | 13 +- src/Clustering/KMeans/Cluster.php | 14 +- src/Clustering/KMeans/Point.php | 16 ++- src/Clustering/KMeans/Space.php | 51 +++++-- src/CrossValidation/Split.php | 2 +- src/CrossValidation/StratifiedRandomSplit.php | 1 + src/Dataset/CsvDataset.php | 4 +- src/Dataset/SvmDataset.php | 7 +- src/DimensionReduction/KernelPCA.php | 8 +- src/DimensionReduction/PCA.php | 2 +- src/Estimator.php | 2 +- src/FeatureExtraction/TfIdfTransformer.php | 2 +- src/FeatureSelection/SelectKBest.php | 2 +- src/Helper/OneVsRest.php | 19 +-- src/Helper/Optimizer/ConjugateGradient.php | 2 +- src/Helper/Optimizer/GD.php | 5 + src/Helper/Optimizer/Optimizer.php | 6 +- src/Helper/Optimizer/StochasticGD.php | 17 ++- src/IncrementalEstimator.php | 2 +- src/Math/Comparison.php | 3 + .../LinearAlgebra/EigenvalueDecomposition.php | 133 +++++++++--------- src/Math/LinearAlgebra/LUDecomposition.php | 6 +- src/Math/Matrix.php | 12 +- src/Math/Product.php | 2 +- src/Math/Set.php | 4 +- src/Math/Statistic/ANOVA.php | 2 +- src/Math/Statistic/Covariance.php | 8 +- src/Math/Statistic/Mean.php | 2 +- src/Math/Statistic/StandardDeviation.php | 9 +- src/Metric/ClassificationReport.php | 8 +- src/Metric/ConfusionMatrix.php | 4 +- src/ModelManager.php | 4 +- src/NeuralNetwork/Network.php | 2 +- .../Network/MultilayerPerceptron.php | 6 +- src/NeuralNetwork/Node/Neuron/Synapse.php | 2 +- src/NeuralNetwork/Training.php | 10 -- src/SupportVectorMachine/DataTransformer.php | 4 +- .../SupportVectorMachine.php | 4 +- src/Tokenization/WhitespaceTokenizer.php | 9 +- tests/Association/AprioriTest.php | 103 +++++++------- .../DecisionTree/DecisionTreeLeafTest.php | 9 +- tests/Classification/DecisionTreeTest.php | 32 +++-- .../Classification/Ensemble/AdaBoostTest.php | 26 ++-- tests/Classification/Ensemble/BaggingTest.php | 36 +++-- .../Ensemble/RandomForestTest.php | 8 +- .../Classification/KNearestNeighborsTest.php | 28 ++-- tests/Classification/Linear/AdalineTest.php | 36 ++--- .../Linear/DecisionStumpTest.php | 34 +++-- .../Linear/LogisticRegressionTest.php | 44 +++--- .../Classification/Linear/PerceptronTest.php | 36 ++--- tests/Classification/MLPClassifierTest.php | 94 ++++++------- tests/Classification/NaiveBayesTest.php | 32 ++--- tests/Classification/SVCTest.php | 32 ++--- tests/Clustering/DBSCANTest.php | 12 +- tests/Clustering/FuzzyCMeansTest.php | 20 +-- tests/Clustering/KMeans/ClusterTest.php | 8 +- tests/Clustering/KMeansTest.php | 14 +- tests/CrossValidation/RandomSplitTest.php | 32 ++--- .../StratifiedRandomSplitTest.php | 21 +-- tests/Dataset/ArrayDatasetTest.php | 6 +- tests/Dataset/CsvDatasetTest.php | 12 +- tests/Dataset/Demo/GlassDatasetTest.php | 6 +- tests/Dataset/Demo/IrisDatasetTest.php | 6 +- tests/Dataset/Demo/WineDatasetTest.php | 6 +- tests/Dataset/FilesDatasetTest.php | 14 +- tests/Dataset/SvmDatasetTest.php | 28 ++-- tests/DimensionReduction/KernelPCATest.php | 4 +- tests/DimensionReduction/LDATest.php | 2 +- tests/DimensionReduction/PCATest.php | 4 +- tests/FeatureExtraction/StopWordsTest.php | 24 ++-- .../TfIdfTransformerTest.php | 2 +- .../TokenCountVectorizerTest.php | 14 +- .../Optimizer/ConjugateGradientTest.php | 6 +- tests/Helper/Optimizer/GDTest.php | 4 +- tests/Helper/Optimizer/OptimizerTest.php | 6 +- tests/Helper/Optimizer/StochasticGDTest.php | 4 +- tests/Math/ComparisonTest.php | 2 +- tests/Math/Distance/ChebyshevTest.php | 6 +- tests/Math/Distance/EuclideanTest.php | 6 +- tests/Math/Distance/ManhattanTest.php | 6 +- tests/Math/Distance/MinkowskiTest.php | 8 +- tests/Math/Kernel/RBFTest.php | 12 +- .../LinearAlgebra/LUDecompositionTest.php | 6 +- tests/Math/MatrixTest.php | 43 +++--- tests/Math/ProductTest.php | 8 +- tests/Math/SetTest.php | 43 +++--- tests/Math/Statistic/CorrelationTest.php | 6 +- tests/Math/Statistic/CovarianceTest.php | 12 +- tests/Math/Statistic/GaussianTest.php | 4 +- tests/Math/Statistic/MeanTest.php | 12 +- .../Math/Statistic/StandardDeviationTest.php | 6 +- tests/Math/Statistic/VarianceTest.php | 2 +- tests/Metric/AccuracyTest.php | 6 +- tests/Metric/ClassificationReportTest.php | 34 ++--- tests/Metric/ConfusionMatrixTest.php | 8 +- tests/ModelManagerTest.php | 4 +- .../ActivationFunction/BinaryStepTest.php | 12 +- .../ActivationFunction/GaussianTest.php | 12 +- .../HyperboliTangentTest.php | 12 +- .../ActivationFunction/PReLUTest.php | 12 +- .../ActivationFunction/SigmoidTest.php | 12 +- .../ThresholdedReLUTest.php | 12 +- tests/NeuralNetwork/LayerTest.php | 12 +- .../Network/LayeredNetworkTest.php | 12 +- .../Network/MultilayerPerceptronTest.php | 36 ++--- tests/NeuralNetwork/Node/BiasTest.php | 2 +- tests/NeuralNetwork/Node/InputTest.php | 6 +- .../NeuralNetwork/Node/Neuron/SynapseTest.php | 13 +- tests/NeuralNetwork/Node/NeuronTest.php | 14 +- tests/PipelineTest.php | 16 +-- tests/Preprocessing/ImputerTest.php | 14 +- tests/Preprocessing/NormalizerTest.php | 16 +-- tests/Regression/LeastSquaresTest.php | 30 ++-- tests/Regression/SVRTest.php | 12 +- .../DataTransformerTest.php | 8 +- .../SupportVectorMachineTest.php | 26 ++-- .../Tokenization/WhitespaceTokenizerTest.php | 4 +- tests/Tokenization/WordTokenizerTest.php | 4 +- 131 files changed, 1011 insertions(+), 975 deletions(-) delete mode 100644 src/NeuralNetwork/Training.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 12a9c3a7..1c1e0970 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ This changelog references the relevant changes done in PHP-ML library. * fix SVM locale (non-locale aware) (#288) * typo, tests, code styles and documentation fixes (#265, #261, #254, #253, #251, #250, #248, #245, #243) * change [MLPClassifier] return labels in output (#315) + * enhancement Update phpstan to 0.10.5 (#320) * 0.6.2 (2018-02-22) * Fix Apriori array keys (#238) diff --git a/composer.json b/composer.json index 7b1cbb65..1604bbfd 100644 --- a/composer.json +++ b/composer.json @@ -24,9 +24,9 @@ }, "require-dev": { "phpbench/phpbench": "^0.14.0", - "phpstan/phpstan-phpunit": "^0.9.4", - "phpstan/phpstan-shim": "^0.9", - "phpstan/phpstan-strict-rules": "^0.9.0", + "phpstan/phpstan-phpunit": "^0.10", + "phpstan/phpstan-shim": "^0.10", + "phpstan/phpstan-strict-rules": "^0.10", "phpunit/phpunit": "^7.0.0", "symplify/coding-standard": "^5.1", "symplify/easy-coding-standard": "^5.1" diff --git a/composer.lock b/composer.lock index fa22470d..70324ee0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "cb4240c977f956be78a7fa686c77d0f2", + "content-hash": "9ec1ca6b843d05e0870bd777026d7a8b", "packages": [], "packages-dev": [ { @@ -1511,83 +1511,42 @@ ], "time": "2018-08-05T17:53:17+00:00" }, - { - "name": "phpstan/phpdoc-parser", - "version": "0.3", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "ed3223362174b8067729930439e139794e9e514a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/ed3223362174b8067729930439e139794e9e514a", - "reference": "ed3223362174b8067729930439e139794e9e514a", - "shasum": "" - }, - "require": { - "php": "~7.1" - }, - "require-dev": { - "consistence/coding-standard": "^2.0.0", - "jakub-onderka/php-parallel-lint": "^0.9.2", - "phing/phing": "^2.16.0", - "phpstan/phpstan": "^0.10@dev", - "phpunit/phpunit": "^6.3", - "slevomat/coding-standard": "^3.3.0", - "symfony/process": "^3.4 || ^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.3-dev" - } - }, - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "time": "2018-06-20T17:48:01+00:00" - }, { "name": "phpstan/phpstan-phpunit", - "version": "0.9.4", + "version": "0.10", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "852411f841a37aeca2fa5af0002b0272c485c9bf" + "reference": "6feecc7faae187daa6be44140cd0f1ba210e6aa0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/852411f841a37aeca2fa5af0002b0272c485c9bf", - "reference": "852411f841a37aeca2fa5af0002b0272c485c9bf", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/6feecc7faae187daa6be44140cd0f1ba210e6aa0", + "reference": "6feecc7faae187daa6be44140cd0f1ba210e6aa0", "shasum": "" }, "require": { - "php": "~7.0", - "phpstan/phpstan": "^0.9.1", - "phpunit/phpunit": "^6.3 || ~7.0" + "nikic/php-parser": "^4.0", + "php": "~7.1", + "phpstan/phpstan": "^0.10" + }, + "conflict": { + "phpunit/phpunit": "<7.0" }, "require-dev": { - "consistence/coding-standard": "^2.0", - "jakub-onderka/php-parallel-lint": "^0.9.2", + "consistence/coding-standard": "^3.0.1", + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.4", + "jakub-onderka/php-parallel-lint": "^1.0", "phing/phing": "^2.16.0", - "phpstan/phpstan-strict-rules": "^0.9", + "phpstan/phpstan-strict-rules": "^0.10", + "phpunit/phpunit": "^7.0", "satooshi/php-coveralls": "^1.0", - "slevomat/coding-standard": "^3.3.0" + "slevomat/coding-standard": "^4.5.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "0.9-dev" + "dev-master": "0.10-dev" } }, "autoload": { @@ -1600,26 +1559,28 @@ "MIT" ], "description": "PHPUnit extensions and rules for PHPStan", - "time": "2018-02-02T09:45:47+00:00" + "time": "2018-06-22T18:12:17+00:00" }, { "name": "phpstan/phpstan-shim", - "version": "0.9.2", + "version": "0.10.5", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-shim.git", - "reference": "e4720fb2916be05de02869780072253e7e0e8a75" + "reference": "a274185548d140a7f48cc1eed5b94f3a9068c674" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-shim/zipball/e4720fb2916be05de02869780072253e7e0e8a75", - "reference": "e4720fb2916be05de02869780072253e7e0e8a75", + "url": "https://api.github.com/repos/phpstan/phpstan-shim/zipball/a274185548d140a7f48cc1eed5b94f3a9068c674", + "reference": "a274185548d140a7f48cc1eed5b94f3a9068c674", "shasum": "" }, "require": { - "php": "~7.0" + "php": "~7.1" }, "replace": { + "nikic/php-parser": "^4.0.2", + "phpstan/phpdoc-parser": "^0.3", "phpstan/phpstan": "self.version" }, "bin": [ @@ -1629,46 +1590,53 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "0.9-dev" + "dev-master": "0.10-dev" } }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "description": "PHPStan Phar distribution", - "time": "2018-01-28T14:29:27+00:00" + "time": "2018-10-20T17:45:03+00:00" }, { "name": "phpstan/phpstan-strict-rules", - "version": "0.9", + "version": "0.10.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "15be9090622c6b85c079922308f831018d8d9e23" + "reference": "18c0b6e8899606b127c680402ab473a7b67166db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/15be9090622c6b85c079922308f831018d8d9e23", - "reference": "15be9090622c6b85c079922308f831018d8d9e23", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/18c0b6e8899606b127c680402ab473a7b67166db", + "reference": "18c0b6e8899606b127c680402ab473a7b67166db", "shasum": "" }, "require": { - "php": "~7.0", - "phpstan/phpstan": "^0.9" + "nikic/php-parser": "^4.0", + "php": "~7.1", + "phpstan/phpstan": "^0.10" }, "require-dev": { - "consistence/coding-standard": "^2.0.0", - "jakub-onderka/php-parallel-lint": "^0.9.2", + "consistence/coding-standard": "^3.0.1", + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.4", + "jakub-onderka/php-parallel-lint": "^1.0", "phing/phing": "^2.16.0", - "phpstan/phpstan-phpunit": "^0.9", - "phpunit/phpunit": "^6.4", - "slevomat/coding-standard": "^3.3.0" + "phpstan/phpstan-phpunit": "^0.10", + "phpunit/phpunit": "^7.0", + "slevomat/coding-standard": "^4.5.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "0.9-dev" + "dev-master": "0.10-dev" } }, "autoload": { @@ -1681,7 +1649,7 @@ "MIT" ], "description": "Extra strict and opinionated rules for PHPStan", - "time": "2017-11-26T20:12:30+00:00" + "time": "2018-07-06T20:36:44+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/phpstan.neon b/phpstan.neon index 7eaee1c8..7a676fa0 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,14 +2,13 @@ includes: - vendor/phpstan/phpstan-strict-rules/rules.neon - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/rules.neon - - vendor/phpstan/phpstan-phpunit/strictRules.neon parameters: ignoreErrors: + - '#Property Phpml\\Clustering\\KMeans\\Cluster\:\:\$points \(iterable\\&SplObjectStorage\) does not accept SplObjectStorage#' - '#Phpml\\Dataset\\FilesDataset::__construct\(\) does not call parent constructor from Phpml\\Dataset\\ArrayDataset#' # wide range cases - - '#Call to function count\(\) with argument type array\|Phpml\\Clustering\\KMeans\\Point will always result in number 1#' - '#Parameter \#1 \$coordinates of class Phpml\\Clustering\\KMeans\\Point constructor expects array, array\|Phpml\\Clustering\\KMeans\\Point given#' # probably known value diff --git a/src/Association/Apriori.php b/src/Association/Apriori.php index 2d09dd7b..201bfbf0 100644 --- a/src/Association/Apriori.php +++ b/src/Association/Apriori.php @@ -64,11 +64,11 @@ public function __construct(float $support = 0.0, float $confidence = 0.0) */ public function getRules(): array { - if (empty($this->large)) { + if (count($this->large) === 0) { $this->large = $this->apriori(); } - if (!empty($this->rules)) { + if (count($this->rules) > 0) { return $this->rules; } @@ -89,7 +89,7 @@ public function apriori(): array $L = []; $items = $this->frequent($this->items()); - for ($k = 1; !empty($items); ++$k) { + for ($k = 1; isset($items[0]); ++$k) { $L[$k] = $items; $items = $this->frequent($this->candidates($items)); } @@ -118,7 +118,7 @@ protected function predictSample(array $sample): array */ private function generateAllRules(): void { - for ($k = 2; !empty($this->large[$k]); ++$k) { + for ($k = 2; isset($this->large[$k]); ++$k) { foreach ($this->large[$k] as $frequent) { $this->generateRules($frequent); } @@ -241,7 +241,7 @@ private function candidates(array $samples): array continue; } - foreach ((array) $this->samples as $sample) { + foreach ($this->samples as $sample) { if ($this->subset($sample, $candidate)) { $candidates[] = $candidate; @@ -316,7 +316,7 @@ private function contains(array $system, array $set): bool */ private function subset(array $set, array $subset): bool { - return !array_diff($subset, array_intersect($subset, $set)); + return count(array_diff($subset, array_intersect($subset, $set))) === 0; } /** diff --git a/src/Classification/DecisionTree.php b/src/Classification/DecisionTree.php index 13a79754..d8010f02 100644 --- a/src/Classification/DecisionTree.php +++ b/src/Classification/DecisionTree.php @@ -249,7 +249,7 @@ protected function getSplitLeaf(array $records, int $depth = 0): DecisionTreeLea foreach ($records as $recordNo) { // Check if the previous record is the same with the current one $record = $this->samples[$recordNo]; - if ($prevRecord && $prevRecord != $record) { + if ($prevRecord !== null && $prevRecord != $record) { $allSame = false; } @@ -275,13 +275,13 @@ protected function getSplitLeaf(array $records, int $depth = 0): DecisionTreeLea if ($allSame || $depth >= $this->maxDepth || count($remainingTargets) === 1) { $split->isTerminal = true; arsort($remainingTargets); - $split->classValue = key($remainingTargets); + $split->classValue = (string) key($remainingTargets); } else { - if (!empty($leftRecords)) { + if (isset($leftRecords[0])) { $split->leftLeaf = $this->getSplitLeaf($leftRecords, $depth + 1); } - if (!empty($rightRecords)) { + if (isset($rightRecords[0])) { $split->rightLeaf = $this->getSplitLeaf($rightRecords, $depth + 1); } } @@ -292,8 +292,10 @@ protected function getSplitLeaf(array $records, int $depth = 0): DecisionTreeLea protected function getBestSplit(array $records): DecisionTreeLeaf { $targets = array_intersect_key($this->targets, array_flip($records)); - $samples = array_intersect_key($this->samples, array_flip($records)); - $samples = array_combine($records, $this->preprocess($samples)); + $samples = (array) array_combine( + $records, + $this->preprocess(array_intersect_key($this->samples, array_flip($records))) + ); $bestGiniVal = 1; $bestSplit = null; $features = $this->getSelectedFeatures(); @@ -306,6 +308,10 @@ protected function getBestSplit(array $records): DecisionTreeLeaf $counts = array_count_values($colValues); arsort($counts); $baseValue = key($counts); + if ($baseValue === null) { + continue; + } + $gini = $this->getGiniIndex($baseValue, $colValues, $targets); if ($bestSplit === null || $bestGiniVal > $gini) { $split = new DecisionTreeLeaf(); @@ -349,11 +355,11 @@ protected function getBestSplit(array $records): DecisionTreeLeaf protected function getSelectedFeatures(): array { $allFeatures = range(0, $this->featureCount - 1); - if ($this->numUsableFeatures === 0 && empty($this->selectedFeatures)) { + if ($this->numUsableFeatures === 0 && count($this->selectedFeatures) === 0) { return $allFeatures; } - if (!empty($this->selectedFeatures)) { + if (count($this->selectedFeatures) > 0) { return $this->selectedFeatures; } @@ -406,7 +412,7 @@ protected static function isCategoricalColumn(array $columnValues): bool // all values in that column (Lower than or equal to %20 of all values) $numericValues = array_filter($columnValues, 'is_numeric'); $floatValues = array_filter($columnValues, 'is_float'); - if (!empty($floatValues)) { + if (count($floatValues) > 0) { return false; } @@ -463,7 +469,7 @@ protected function predictSample(array $sample) $node = $this->tree; do { if ($node->isTerminal) { - break; + return $node->classValue; } if ($node->evaluate($sample)) { @@ -473,6 +479,6 @@ protected function predictSample(array $sample) } } while ($node); - return $node !== null ? $node->classValue : $this->labels[0]; + return $this->labels[0]; } } diff --git a/src/Classification/DecisionTree/DecisionTreeLeaf.php b/src/Classification/DecisionTree/DecisionTreeLeaf.php index 649c7433..04af3d62 100644 --- a/src/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Classification/DecisionTree/DecisionTreeLeaf.php @@ -119,7 +119,7 @@ public function getNodeImpurityDecrease(int $parentRecordCount): float /** * Returns HTML representation of the node including children nodes */ - public function getHTML($columnNames = null): string + public function getHTML(?array $columnNames = null): string { if ($this->isTerminal) { $value = "${this}->classValue"; @@ -131,7 +131,7 @@ public function getHTML($columnNames = null): string $col = "col_$this->columnIndex"; } - if (!preg_match('/^[<>=]{1,2}/', (string) $value)) { + if ((bool) preg_match('/^[<>=]{1,2}/', (string) $value) === false) { $value = "=${value}"; } diff --git a/src/Classification/Ensemble/AdaBoost.php b/src/Classification/Ensemble/AdaBoost.php index 3859295a..fdaeb63e 100644 --- a/src/Classification/Ensemble/AdaBoost.php +++ b/src/Classification/Ensemble/AdaBoost.php @@ -100,7 +100,7 @@ public function train(array $samples, array $targets): void { // Initialize usual variables $this->labels = array_keys(array_count_values($targets)); - if (count($this->labels) != 2) { + if (count($this->labels) !== 2) { throw new InvalidArgumentException('AdaBoost is a binary classifier and can classify between two classes only'); } @@ -159,13 +159,10 @@ public function predictSample(array $sample) protected function getBestClassifier(): Classifier { $ref = new ReflectionClass($this->baseClassifier); - if (!empty($this->classifierOptions)) { - $classifier = $ref->newInstanceArgs($this->classifierOptions); - } else { - $classifier = $ref->newInstance(); - } + /** @var Classifier $classifier */ + $classifier = count($this->classifierOptions) === 0 ? $ref->newInstance() : $ref->newInstanceArgs($this->classifierOptions); - if (is_subclass_of($classifier, WeightedClassifier::class)) { + if ($classifier instanceof WeightedClassifier) { $classifier->setSampleWeights($this->weights); $classifier->train($this->samples, $this->targets); } else { diff --git a/src/Classification/Ensemble/Bagging.php b/src/Classification/Ensemble/Bagging.php index b73a1d39..26cc7a6d 100644 --- a/src/Classification/Ensemble/Bagging.php +++ b/src/Classification/Ensemble/Bagging.php @@ -51,16 +51,6 @@ class Bagging implements Classifier */ protected $subsetRatio = 0.7; - /** - * @var array - */ - private $targets = []; - - /** - * @var array - */ - private $samples = []; - /** * Creates an ensemble classifier with given number of base classifiers * Default number of base classifiers is 50. @@ -146,11 +136,8 @@ protected function initClassifiers(): array $classifiers = []; for ($i = 0; $i < $this->numClassifier; ++$i) { $ref = new ReflectionClass($this->classifier); - if (!empty($this->classifierOptions)) { - $obj = $ref->newInstanceArgs($this->classifierOptions); - } else { - $obj = $ref->newInstance(); - } + /** @var Classifier $obj */ + $obj = count($this->classifierOptions) === 0 ? $ref->newInstance() : $ref->newInstanceArgs($this->classifierOptions); $classifiers[] = $this->initSingleClassifier($obj); } diff --git a/src/Classification/KNearestNeighbors.php b/src/Classification/KNearestNeighbors.php index cac54163..9b78baa8 100644 --- a/src/Classification/KNearestNeighbors.php +++ b/src/Classification/KNearestNeighbors.php @@ -45,8 +45,7 @@ public function __construct(int $k = 3, ?Distance $distanceMetric = null) protected function predictSample(array $sample) { $distances = $this->kNeighborsDistances($sample); - - $predictions = array_combine(array_values($this->targets), array_fill(0, count($this->targets), 0)); + $predictions = (array) array_combine(array_values($this->targets), array_fill(0, count($this->targets), 0)); foreach (array_keys($distances) as $index) { ++$predictions[$this->targets[$index]]; diff --git a/src/Classification/Linear/Adaline.php b/src/Classification/Linear/Adaline.php index a1337328..797cdc9c 100644 --- a/src/Classification/Linear/Adaline.php +++ b/src/Classification/Linear/Adaline.php @@ -55,7 +55,7 @@ public function __construct( * Adapts the weights with respect to given samples and targets * by use of gradient descent learning rule */ - protected function runTraining(array $samples, array $targets) + protected function runTraining(array $samples, array $targets): void { // The cost function is the sum of squares $callback = function ($weights, $sample, $target) { @@ -70,6 +70,6 @@ protected function runTraining(array $samples, array $targets) $isBatch = $this->trainingType == self::BATCH_TRAINING; - return parent::runGradientDescent($samples, $targets, $callback, $isBatch); + parent::runGradientDescent($samples, $targets, $callback, $isBatch); } } diff --git a/src/Classification/Linear/DecisionStump.php b/src/Classification/Linear/DecisionStump.php index c83d339e..258939e3 100644 --- a/src/Classification/Linear/DecisionStump.php +++ b/src/Classification/Linear/DecisionStump.php @@ -119,13 +119,13 @@ protected function trainBinary(array $samples, array $targets, array $labels): v // Check the size of the weights given. // If none given, then assign 1 as a weight to each sample - if (!empty($this->weights)) { + if (count($this->weights) === 0) { + $this->weights = array_fill(0, count($samples), 1); + } else { $numWeights = count($this->weights); - if ($numWeights != count($samples)) { + if ($numWeights !== count($samples)) { throw new InvalidArgumentException('Number of sample weights does not match with number of samples'); } - } else { - $this->weights = array_fill(0, count($samples), 1); } // Determine type of each column as either "continuous" or "nominal" @@ -134,7 +134,7 @@ protected function trainBinary(array $samples, array $targets, array $labels): v // Try to find the best split in the columns of the dataset // by calculating error rate for each split point in each column $columns = range(0, count($samples[0]) - 1); - if ($this->givenColumnIndex != self::AUTO_SELECT) { + if ($this->givenColumnIndex !== self::AUTO_SELECT) { $columns = [$this->givenColumnIndex]; } @@ -184,7 +184,7 @@ protected function getBestNumericalSplit(array $samples, array $targets, int $co // the average value for the cut point $threshold = array_sum($values) / (float) count($values); [$errorRate, $prob] = $this->calculateErrorRate($targets, $threshold, $operator, $values); - if ($split === [] || $errorRate < $split['trainingErrorRate']) { + if (!isset($split['trainingErrorRate']) || $errorRate < $split['trainingErrorRate']) { $split = [ 'value' => $threshold, 'operator' => $operator, @@ -224,8 +224,7 @@ protected function getBestNominalSplit(array $samples, array $targets, int $col) foreach (['=', '!='] as $operator) { foreach ($distinctVals as $val) { [$errorRate, $prob] = $this->calculateErrorRate($targets, $val, $operator, $values); - - if ($split === [] || $split['trainingErrorRate'] < $errorRate) { + if (!isset($split['trainingErrorRate']) || $split['trainingErrorRate'] < $errorRate) { $split = [ 'value' => $val, 'operator' => $operator, diff --git a/src/Classification/Linear/Perceptron.php b/src/Classification/Linear/Perceptron.php index adc6b36a..36cd4d1f 100644 --- a/src/Classification/Linear/Perceptron.php +++ b/src/Classification/Linear/Perceptron.php @@ -60,11 +60,6 @@ class Perceptron implements Classifier, IncrementalEstimator */ protected $enableEarlyStop = true; - /** - * @var array - */ - protected $costValues = []; - /** * Initalize a perceptron classifier with given learning rate and maximum * number of iterations used while training the perceptron @@ -156,7 +151,7 @@ protected function resetBinary(): void * Trains the perceptron model with Stochastic Gradient Descent optimization * to get the correct set of weights */ - protected function runTraining(array $samples, array $targets) + protected function runTraining(array $samples, array $targets): void { // The cost function is the sum of squares $callback = function ($weights, $sample, $target) { @@ -176,7 +171,7 @@ protected function runTraining(array $samples, array $targets) * Executes a Gradient Descent algorithm for * the given cost function */ - protected function runGradientDescent(array $samples, array $targets, Closure $gradientFunc, bool $isBatch = false) + protected function runGradientDescent(array $samples, array $targets, Closure $gradientFunc, bool $isBatch = false): void { $class = $isBatch ? GD::class : StochasticGD::class; diff --git a/src/Clustering/FuzzyCMeans.php b/src/Clustering/FuzzyCMeans.php index 5e6fa0c0..db42fe94 100644 --- a/src/Clustering/FuzzyCMeans.php +++ b/src/Clustering/FuzzyCMeans.php @@ -108,8 +108,7 @@ public function cluster(array $samples): array $column = array_column($this->membership, $k); arsort($column); reset($column); - $i = key($column); - $cluster = $this->clusters[$i]; + $cluster = $this->clusters[key($column)]; $cluster->attach(new Point($this->samples[$k])); } @@ -152,7 +151,7 @@ protected function generateRandomMembership(int $rows, int $cols): void protected function updateClusters(): void { $dim = $this->space->getDimension(); - if (empty($this->clusters)) { + if (count($this->clusters) === 0) { for ($i = 0; $i < $this->clustersNumber; ++$i) { $this->clusters[] = new Cluster($this->space, array_fill(0, $dim, 0.0)); } @@ -171,11 +170,11 @@ protected function updateClusters(): void } } - protected function getMembershipRowTotal(int $row, int $col, bool $multiply) + protected function getMembershipRowTotal(int $row, int $col, bool $multiply): float { $sum = 0.0; for ($k = 0; $k < $this->sampleCount; ++$k) { - $val = pow($this->membership[$row][$k], $this->fuzziness); + $val = $this->membership[$row][$k] ** $this->fuzziness; if ($multiply) { $val *= $this->samples[$k][$col]; } @@ -211,7 +210,7 @@ protected function getDistanceCalc(int $row, int $col): float $this->samples[$col] ); - $val = pow($dist1 / $dist2, 2.0 / ($this->fuzziness - 1)); + $val = ($dist1 / $dist2) ** 2.0 / ($this->fuzziness - 1); $sum += $val; } @@ -223,7 +222,7 @@ protected function getDistanceCalc(int $row, int $col): float * and all cluster centers. This method returns the summation of all * these distances */ - protected function getObjective() + protected function getObjective(): float { $sum = 0.0; $distance = new Euclidean(); diff --git a/src/Clustering/KMeans/Cluster.php b/src/Clustering/KMeans/Cluster.php index 731d79c4..fa73e4bb 100644 --- a/src/Clustering/KMeans/Cluster.php +++ b/src/Clustering/KMeans/Cluster.php @@ -4,12 +4,11 @@ namespace Phpml\Clustering\KMeans; -use Countable; use IteratorAggregate; use LogicException; use SplObjectStorage; -class Cluster extends Point implements IteratorAggregate, Countable +class Cluster extends Point implements IteratorAggregate { /** * @var Space @@ -32,10 +31,10 @@ public function getPoints(): array { $points = []; foreach ($this->points as $point) { - if (!empty($point->label)) { - $points[$point->label] = $point->toArray(); - } else { + if (count($point->label) === 0) { $points[] = $point->toArray(); + } else { + $points[$point->label] = $point->toArray(); } } @@ -106,10 +105,7 @@ public function getIterator() return $this->points; } - /** - * @return mixed - */ - public function count() + public function count(): int { return count($this->points); } diff --git a/src/Clustering/KMeans/Point.php b/src/Clustering/KMeans/Point.php index 7d41093c..f6ad3f57 100644 --- a/src/Clustering/KMeans/Point.php +++ b/src/Clustering/KMeans/Point.php @@ -6,7 +6,7 @@ use ArrayAccess; -class Point implements ArrayAccess +class Point implements ArrayAccess, \Countable { /** * @var int @@ -23,6 +23,9 @@ class Point implements ArrayAccess */ protected $label; + /** + * @param mixed $label + */ public function __construct(array $coordinates, $label = null) { $this->dimension = count($coordinates); @@ -36,7 +39,7 @@ public function toArray(): array } /** - * @return int|mixed + * @return float|int */ public function getDistanceWith(self $point, bool $precise = true) { @@ -50,9 +53,9 @@ public function getDistanceWith(self $point, bool $precise = true) } /** - * @return mixed + * @param Point[] $points */ - public function getClosest(array $points) + public function getClosest(array $points): ?self { $minPoint = null; @@ -114,4 +117,9 @@ public function offsetUnset($offset): void { unset($this->coordinates[$offset]); } + + public function count(): int + { + return count($this->coordinates); + } } diff --git a/src/Clustering/KMeans/Space.php b/src/Clustering/KMeans/Space.php index aa60eb3c..566d691d 100644 --- a/src/Clustering/KMeans/Space.php +++ b/src/Clustering/KMeans/Space.php @@ -28,6 +28,8 @@ public function __construct(int $dimension) public function toArray(): array { $points = []; + + /** @var Point $point */ foreach ($this as $point) { $points[] = $point->toArray(); } @@ -35,9 +37,12 @@ public function toArray(): array return ['points' => $points]; } + /** + * @param mixed $label + */ public function newPoint(array $coordinates, $label = null): Point { - if (count($coordinates) != $this->dimension) { + if (count($coordinates) !== $this->dimension) { throw new LogicException('('.implode(',', $coordinates).') is not a point of this space'); } @@ -45,7 +50,8 @@ public function newPoint(array $coordinates, $label = null): Point } /** - * @param null $data + * @param mixed $label + * @param mixed $data */ public function addPoint(array $coordinates, $label = null, $data = null): void { @@ -53,8 +59,8 @@ public function addPoint(array $coordinates, $label = null, $data = null): void } /** - * @param Point $point - * @param null $data + * @param object $point + * @param mixed $data */ public function attach($point, $data = null): void { @@ -82,10 +88,16 @@ public function getBoundaries() $min = $this->newPoint(array_fill(0, $this->dimension, null)); $max = $this->newPoint(array_fill(0, $this->dimension, null)); + /** @var self $point */ foreach ($this as $point) { for ($n = 0; $n < $this->dimension; ++$n) { - ($min[$n] > $point[$n] || $min[$n] === null) && $min[$n] = $point[$n]; - ($max[$n] < $point[$n] || $max[$n] === null) && $max[$n] = $point[$n]; + if ($min[$n] === null || $min[$n] > $point[$n]) { + $min[$n] = $point[$n]; + } + + if ($max[$n] === null || $max[$n] < $point[$n]) { + $max[$n] = $point[$n]; + } } } @@ -141,7 +153,10 @@ protected function initializeClusters(int $clustersNumber, int $initMethod): arr return $clusters; } - protected function iterate($clusters): bool + /** + * @param Cluster[] $clusters + */ + protected function iterate(array $clusters): bool { $convergence = true; @@ -164,10 +179,12 @@ protected function iterate($clusters): bool } } + /** @var Cluster $cluster */ foreach ($attach as $cluster) { $cluster->attachAll($attach[$cluster]); } + /** @var Cluster $cluster */ foreach ($detach as $cluster) { $cluster->detachAll($detach[$cluster]); } @@ -179,23 +196,36 @@ protected function iterate($clusters): bool return $convergence; } + /** + * @return Cluster[] + */ protected function initializeKMPPClusters(int $clustersNumber): array { $clusters = []; $this->rewind(); - $clusters[] = new Cluster($this, $this->current()->getCoordinates()); + /** @var Point $current */ + $current = $this->current(); + + $clusters[] = new Cluster($this, $current->getCoordinates()); $distances = new SplObjectStorage(); for ($i = 1; $i < $clustersNumber; ++$i) { $sum = 0; + /** @var Point $point */ foreach ($this as $point) { - $distance = $point->getDistanceWith($point->getClosest($clusters)); + $closest = $point->getClosest($clusters); + if ($closest === null) { + continue; + } + + $distance = $point->getDistanceWith($closest); $sum += $distances[$point] = $distance; } $sum = random_int(0, (int) $sum); + /** @var Point $point */ foreach ($this as $point) { $sum -= $distances[$point]; @@ -212,6 +242,9 @@ protected function initializeKMPPClusters(int $clustersNumber): array return $clusters; } + /** + * @return Cluster[] + */ private function initializeRandomClusters(int $clustersNumber): array { $clusters = []; diff --git a/src/CrossValidation/Split.php b/src/CrossValidation/Split.php index bffb59aa..e9d401c4 100644 --- a/src/CrossValidation/Split.php +++ b/src/CrossValidation/Split.php @@ -60,7 +60,7 @@ public function getTestLabels(): array return $this->testLabels; } - abstract protected function splitDataset(Dataset $dataset, float $testSize); + abstract protected function splitDataset(Dataset $dataset, float $testSize): void; protected function seedGenerator(?int $seed = null): void { diff --git a/src/CrossValidation/StratifiedRandomSplit.php b/src/CrossValidation/StratifiedRandomSplit.php index 85dd5d13..4974d4ce 100644 --- a/src/CrossValidation/StratifiedRandomSplit.php +++ b/src/CrossValidation/StratifiedRandomSplit.php @@ -27,6 +27,7 @@ private function splitByTarget(Dataset $dataset): array $samples = $dataset->getSamples(); $uniqueTargets = array_unique($targets); + /** @var array $split */ $split = array_combine($uniqueTargets, array_fill(0, count($uniqueTargets), [])); foreach ($samples as $key => $sample) { diff --git a/src/Dataset/CsvDataset.php b/src/Dataset/CsvDataset.php index 631c6a6e..cdd387fb 100644 --- a/src/Dataset/CsvDataset.php +++ b/src/Dataset/CsvDataset.php @@ -29,14 +29,14 @@ public function __construct(string $filepath, int $features, bool $headingRow = if ($headingRow) { $data = fgetcsv($handle, $maxLineLength, $delimiter); - $this->columnNames = array_slice($data, 0, $features); + $this->columnNames = array_slice((array) $data, 0, $features); } else { $this->columnNames = range(0, $features - 1); } $samples = $targets = []; while (($data = fgetcsv($handle, $maxLineLength, $delimiter)) !== false) { - $samples[] = array_slice($data, 0, $features); + $samples[] = array_slice((array) $data, 0, $features); $targets[] = $data[$features]; } diff --git a/src/Dataset/SvmDataset.php b/src/Dataset/SvmDataset.php index e5f2a86e..334ec6c2 100644 --- a/src/Dataset/SvmDataset.php +++ b/src/Dataset/SvmDataset.php @@ -23,8 +23,8 @@ private static function readProblem(string $filePath): array $samples = []; $targets = []; $maxIndex = 0; - while (($line = fgets($handle)) !== false) { - [$sample, $target, $maxIndex] = self::processLine($line, $maxIndex); + while (false !== $line = fgets($handle)) { + [$sample, $target, $maxIndex] = self::processLine((string) $line, $maxIndex); $samples[] = $sample; $targets[] = $target; } @@ -38,6 +38,9 @@ private static function readProblem(string $filePath): array return [$samples, $targets]; } + /** + * @return resource + */ private static function openFile(string $filePath) { if (!file_exists($filePath)) { diff --git a/src/DimensionReduction/KernelPCA.php b/src/DimensionReduction/KernelPCA.php index 29deb4c4..41c7340f 100644 --- a/src/DimensionReduction/KernelPCA.php +++ b/src/DimensionReduction/KernelPCA.php @@ -5,7 +5,6 @@ namespace Phpml\DimensionReduction; use Closure; -use Exception; use Phpml\Exception\InvalidArgumentException; use Phpml\Exception\InvalidOperationException; use Phpml\Math\Distance\Euclidean; @@ -59,8 +58,7 @@ class KernelPCA extends PCA */ public function __construct(int $kernel = self::KERNEL_RBF, ?float $totalVariance = null, ?int $numFeatures = null, ?float $gamma = null) { - $availableKernels = [self::KERNEL_RBF, self::KERNEL_SIGMOID, self::KERNEL_LAPLACIAN, self::KERNEL_LINEAR]; - if (!in_array($kernel, $availableKernels, true)) { + if (!in_array($kernel, [self::KERNEL_RBF, self::KERNEL_SIGMOID, self::KERNEL_LAPLACIAN, self::KERNEL_LINEAR], true)) { throw new InvalidArgumentException('KernelPCA can be initialized with the following kernels only: Linear, RBF, Sigmoid and Laplacian'); } @@ -190,7 +188,7 @@ protected function getKernel(): Closure return function ($x, $y) { $res = Matrix::dot($x, $y)[0] + 1.0; - return tanh($this->gamma * $res); + return tanh((float) $this->gamma * $res); }; case self::KERNEL_LAPLACIAN: @@ -203,7 +201,7 @@ protected function getKernel(): Closure default: // Not reached - throw new Exception(sprintf('KernelPCA initialized with invalid kernel: %d', $this->kernel)); + throw new InvalidArgumentException(sprintf('KernelPCA initialized with invalid kernel: %d', $this->kernel)); } } diff --git a/src/DimensionReduction/PCA.php b/src/DimensionReduction/PCA.php index fa651da9..5556558f 100644 --- a/src/DimensionReduction/PCA.php +++ b/src/DimensionReduction/PCA.php @@ -115,7 +115,7 @@ protected function calculateMeans(array $data, int $n): void */ protected function normalize(array $data, int $n): array { - if (empty($this->means)) { + if (count($this->means) === 0) { $this->calculateMeans($data, $n); } diff --git a/src/Estimator.php b/src/Estimator.php index b4268896..a0541089 100644 --- a/src/Estimator.php +++ b/src/Estimator.php @@ -6,7 +6,7 @@ interface Estimator { - public function train(array $samples, array $targets); + public function train(array $samples, array $targets): void; /** * @return mixed diff --git a/src/FeatureExtraction/TfIdfTransformer.php b/src/FeatureExtraction/TfIdfTransformer.php index 4a478c30..d1ac35db 100644 --- a/src/FeatureExtraction/TfIdfTransformer.php +++ b/src/FeatureExtraction/TfIdfTransformer.php @@ -15,7 +15,7 @@ class TfIdfTransformer implements Transformer public function __construct(array $samples = []) { - if (!empty($samples)) { + if (count($samples) > 0) { $this->fit($samples); } } diff --git a/src/FeatureSelection/SelectKBest.php b/src/FeatureSelection/SelectKBest.php index b0ff6449..36b4245a 100644 --- a/src/FeatureSelection/SelectKBest.php +++ b/src/FeatureSelection/SelectKBest.php @@ -43,7 +43,7 @@ public function __construct(int $k = 10, ?ScoringFunction $scoringFunction = nul public function fit(array $samples, ?array $targets = null): void { - if ($targets === null || empty($targets)) { + if ($targets === null || count($targets) === 0) { throw new InvalidArgumentException('The array has zero elements'); } diff --git a/src/Helper/OneVsRest.php b/src/Helper/OneVsRest.php index e68b10d0..691fb643 100644 --- a/src/Helper/OneVsRest.php +++ b/src/Helper/OneVsRest.php @@ -51,18 +51,13 @@ public function reset(): void protected function trainByLabel(array $samples, array $targets, array $allLabels = []): void { // Overwrites the current value if it exist. $allLabels must be provided for each partialTrain run. - if (!empty($allLabels)) { - $this->allLabels = $allLabels; - } else { - $this->allLabels = array_keys(array_count_values($targets)); - } - + $this->allLabels = count($allLabels) === 0 ? array_keys(array_count_values($targets)) : $allLabels; sort($this->allLabels, SORT_STRING); // If there are only two targets, then there is no need to perform OvR - if (count($this->allLabels) == 2) { + if (count($this->allLabels) === 2) { // Init classifier if required. - if (empty($this->classifiers)) { + if (count($this->classifiers) === 0) { $this->classifiers[0] = $this->getClassifierCopy(); } @@ -72,7 +67,7 @@ protected function trainByLabel(array $samples, array $targets, array $allLabels foreach ($this->allLabels as $label) { // Init classifier if required. - if (empty($this->classifiers[$label])) { + if (!isset($this->classifiers[$label])) { $this->classifiers[$label] = $this->getClassifierCopy(); } @@ -92,10 +87,8 @@ protected function trainByLabel(array $samples, array $targets, array $allLabels /** * Returns an instance of the current class after cleaning up OneVsRest stuff. - * - * @return Classifier|OneVsRest */ - protected function getClassifierCopy() + protected function getClassifierCopy(): Classifier { // Clone the current classifier, so that // we don't mess up its variables while training @@ -111,7 +104,7 @@ protected function getClassifierCopy() */ protected function predictSample(array $sample) { - if (count($this->allLabels) == 2) { + if (count($this->allLabels) === 2) { return $this->classifiers[0]->predictSampleBinary($sample); } diff --git a/src/Helper/Optimizer/ConjugateGradient.php b/src/Helper/Optimizer/ConjugateGradient.php index 67210abd..d7c064f1 100644 --- a/src/Helper/Optimizer/ConjugateGradient.php +++ b/src/Helper/Optimizer/ConjugateGradient.php @@ -91,7 +91,7 @@ protected function cost(array $theta): float { [$cost] = parent::gradient($theta); - return array_sum($cost) / $this->sampleCount; + return array_sum($cost) / (int) $this->sampleCount; } /** diff --git a/src/Helper/Optimizer/GD.php b/src/Helper/Optimizer/GD.php index 11577c9d..28320329 100644 --- a/src/Helper/Optimizer/GD.php +++ b/src/Helper/Optimizer/GD.php @@ -5,6 +5,7 @@ namespace Phpml\Helper\Optimizer; use Closure; +use Phpml\Exception\InvalidOperationException; /** * Batch version of Gradient Descent to optimize the weights @@ -59,6 +60,10 @@ protected function gradient(array $theta): array $gradient = []; $totalPenalty = 0; + if ($this->gradientCb === null) { + throw new InvalidOperationException('Gradient callback is not defined'); + } + foreach ($this->samples as $index => $sample) { $target = $this->targets[$index]; diff --git a/src/Helper/Optimizer/Optimizer.php b/src/Helper/Optimizer/Optimizer.php index dba0cd06..99a82ab3 100644 --- a/src/Helper/Optimizer/Optimizer.php +++ b/src/Helper/Optimizer/Optimizer.php @@ -37,9 +37,9 @@ public function __construct(int $dimensions) } } - public function setTheta(array $theta) + public function setTheta(array $theta): self { - if (count($theta) != $this->dimensions) { + if (count($theta) !== $this->dimensions) { throw new InvalidArgumentException(sprintf('Number of values in the weights array should be %s', $this->dimensions)); } @@ -52,5 +52,5 @@ public function setTheta(array $theta) * Executes the optimization with the given samples & targets * and returns the weights */ - abstract public function runOptimization(array $samples, array $targets, Closure $gradientCb); + abstract public function runOptimization(array $samples, array $targets, Closure $gradientCb): array; } diff --git a/src/Helper/Optimizer/StochasticGD.php b/src/Helper/Optimizer/StochasticGD.php index c4fabd33..9927c3f2 100644 --- a/src/Helper/Optimizer/StochasticGD.php +++ b/src/Helper/Optimizer/StochasticGD.php @@ -6,6 +6,7 @@ use Closure; use Phpml\Exception\InvalidArgumentException; +use Phpml\Exception\InvalidOperationException; /** * Stochastic Gradient Descent optimization method @@ -34,7 +35,7 @@ class StochasticGD extends Optimizer * * @var \Closure|null */ - protected $gradientCb = null; + protected $gradientCb; /** * Maximum number of iterations used to train the model @@ -89,9 +90,9 @@ public function __construct(int $dimensions) $this->dimensions = $dimensions; } - public function setTheta(array $theta) + public function setTheta(array $theta): Optimizer { - if (count($theta) != $this->dimensions + 1) { + if (count($theta) !== $this->dimensions + 1) { throw new InvalidArgumentException(sprintf('Number of values in the weights array should be %s', $this->dimensions + 1)); } @@ -156,7 +157,7 @@ public function setMaxIterations(int $maxIterations) * The cost function to minimize and the gradient of the function are to be * handled by the callback function provided as the third parameter of the method. */ - public function runOptimization(array $samples, array $targets, Closure $gradientCb): ?array + public function runOptimization(array $samples, array $targets, Closure $gradientCb): array { $this->samples = $samples; $this->targets = $targets; @@ -175,7 +176,7 @@ public function runOptimization(array $samples, array $targets, Closure $gradien // Save the best theta in the "pocket" so that // any future set of theta worse than this will be disregarded - if ($bestTheta == null || $cost <= $bestScore) { + if ($bestTheta === null || $cost <= $bestScore) { $bestTheta = $theta; $bestScore = $cost; } @@ -210,6 +211,10 @@ protected function updateTheta(): float $jValue = 0.0; $theta = $this->theta; + if ($this->gradientCb === null) { + throw new InvalidOperationException('Gradient callback is not defined'); + } + foreach ($this->samples as $index => $sample) { $target = $this->targets[$index]; @@ -254,7 +259,7 @@ function ($w1, $w2) { // Check if the last two cost values are almost the same $costs = array_slice($this->costValues, -2); - if (count($costs) == 2 && abs($costs[1] - $costs[0]) < $this->threshold) { + if (count($costs) === 2 && abs($costs[1] - $costs[0]) < $this->threshold) { return true; } diff --git a/src/IncrementalEstimator.php b/src/IncrementalEstimator.php index e356be01..600bfbb2 100644 --- a/src/IncrementalEstimator.php +++ b/src/IncrementalEstimator.php @@ -6,5 +6,5 @@ interface IncrementalEstimator { - public function partialTrain(array $samples, array $targets, array $labels = []); + public function partialTrain(array $samples, array $targets, array $labels = []): void; } diff --git a/src/Math/Comparison.php b/src/Math/Comparison.php index d9ad00cc..d1330a98 100644 --- a/src/Math/Comparison.php +++ b/src/Math/Comparison.php @@ -9,6 +9,9 @@ class Comparison { /** + * @param mixed $a + * @param mixed $b + * * @throws InvalidArgumentException */ public static function compare($a, $b, string $operator): bool diff --git a/src/Math/LinearAlgebra/EigenvalueDecomposition.php b/src/Math/LinearAlgebra/EigenvalueDecomposition.php index 3d7484dc..e0f16397 100644 --- a/src/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -3,25 +3,25 @@ declare(strict_types=1); /** - * Class to obtain eigenvalues and eigenvectors of a real matrix. + * Class to obtain eigenvalues and eigenvectors of a real matrix. * - * If A is symmetric, then A = V*D*V' where the eigenvalue matrix D - * is diagonal and the eigenvector matrix V is orthogonal (i.e. - * A = V.times(D.times(V.transpose())) and V.times(V.transpose()) - * equals the identity matrix). + * If A is symmetric, then A = V*D*V' where the eigenvalue matrix D + * is diagonal and the eigenvector matrix V is orthogonal (i.e. + * A = V.times(D.times(V.transpose())) and V.times(V.transpose()) + * equals the identity matrix). * - * If A is not symmetric, then the eigenvalue matrix D is block diagonal - * with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues, - * lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The - * columns of V represent the eigenvectors in the sense that A*V = V*D, - * i.e. A.times(V) equals V.times(D). The matrix V may be badly - * conditioned, or even singular, so the validity of the equation - * A = V*D*inverse(V) depends upon V.cond(). + * If A is not symmetric, then the eigenvalue matrix D is block diagonal + * with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues, + * lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The + * columns of V represent the eigenvectors in the sense that A*V = V*D, + * i.e. A.times(V) equals V.times(D). The matrix V may be badly + * conditioned, or even singular, so the validity of the equation + * A = V*D*inverse(V) depends upon V.cond(). * - * @author Paul Meagher - * @license PHP v3.0 + * @author Paul Meagher + * @license PHP v3.0 * - * @version 1.1 + * @version 1.1 * * Slightly changed to adapt the original code to PHP-ML library * @date 2017/04/11 @@ -36,83 +36,79 @@ class EigenvalueDecomposition { /** - * Row and column dimension (square matrix). + * Row and column dimension (square matrix). * - * @var int + * @var int */ private $n; /** - * Internal symmetry flag. + * Arrays for internal storage of eigenvalues. * - * @var bool + * @var array */ - private $symmetric; + private $d = []; /** - * Arrays for internal storage of eigenvalues. - * - * @var array + * @var array */ - private $d = []; - private $e = []; /** - * Array for internal storage of eigenvectors. + * Array for internal storage of eigenvectors. * - * @var array + * @var array */ private $V = []; /** - * Array for internal storage of nonsymmetric Hessenberg form. + * Array for internal storage of nonsymmetric Hessenberg form. * - * @var array + * @var array */ private $H = []; /** - * Working storage for nonsymmetric algorithm. + * Working storage for nonsymmetric algorithm. * - * @var array + * @var array */ private $ort = []; /** - * Used for complex scalar division. + * Used for complex scalar division. * - * @var float + * @var float */ private $cdivr; + /** + * @var float + */ private $cdivi; - private $A; - /** - * Constructor: Check for symmetry, then construct the eigenvalue decomposition + * Constructor: Check for symmetry, then construct the eigenvalue decomposition */ - public function __construct(array $Arg) + public function __construct(array $arg) { - $this->A = $Arg; - $this->n = count($Arg[0]); - $this->symmetric = true; + $this->n = count($arg[0]); + $symmetric = true; - for ($j = 0; ($j < $this->n) & $this->symmetric; ++$j) { - for ($i = 0; ($i < $this->n) & $this->symmetric; ++$i) { - $this->symmetric = ($this->A[$i][$j] == $this->A[$j][$i]); + for ($j = 0; ($j < $this->n) & $symmetric; ++$j) { + for ($i = 0; ($i < $this->n) & $symmetric; ++$i) { + $symmetric = $arg[$i][$j] == $arg[$j][$i]; } } - if ($this->symmetric) { - $this->V = $this->A; + if ($symmetric) { + $this->V = $arg; // Tridiagonalize. $this->tred2(); // Diagonalize. $this->tql2(); } else { - $this->H = $this->A; + $this->H = $arg; $this->ort = []; // Reduce to Hessenberg form. $this->orthes(); @@ -148,7 +144,7 @@ public function getEigenvectors(): array } /** - * Return the real parts of the eigenvalues
+ * Return the real parts of the eigenvalues
* d = real(diag(D)); */ public function getRealEigenvalues(): array @@ -157,7 +153,7 @@ public function getRealEigenvalues(): array } /** - * Return the imaginary parts of the eigenvalues
+ * Return the imaginary parts of the eigenvalues
* d = imag(diag(D)) */ public function getImagEigenvalues(): array @@ -166,7 +162,7 @@ public function getImagEigenvalues(): array } /** - * Return the block diagonal eigenvalue matrix + * Return the block diagonal eigenvalue matrix */ public function getDiagonalEigenvalues(): array { @@ -187,7 +183,7 @@ public function getDiagonalEigenvalues(): array } /** - * Symmetric Householder reduction to tridiagonal form. + * Symmetric Householder reduction to tridiagonal form. */ private function tred2(): void { @@ -308,12 +304,12 @@ private function tred2(): void } /** - * Symmetric tridiagonal QL algorithm. + * Symmetric tridiagonal QL algorithm. * - * This is derived from the Algol procedures tql2, by - * Bowdler, Martin, Reinsch, and Wilkinson, Handbook for - * Auto. Comp., Vol.ii-Linear Algebra, and the corresponding - * Fortran subroutine in EISPACK. + * This is derived from the Algol procedures tql2, by + * Bowdler, Martin, Reinsch, and Wilkinson, Handbook for + * Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + * Fortran subroutine in EISPACK. */ private function tql2(): void { @@ -341,10 +337,7 @@ private function tql2(): void // If m == l, $this->d[l] is an eigenvalue, // otherwise, iterate. if ($m > $l) { - $iter = 0; do { - // Could check iteration count here. - ++$iter; // Compute implicit shift $g = $this->d[$l]; $p = ($this->d[$l + 1] - $g) / (2.0 * $this->e[$l]); @@ -423,12 +416,12 @@ private function tql2(): void } /** - * Nonsymmetric reduction to Hessenberg form. + * Nonsymmetric reduction to Hessenberg form. * - * This is derived from the Algol procedures orthes and ortran, - * by Martin and Wilkinson, Handbook for Auto. Comp., - * Vol.ii-Linear Algebra, and the corresponding - * Fortran subroutines in EISPACK. + * This is derived from the Algol procedures orthes and ortran, + * by Martin and Wilkinson, Handbook for Auto. Comp., + * Vol.ii-Linear Algebra, and the corresponding + * Fortran subroutines in EISPACK. */ private function orthes(): void { @@ -541,12 +534,12 @@ private function cdiv($xr, $xi, $yr, $yi): void } /** - * Nonsymmetric reduction from Hessenberg to real Schur form. + * Nonsymmetric reduction from Hessenberg to real Schur form. * - * Code is derived from the Algol procedure hqr2, - * by Martin and Wilkinson, Handbook for Auto. Comp., - * Vol.ii-Linear Algebra, and the corresponding - * Fortran subroutine in EISPACK. + * Code is derived from the Algol procedure hqr2, + * by Martin and Wilkinson, Handbook for Auto. Comp., + * Vol.ii-Linear Algebra, and the corresponding + * Fortran subroutine in EISPACK. */ private function hqr2(): void { @@ -911,7 +904,7 @@ private function hqr2(): void $y = $this->H[$i + 1][$i]; $vr = ($this->d[$i] - $p) * ($this->d[$i] - $p) + $this->e[$i] * $this->e[$i] - $q * $q; $vi = ($this->d[$i] - $p) * 2.0 * $q; - if ($vr == 0.0 & $vi == 0.0) { + if ($vr == 0.0 && $vi == 0.0) { $vr = $eps * $norm * (abs($w) + abs($q) + abs($x) + abs($y) + abs($z)); } @@ -943,7 +936,7 @@ private function hqr2(): void // Vectors of isolated roots for ($i = 0; $i < $nn; ++$i) { - if ($i < $low | $i > $high) { + if ($i < $low || $i > $high) { for ($j = $i; $j < $nn; ++$j) { $this->V[$i][$j] = $this->H[$i][$j]; } diff --git a/src/Math/LinearAlgebra/LUDecomposition.php b/src/Math/LinearAlgebra/LUDecomposition.php index f9e7300a..61f7c3f2 100644 --- a/src/Math/LinearAlgebra/LUDecomposition.php +++ b/src/Math/LinearAlgebra/LUDecomposition.php @@ -80,7 +80,7 @@ class LUDecomposition */ public function __construct(Matrix $A) { - if ($A->getRows() != $A->getColumns()) { + if ($A->getRows() !== $A->getColumns()) { throw new MatrixException('Matrix is not square matrix'); } @@ -118,7 +118,7 @@ public function __construct(Matrix $A) // Find pivot and exchange if necessary. $p = $j; for ($i = $j + 1; $i < $this->m; ++$i) { - if (abs($LUcolj[$i]) > abs($LUcolj[$p])) { + if (abs($LUcolj[$i] ?? 0) > abs($LUcolj[$p] ?? 0)) { $p = $i; } } @@ -204,7 +204,7 @@ public function getPivot(): array * * @see getPivot */ - public function getDoublePivot() + public function getDoublePivot(): array { return $this->getPivot(); } diff --git a/src/Math/Matrix.php b/src/Math/Matrix.php index 840d7469..a511f558 100644 --- a/src/Math/Matrix.php +++ b/src/Math/Matrix.php @@ -89,7 +89,7 @@ public function getColumns(): int /** * @throws MatrixException */ - public function getColumnValues($column): array + public function getColumnValues(int $column): array { if ($column >= $this->columns) { throw new MatrixException('Column out of range'); @@ -125,7 +125,7 @@ public function isSquare(): bool public function transpose(): self { - if ($this->rows == 1) { + if ($this->rows === 1) { $matrix = array_map(function ($el) { return [$el]; }, $this->matrix[0]); @@ -138,7 +138,7 @@ public function transpose(): self public function multiply(self $matrix): self { - if ($this->columns != $matrix->getRows()) { + if ($this->columns !== $matrix->getRows()) { throw new InvalidArgumentException('Inconsistent matrix supplied'); } @@ -166,6 +166,9 @@ public function multiply(self $matrix): self return new self($product, false); } + /** + * @param float|int $value + */ public function divideByScalar($value): self { $newMatrix = []; @@ -178,6 +181,9 @@ public function divideByScalar($value): self return new self($newMatrix, false); } + /** + * @param float|int $value + */ public function multiplyByScalar($value): self { $newMatrix = []; diff --git a/src/Math/Product.php b/src/Math/Product.php index ab1e75a9..78f36931 100644 --- a/src/Math/Product.php +++ b/src/Math/Product.php @@ -14,7 +14,7 @@ public static function scalar(array $a, array $b) $product = 0; foreach ($a as $index => $value) { if (is_numeric($value) && is_numeric($b[$index])) { - $product += $value * $b[$index]; + $product += (float) $value * (float) $b[$index]; } } diff --git a/src/Math/Set.php b/src/Math/Set.php index fab2923a..b22d2f81 100644 --- a/src/Math/Set.php +++ b/src/Math/Set.php @@ -131,7 +131,7 @@ public function contains($element): bool */ public function containsAll(array $elements): bool { - return !array_diff($elements, $this->elements); + return count(array_diff($elements, $this->elements)) === 0; } /** @@ -149,7 +149,7 @@ public function getIterator(): ArrayIterator public function isEmpty(): bool { - return $this->cardinality() == 0; + return $this->cardinality() === 0; } public function cardinality(): int diff --git a/src/Math/Statistic/ANOVA.php b/src/Math/Statistic/ANOVA.php index 0118347e..f7b01c7e 100644 --- a/src/Math/Statistic/ANOVA.php +++ b/src/Math/Statistic/ANOVA.php @@ -31,7 +31,7 @@ public static function oneWayF(array $samples): array $samplesPerClass = array_map(function (array $class): int { return count($class); }, $samples); - $allSamples = array_sum($samplesPerClass); + $allSamples = (int) array_sum($samplesPerClass); $ssAllSamples = self::sumOfSquaresPerFeature($samples); $sumSamples = self::sumOfFeaturesPerClass($samples); $squareSumSamples = self::sumOfSquares($sumSamples); diff --git a/src/Math/Statistic/Covariance.php b/src/Math/Statistic/Covariance.php index 4ed07764..52cac5e4 100644 --- a/src/Math/Statistic/Covariance.php +++ b/src/Math/Statistic/Covariance.php @@ -15,11 +15,11 @@ class Covariance */ public static function fromXYArrays(array $x, array $y, bool $sample = true, ?float $meanX = null, ?float $meanY = null): float { - if (empty($x) || empty($y)) { + $n = count($x); + if ($n === 0 || count($y) === 0) { throw new InvalidArgumentException('The array has zero elements'); } - $n = count($x); if ($sample && $n === 1) { throw new InvalidArgumentException('The array must have at least 2 elements'); } @@ -53,7 +53,7 @@ public static function fromXYArrays(array $x, array $y, bool $sample = true, ?fl */ public static function fromDataset(array $data, int $i, int $k, bool $sample = true, ?float $meanX = null, ?float $meanY = null): float { - if (empty($data)) { + if (count($data) === 0) { throw new InvalidArgumentException('The array has zero elements'); } @@ -87,7 +87,7 @@ public static function fromDataset(array $data, int $i, int $k, bool $sample = t // with a slight cost of CPU utilization. $sum = 0.0; foreach ($data as $row) { - $val = []; + $val = [0, 0]; foreach ($row as $index => $col) { if ($index == $i) { $val[0] = $col - $meanX; diff --git a/src/Math/Statistic/Mean.php b/src/Math/Statistic/Mean.php index 6b6d555a..2ae55aa8 100644 --- a/src/Math/Statistic/Mean.php +++ b/src/Math/Statistic/Mean.php @@ -58,7 +58,7 @@ public static function mode(array $numbers) */ private static function checkArrayLength(array $array): void { - if (empty($array)) { + if (count($array) === 0) { throw new InvalidArgumentException('The array has zero elements'); } } diff --git a/src/Math/Statistic/StandardDeviation.php b/src/Math/Statistic/StandardDeviation.php index f1eae8a8..170a9ee7 100644 --- a/src/Math/Statistic/StandardDeviation.php +++ b/src/Math/Statistic/StandardDeviation.php @@ -13,12 +13,11 @@ class StandardDeviation */ public static function population(array $numbers, bool $sample = true): float { - if (empty($numbers)) { + $n = count($numbers); + if ($n === 0) { throw new InvalidArgumentException('The array has zero elements'); } - $n = count($numbers); - if ($sample && $n === 1) { throw new InvalidArgumentException('The array must have at least 2 elements'); } @@ -33,7 +32,7 @@ public static function population(array $numbers, bool $sample = true): float --$n; } - return sqrt((float) ($carry / $n)); + return sqrt($carry / $n); } /** @@ -44,7 +43,7 @@ public static function population(array $numbers, bool $sample = true): float */ public static function sumOfSquares(array $numbers): float { - if (empty($numbers)) { + if (count($numbers) === 0) { throw new InvalidArgumentException('The array has zero elements'); } diff --git a/src/Metric/ClassificationReport.php b/src/Metric/ClassificationReport.php index 969dcc6d..6263a525 100644 --- a/src/Metric/ClassificationReport.php +++ b/src/Metric/ClassificationReport.php @@ -142,9 +142,9 @@ private function computeAverage(int $average): void private function computeMicroAverage(): void { - $truePositive = array_sum($this->truePositive); - $falsePositive = array_sum($this->falsePositive); - $falseNegative = array_sum($this->falseNegative); + $truePositive = (int) array_sum($this->truePositive); + $falsePositive = (int) array_sum($this->falsePositive); + $falseNegative = (int) array_sum($this->falseNegative); $precision = $this->computePrecision($truePositive, $falsePositive); $recall = $this->computeRecall($truePositive, $falseNegative); @@ -227,6 +227,6 @@ private static function getLabelIndexedArray(array $actualLabels, array $predict $labels = array_values(array_unique(array_merge($actualLabels, $predictedLabels))); sort($labels); - return array_combine($labels, array_fill(0, count($labels), 0)); + return (array) array_combine($labels, array_fill(0, count($labels), 0)); } } diff --git a/src/Metric/ConfusionMatrix.php b/src/Metric/ConfusionMatrix.php index 5fd3ac59..5b8021cb 100644 --- a/src/Metric/ConfusionMatrix.php +++ b/src/Metric/ConfusionMatrix.php @@ -8,13 +8,13 @@ class ConfusionMatrix { public static function compute(array $actualLabels, array $predictedLabels, array $labels = []): array { - $labels = !empty($labels) ? array_flip($labels) : self::getUniqueLabels($actualLabels); + $labels = count($labels) === 0 ? self::getUniqueLabels($actualLabels) : array_flip($labels); $matrix = self::generateMatrixWithZeros($labels); foreach ($actualLabels as $index => $actual) { $predicted = $predictedLabels[$index]; - if (!isset($labels[$actual]) || !isset($labels[$predicted])) { + if (!isset($labels[$actual], $labels[$predicted])) { continue; } diff --git a/src/ModelManager.php b/src/ModelManager.php index 36e8e2c1..057e0ead 100644 --- a/src/ModelManager.php +++ b/src/ModelManager.php @@ -16,7 +16,7 @@ public function saveToFile(Estimator $estimator, string $filepath): void } $serialized = serialize($estimator); - if (empty($serialized)) { + if (!isset($serialized[0])) { throw new SerializeException(sprintf('Class "%s" can not be serialized.', gettype($estimator))); } @@ -32,7 +32,7 @@ public function restoreFromFile(string $filepath): Estimator throw new FileException(sprintf('File "%s" can\'t be open.', basename($filepath))); } - $object = unserialize(file_get_contents($filepath)); + $object = unserialize((string) file_get_contents($filepath), [Estimator::class]); if ($object === false) { throw new SerializeException(sprintf('"%s" can not be unserialized.', basename($filepath))); } diff --git a/src/NeuralNetwork/Network.php b/src/NeuralNetwork/Network.php index c2248a64..0b0ce651 100644 --- a/src/NeuralNetwork/Network.php +++ b/src/NeuralNetwork/Network.php @@ -13,7 +13,7 @@ public function setInput($input): self; public function getOutput(): array; - public function addLayer(Layer $layer); + public function addLayer(Layer $layer): void; /** * @return Layer[] diff --git a/src/NeuralNetwork/Network/MultilayerPerceptron.php b/src/NeuralNetwork/Network/MultilayerPerceptron.php index 8ff49bc6..7fe08e14 100644 --- a/src/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/NeuralNetwork/Network/MultilayerPerceptron.php @@ -61,7 +61,7 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, */ public function __construct(int $inputLayerFeatures, array $hiddenLayers, array $classes, int $iterations = 10000, ?ActivationFunction $activationFunction = null, float $learningRate = 1) { - if (empty($hiddenLayers)) { + if (count($hiddenLayers) === 0) { throw new InvalidArgumentException('Provide at least 1 hidden layer'); } @@ -95,7 +95,7 @@ public function train(array $samples, array $targets): void */ public function partialTrain(array $samples, array $targets, array $classes = []): void { - if (!empty($classes) && array_values($classes) !== $this->classes) { + if (count($classes) > 0 && array_values($classes) !== $this->classes) { // We require the list of classes in the constructor. throw new InvalidArgumentException( 'The provided classes don\'t match the classes provided in the constructor' @@ -126,7 +126,7 @@ public function getOutput(): array /** * @param mixed $target */ - abstract protected function trainSample(array $sample, $target); + abstract protected function trainSample(array $sample, $target): void; /** * @return mixed diff --git a/src/NeuralNetwork/Node/Neuron/Synapse.php b/src/NeuralNetwork/Node/Neuron/Synapse.php index 08899bf7..0a6e0f8c 100644 --- a/src/NeuralNetwork/Node/Neuron/Synapse.php +++ b/src/NeuralNetwork/Node/Neuron/Synapse.php @@ -49,6 +49,6 @@ public function getNode(): Node protected function generateRandomWeight(): float { - return 1 / random_int(5, 25) * (random_int(0, 1) ? -1 : 1); + return (1 / random_int(5, 25) * random_int(0, 1)) > 0 ? -1 : 1; } } diff --git a/src/NeuralNetwork/Training.php b/src/NeuralNetwork/Training.php deleted file mode 100644 index e699c470..00000000 --- a/src/NeuralNetwork/Training.php +++ /dev/null @@ -1,10 +0,0 @@ -model = file_get_contents($modelFileName); + $this->model = (string) file_get_contents($modelFileName); unlink($modelFileName); } @@ -241,7 +241,7 @@ private function runSvmPredict(array $samples, bool $probabilityEstimates): stri unlink($testSetFileName); unlink($modelFileName); - $predictions = file_get_contents($outputFileName); + $predictions = (string) file_get_contents($outputFileName); unlink($outputFileName); diff --git a/src/Tokenization/WhitespaceTokenizer.php b/src/Tokenization/WhitespaceTokenizer.php index 5b071b8e..4c0ae60d 100644 --- a/src/Tokenization/WhitespaceTokenizer.php +++ b/src/Tokenization/WhitespaceTokenizer.php @@ -4,10 +4,17 @@ namespace Phpml\Tokenization; +use Phpml\Exception\InvalidArgumentException; + class WhitespaceTokenizer implements Tokenizer { public function tokenize(string $text): array { - return preg_split('/[\pZ\pC]+/u', $text, -1, PREG_SPLIT_NO_EMPTY); + $substrings = preg_split('/[\pZ\pC]+/u', $text, -1, PREG_SPLIT_NO_EMPTY); + if ($substrings === false) { + throw new InvalidArgumentException('preg_split failed on: '.$text); + } + + return $substrings; } } diff --git a/tests/Association/AprioriTest.php b/tests/Association/AprioriTest.php index 81a6ce68..bec58f90 100644 --- a/tests/Association/AprioriTest.php +++ b/tests/Association/AprioriTest.php @@ -11,6 +11,9 @@ class AprioriTest extends TestCase { + /** + * @var array + */ private $sampleGreek = [ ['alpha', 'beta', 'epsilon'], ['alpha', 'beta', 'theta'], @@ -18,6 +21,9 @@ class AprioriTest extends TestCase ['alpha', 'beta', 'theta'], ]; + /** + * @var array + */ private $sampleChars = [ ['E', 'D', 'N', 'E+N', 'EN'], ['E', 'R', 'N', 'E+R', 'E+N', 'ER', 'EN'], @@ -31,6 +37,9 @@ class AprioriTest extends TestCase ['N'], ]; + /** + * @var array + */ private $sampleBasket = [ [1, 2, 3, 4], [1, 2, 4], @@ -48,16 +57,16 @@ public function testGreek(): void $predicted = $apriori->predict([['alpha', 'epsilon'], ['beta', 'theta']]); - $this->assertCount(2, $predicted); - $this->assertEquals([['beta']], $predicted[0]); - $this->assertEquals([['alpha']], $predicted[1]); + self::assertCount(2, $predicted); + self::assertEquals([['beta']], $predicted[0]); + self::assertEquals([['alpha']], $predicted[1]); } public function testPowerSet(): void { $apriori = new Apriori(); - $this->assertCount(8, self::invoke($apriori, 'powerSet', [['a', 'b', 'c']])); + self::assertCount(8, self::invoke($apriori, 'powerSet', [['a', 'b', 'c']])); } public function testApriori(): void @@ -67,13 +76,13 @@ public function testApriori(): void $L = $apriori->apriori(); - $this->assertCount(4, $L[2]); - $this->assertTrue(self::invoke($apriori, 'contains', [$L[2], [1, 2]])); - $this->assertFalse(self::invoke($apriori, 'contains', [$L[2], [1, 3]])); - $this->assertFalse(self::invoke($apriori, 'contains', [$L[2], [1, 4]])); - $this->assertTrue(self::invoke($apriori, 'contains', [$L[2], [2, 3]])); - $this->assertTrue(self::invoke($apriori, 'contains', [$L[2], [2, 4]])); - $this->assertTrue(self::invoke($apriori, 'contains', [$L[2], [3, 4]])); + self::assertCount(4, $L[2]); + self::assertTrue(self::invoke($apriori, 'contains', [$L[2], [1, 2]])); + self::assertFalse(self::invoke($apriori, 'contains', [$L[2], [1, 3]])); + self::assertFalse(self::invoke($apriori, 'contains', [$L[2], [1, 4]])); + self::assertTrue(self::invoke($apriori, 'contains', [$L[2], [2, 3]])); + self::assertTrue(self::invoke($apriori, 'contains', [$L[2], [2, 4]])); + self::assertTrue(self::invoke($apriori, 'contains', [$L[2], [3, 4]])); } public function testAprioriEmpty(): void @@ -85,7 +94,7 @@ public function testAprioriEmpty(): void $L = $apriori->apriori(); - $this->assertEmpty($L); + self::assertEmpty($L); } public function testAprioriSingleItem(): void @@ -97,8 +106,8 @@ public function testAprioriSingleItem(): void $L = $apriori->apriori(); - $this->assertEquals([1], array_keys($L)); - $this->assertEquals([['a']], $L[1]); + self::assertEquals([1], array_keys($L)); + self::assertEquals([['a']], $L[1]); } public function testAprioriL3(): void @@ -110,7 +119,7 @@ public function testAprioriL3(): void $L = $apriori->apriori(); - $this->assertEquals([['a', 'b', 'c']], $L[3]); + self::assertEquals([['a', 'b', 'c']], $L[3]); } public function testGetRules(): void @@ -118,7 +127,7 @@ public function testGetRules(): void $apriori = new Apriori(0.4, 0.8); $apriori->train($this->sampleChars, []); - $this->assertCount(19, $apriori->getRules()); + self::assertCount(19, $apriori->getRules()); } public function testGetRulesSupportAndConfidence(): void @@ -130,14 +139,14 @@ public function testGetRulesSupportAndConfidence(): void $rules = $apriori->getRules(); - $this->assertCount(4, $rules); - $this->assertContains([ + self::assertCount(4, $rules); + self::assertContains([ Apriori::ARRAY_KEY_ANTECEDENT => ['a'], Apriori::ARRAY_KEY_CONSEQUENT => ['b'], Apriori::ARRAY_KEY_SUPPORT => 0.5, Apriori::ARRAY_KEY_CONFIDENCE => 0.5, ], $rules); - $this->assertContains([ + self::assertContains([ Apriori::ARRAY_KEY_ANTECEDENT => ['b'], Apriori::ARRAY_KEY_CONSEQUENT => ['a'], Apriori::ARRAY_KEY_SUPPORT => 0.5, @@ -149,14 +158,14 @@ public function testAntecedents(): void { $apriori = new Apriori(); - $this->assertCount(6, self::invoke($apriori, 'antecedents', [['a', 'b', 'c']])); + self::assertCount(6, self::invoke($apriori, 'antecedents', [['a', 'b', 'c']])); } public function testItems(): void { $apriori = new Apriori(); $apriori->train($this->sampleGreek, []); - $this->assertCount(4, self::invoke($apriori, 'items', [])); + self::assertCount(4, self::invoke($apriori, 'items', [])); } public function testFrequent(): void @@ -164,8 +173,8 @@ public function testFrequent(): void $apriori = new Apriori(0.51); $apriori->train($this->sampleGreek, []); - $this->assertCount(0, self::invoke($apriori, 'frequent', [[['epsilon'], ['theta']]])); - $this->assertCount(2, self::invoke($apriori, 'frequent', [[['alpha'], ['beta']]])); + self::assertCount(0, self::invoke($apriori, 'frequent', [[['epsilon'], ['theta']]])); + self::assertCount(2, self::invoke($apriori, 'frequent', [[['alpha'], ['beta']]])); } public function testCandidates(): void @@ -175,10 +184,10 @@ public function testCandidates(): void $candidates = self::invoke($apriori, 'candidates', [[['alpha'], ['beta'], ['theta']]]); - $this->assertCount(3, $candidates); - $this->assertEquals(['alpha', 'beta'], $candidates[0]); - $this->assertEquals(['alpha', 'theta'], $candidates[1]); - $this->assertEquals(['beta', 'theta'], $candidates[2]); + self::assertCount(3, $candidates); + self::assertEquals(['alpha', 'beta'], $candidates[0]); + self::assertEquals(['alpha', 'theta'], $candidates[1]); + self::assertEquals(['beta', 'theta'], $candidates[2]); } public function testConfidence(): void @@ -186,8 +195,8 @@ public function testConfidence(): void $apriori = new Apriori(); $apriori->train($this->sampleGreek, []); - $this->assertEquals(0.5, self::invoke($apriori, 'confidence', [['alpha', 'beta', 'theta'], ['alpha', 'beta']])); - $this->assertEquals(1, self::invoke($apriori, 'confidence', [['alpha', 'beta'], ['alpha']])); + self::assertEquals(0.5, self::invoke($apriori, 'confidence', [['alpha', 'beta', 'theta'], ['alpha', 'beta']])); + self::assertEquals(1, self::invoke($apriori, 'confidence', [['alpha', 'beta'], ['alpha']])); } public function testSupport(): void @@ -195,8 +204,8 @@ public function testSupport(): void $apriori = new Apriori(); $apriori->train($this->sampleGreek, []); - $this->assertEquals(1.0, self::invoke($apriori, 'support', [['alpha', 'beta']])); - $this->assertEquals(0.5, self::invoke($apriori, 'support', [['epsilon']])); + self::assertEquals(1.0, self::invoke($apriori, 'support', [['alpha', 'beta']])); + self::assertEquals(0.5, self::invoke($apriori, 'support', [['epsilon']])); } public function testFrequency(): void @@ -204,35 +213,35 @@ public function testFrequency(): void $apriori = new Apriori(); $apriori->train($this->sampleGreek, []); - $this->assertEquals(4, self::invoke($apriori, 'frequency', [['alpha', 'beta']])); - $this->assertEquals(2, self::invoke($apriori, 'frequency', [['epsilon']])); + self::assertEquals(4, self::invoke($apriori, 'frequency', [['alpha', 'beta']])); + self::assertEquals(2, self::invoke($apriori, 'frequency', [['epsilon']])); } public function testContains(): void { $apriori = new Apriori(); - $this->assertTrue(self::invoke($apriori, 'contains', [[['a'], ['b']], ['a']])); - $this->assertTrue(self::invoke($apriori, 'contains', [[[1, 2]], [1, 2]])); - $this->assertFalse(self::invoke($apriori, 'contains', [[['a'], ['b']], ['c']])); + self::assertTrue(self::invoke($apriori, 'contains', [[['a'], ['b']], ['a']])); + self::assertTrue(self::invoke($apriori, 'contains', [[[1, 2]], [1, 2]])); + self::assertFalse(self::invoke($apriori, 'contains', [[['a'], ['b']], ['c']])); } public function testSubset(): void { $apriori = new Apriori(); - $this->assertTrue(self::invoke($apriori, 'subset', [['a', 'b'], ['a']])); - $this->assertTrue(self::invoke($apriori, 'subset', [['a'], ['a']])); - $this->assertFalse(self::invoke($apriori, 'subset', [['a'], ['a', 'b']])); + self::assertTrue(self::invoke($apriori, 'subset', [['a', 'b'], ['a']])); + self::assertTrue(self::invoke($apriori, 'subset', [['a'], ['a']])); + self::assertFalse(self::invoke($apriori, 'subset', [['a'], ['a', 'b']])); } public function testEquals(): void { $apriori = new Apriori(); - $this->assertTrue(self::invoke($apriori, 'equals', [['a'], ['a']])); - $this->assertFalse(self::invoke($apriori, 'equals', [['a'], []])); - $this->assertFalse(self::invoke($apriori, 'equals', [['a'], ['b', 'a']])); + self::assertTrue(self::invoke($apriori, 'equals', [['a'], ['a']])); + self::assertFalse(self::invoke($apriori, 'equals', [['a'], []])); + self::assertFalse(self::invoke($apriori, 'equals', [['a'], ['b', 'a']])); } public function testSaveAndRestore(): void @@ -243,14 +252,14 @@ public function testSaveAndRestore(): void $testSamples = [['alpha', 'epsilon'], ['beta', 'theta']]; $predicted = $classifier->predict($testSamples); - $filename = 'apriori-test-'.random_int(100, 999).'-'.uniqid(); - $filepath = tempnam(sys_get_temp_dir(), $filename); + $filename = 'apriori-test-'.random_int(100, 999).'-'.uniqid('', false); + $filepath = (string) tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); $restoredClassifier = $modelManager->restoreFromFile($filepath); - $this->assertEquals($classifier, $restoredClassifier); - $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + self::assertEquals($classifier, $restoredClassifier); + self::assertEquals($predicted, $restoredClassifier->predict($testSamples)); } /** @@ -261,7 +270,7 @@ public function testSaveAndRestore(): void * * @return mixed */ - private static function invoke(&$object, string $method, array $params = []) + private static function invoke(Apriori $object, string $method, array $params = []) { $reflection = new ReflectionClass(get_class($object)); $method = $reflection->getMethod($method); diff --git a/tests/Classification/DecisionTree/DecisionTreeLeafTest.php b/tests/Classification/DecisionTree/DecisionTreeLeafTest.php index e6948782..05139eea 100644 --- a/tests/Classification/DecisionTree/DecisionTreeLeafTest.php +++ b/tests/Classification/DecisionTree/DecisionTreeLeafTest.php @@ -21,7 +21,10 @@ public function testHTMLOutput(): void $leaf->rightLeaf = $rightLeaf; - $this->assertEquals('
col_0 =1
Gini: 0.00
 No |
col_1 <= 2
Gini: 0.00
', $leaf->getHTML()); + self::assertEquals( + '
col_0 =1
Gini: 0.00
 No |
col_1 <= 2
Gini: 0.00
', + $leaf->getHTML() + ); } public function testNodeImpurityDecreaseShouldBeZeroWhenLeafIsTerminal(): void @@ -29,7 +32,7 @@ public function testNodeImpurityDecreaseShouldBeZeroWhenLeafIsTerminal(): void $leaf = new DecisionTreeLeaf(); $leaf->isTerminal = true; - $this->assertEquals(0.0, $leaf->getNodeImpurityDecrease(1)); + self::assertEquals(0.0, $leaf->getNodeImpurityDecrease(1)); } public function testNodeImpurityDecrease(): void @@ -45,6 +48,6 @@ public function testNodeImpurityDecrease(): void $leaf->rightLeaf->records = []; $leaf->rightLeaf->giniIndex = 0.3; - $this->assertSame(0.75, $leaf->getNodeImpurityDecrease(2)); + self::assertSame(0.75, $leaf->getNodeImpurityDecrease(2)); } } diff --git a/tests/Classification/DecisionTreeTest.php b/tests/Classification/DecisionTreeTest.php index 9478a2ac..3f0a7636 100644 --- a/tests/Classification/DecisionTreeTest.php +++ b/tests/Classification/DecisionTreeTest.php @@ -10,6 +10,9 @@ class DecisionTreeTest extends TestCase { + /** + * @var array + */ private $data = [ ['sunny', 85, 85, 'false', 'Dont_play'], ['sunny', 80, 90, 'true', 'Dont_play'], @@ -27,26 +30,27 @@ class DecisionTreeTest extends TestCase ['rain', 71, 80, 'true', 'Dont_play'], ]; + /** + * @var array + */ private $extraData = [ ['scorching', 90, 95, 'false', 'Dont_play'], ['scorching', 100, 93, 'true', 'Dont_play'], ]; - public function testPredictSingleSample() + public function testPredictSingleSample(): void { [$data, $targets] = $this->getData($this->data); $classifier = new DecisionTree(5); $classifier->train($data, $targets); - $this->assertEquals('Dont_play', $classifier->predict(['sunny', 78, 72, 'false'])); - $this->assertEquals('Play', $classifier->predict(['overcast', 60, 60, 'false'])); - $this->assertEquals('Dont_play', $classifier->predict(['rain', 60, 60, 'true'])); + self::assertEquals('Dont_play', $classifier->predict(['sunny', 78, 72, 'false'])); + self::assertEquals('Play', $classifier->predict(['overcast', 60, 60, 'false'])); + self::assertEquals('Dont_play', $classifier->predict(['rain', 60, 60, 'true'])); [$data, $targets] = $this->getData($this->extraData); $classifier->train($data, $targets); - $this->assertEquals('Dont_play', $classifier->predict(['scorching', 95, 90, 'true'])); - $this->assertEquals('Play', $classifier->predict(['overcast', 60, 60, 'false'])); - - return $classifier; + self::assertEquals('Dont_play', $classifier->predict(['scorching', 95, 90, 'true'])); + self::assertEquals('Play', $classifier->predict(['overcast', 60, 60, 'false'])); } public function testSaveAndRestore(): void @@ -58,14 +62,14 @@ public function testSaveAndRestore(): void $testSamples = [['sunny', 78, 72, 'false'], ['overcast', 60, 60, 'false']]; $predicted = $classifier->predict($testSamples); - $filename = 'decision-tree-test-'.random_int(100, 999).'-'.uniqid(); - $filepath = tempnam(sys_get_temp_dir(), $filename); + $filename = 'decision-tree-test-'.random_int(100, 999).'-'.uniqid('', false); + $filepath = (string) tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); $restoredClassifier = $modelManager->restoreFromFile($filepath); - $this->assertEquals($classifier, $restoredClassifier); - $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + self::assertEquals($classifier, $restoredClassifier); + self::assertEquals($predicted, $restoredClassifier->predict($testSamples)); } public function testTreeDepth(): void @@ -73,10 +77,10 @@ public function testTreeDepth(): void [$data, $targets] = $this->getData($this->data); $classifier = new DecisionTree(5); $classifier->train($data, $targets); - $this->assertTrue($classifier->actualDepth <= 5); + self::assertTrue($classifier->actualDepth <= 5); } - private function getData($input) + private function getData(array $input): array { $targets = array_column($input, 4); array_walk($input, function (&$v): void { diff --git a/tests/Classification/Ensemble/AdaBoostTest.php b/tests/Classification/Ensemble/AdaBoostTest.php index 095cde03..173df6ce 100644 --- a/tests/Classification/Ensemble/AdaBoostTest.php +++ b/tests/Classification/Ensemble/AdaBoostTest.php @@ -37,27 +37,27 @@ public function testPredictSingleSample(): void $targets = [0, 0, 0, 1, 1, 1]; $classifier = new AdaBoost(); $classifier->train($samples, $targets); - $this->assertEquals(0, $classifier->predict([0.1, 0.2])); - $this->assertEquals(0, $classifier->predict([0.1, 0.99])); - $this->assertEquals(1, $classifier->predict([1.1, 0.8])); + self::assertEquals(0, $classifier->predict([0.1, 0.2])); + self::assertEquals(0, $classifier->predict([0.1, 0.99])); + self::assertEquals(1, $classifier->predict([1.1, 0.8])); // OR problem $samples = [[0, 0], [0.1, 0.2], [0.2, 0.1], [1, 0], [0, 1], [1, 1]]; $targets = [0, 0, 0, 1, 1, 1]; $classifier = new AdaBoost(); $classifier->train($samples, $targets); - $this->assertEquals(0, $classifier->predict([0.1, 0.2])); - $this->assertEquals(1, $classifier->predict([0.1, 0.99])); - $this->assertEquals(1, $classifier->predict([1.1, 0.8])); + self::assertEquals(0, $classifier->predict([0.1, 0.2])); + self::assertEquals(1, $classifier->predict([0.1, 0.99])); + self::assertEquals(1, $classifier->predict([1.1, 0.8])); // XOR problem $samples = [[0.1, 0.2], [1., 1.], [0.9, 0.8], [0., 1.], [1., 0.], [0.2, 0.8]]; $targets = [0, 0, 0, 1, 1, 1]; $classifier = new AdaBoost(5); $classifier->train($samples, $targets); - $this->assertEquals(0, $classifier->predict([0.1, 0.1])); - $this->assertEquals(1, $classifier->predict([0, 0.999])); - $this->assertEquals(0, $classifier->predict([1.1, 0.8])); + self::assertEquals(0, $classifier->predict([0.1, 0.1])); + self::assertEquals(1, $classifier->predict([0, 0.999])); + self::assertEquals(0, $classifier->predict([1.1, 0.8])); } public function testSaveAndRestore(): void @@ -70,13 +70,13 @@ public function testSaveAndRestore(): void $testSamples = [[0, 1], [1, 1], [0.2, 0.1]]; $predicted = $classifier->predict($testSamples); - $filename = 'adaboost-test-'.random_int(100, 999).'-'.uniqid(); - $filepath = tempnam(sys_get_temp_dir(), $filename); + $filename = 'adaboost-test-'.random_int(100, 999).'-'.uniqid('', false); + $filepath = (string) tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); $restoredClassifier = $modelManager->restoreFromFile($filepath); - $this->assertEquals($classifier, $restoredClassifier); - $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + self::assertEquals($classifier, $restoredClassifier); + self::assertEquals($predicted, $restoredClassifier->predict($testSamples)); } } diff --git a/tests/Classification/Ensemble/BaggingTest.php b/tests/Classification/Ensemble/BaggingTest.php index 5b2e47bf..9738ce73 100644 --- a/tests/Classification/Ensemble/BaggingTest.php +++ b/tests/Classification/Ensemble/BaggingTest.php @@ -4,6 +4,7 @@ namespace Phpml\Tests\Classification\Ensemble; +use Phpml\Classification\Classifier; use Phpml\Classification\DecisionTree; use Phpml\Classification\Ensemble\Bagging; use Phpml\Classification\NaiveBayes; @@ -13,6 +14,9 @@ class BaggingTest extends TestCase { + /** + * @var array + */ private $data = [ ['sunny', 85, 85, 'false', 'Dont_play'], ['sunny', 80, 90, 'true', 'Dont_play'], @@ -30,6 +34,9 @@ class BaggingTest extends TestCase ['rain', 71, 80, 'true', 'Dont_play'], ]; + /** + * @var array + */ private $extraData = [ ['scorching', 90, 95, 'false', 'Dont_play'], ['scorching', 0, 0, 'false', 'Dont_play'], @@ -49,14 +56,14 @@ public function testPredictSingleSample(): void $classifier = $this->getClassifier(); // Testing with default options $classifier->train($data, $targets); - $this->assertEquals('Dont_play', $classifier->predict(['sunny', 78, 72, 'false'])); - $this->assertEquals('Play', $classifier->predict(['overcast', 60, 60, 'false'])); - $this->assertEquals('Dont_play', $classifier->predict(['rain', 60, 60, 'true'])); + self::assertEquals('Dont_play', $classifier->predict(['sunny', 78, 72, 'false'])); + self::assertEquals('Play', $classifier->predict(['overcast', 60, 60, 'false'])); + self::assertEquals('Dont_play', $classifier->predict(['rain', 60, 60, 'true'])); [$data, $targets] = $this->getData($this->extraData); $classifier->train($data, $targets); - $this->assertEquals('Dont_play', $classifier->predict(['scorching', 95, 90, 'true'])); - $this->assertEquals('Play', $classifier->predict(['overcast', 60, 60, 'false'])); + self::assertEquals('Dont_play', $classifier->predict(['scorching', 95, 90, 'true'])); + self::assertEquals('Play', $classifier->predict(['overcast', 60, 60, 'false'])); } public function testSaveAndRestore(): void @@ -68,14 +75,14 @@ public function testSaveAndRestore(): void $testSamples = [['sunny', 78, 72, 'false'], ['overcast', 60, 60, 'false']]; $predicted = $classifier->predict($testSamples); - $filename = 'bagging-test-'.random_int(100, 999).'-'.uniqid(); - $filepath = tempnam(sys_get_temp_dir(), $filename); + $filename = 'bagging-test-'.random_int(100, 999).'-'.uniqid('', false); + $filepath = (string) tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); $restoredClassifier = $modelManager->restoreFromFile($filepath); - $this->assertEquals($classifier, $restoredClassifier); - $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + self::assertEquals($classifier, $restoredClassifier); + self::assertEquals($predicted, $restoredClassifier->predict($testSamples)); } public function testBaseClassifiers(): void @@ -94,12 +101,15 @@ public function testBaseClassifiers(): void foreach ($testData as $test) { $result = $classifier->predict($test); $baseResult = $classifier->predict($test); - $this->assertEquals($result, $baseResult); + self::assertEquals($result, $baseResult); } } } - protected function getClassifier($numBaseClassifiers = 50) + /** + * @return Bagging + */ + protected function getClassifier(int $numBaseClassifiers = 50): Classifier { $classifier = new Bagging($numBaseClassifiers); $classifier->setSubsetRatio(1.0); @@ -108,7 +118,7 @@ protected function getClassifier($numBaseClassifiers = 50) return $classifier; } - protected function getAvailableBaseClassifiers() + protected function getAvailableBaseClassifiers(): array { return [ DecisionTree::class => ['depth' => 5], @@ -116,7 +126,7 @@ protected function getAvailableBaseClassifiers() ]; } - private function getData($input) + private function getData(array $input): array { // Populating input data to a size large enough // for base classifiers that they can work with a subset of it diff --git a/tests/Classification/Ensemble/RandomForestTest.php b/tests/Classification/Ensemble/RandomForestTest.php index 93353a36..2f21c5ca 100644 --- a/tests/Classification/Ensemble/RandomForestTest.php +++ b/tests/Classification/Ensemble/RandomForestTest.php @@ -4,6 +4,7 @@ namespace Phpml\Tests\Classification\Ensemble; +use Phpml\Classification\Classifier; use Phpml\Classification\DecisionTree; use Phpml\Classification\Ensemble\RandomForest; use Phpml\Classification\NaiveBayes; @@ -47,7 +48,10 @@ public function testThrowExceptionWithInvalidFeatureSubsetRatioString(): void $classifier->setFeatureSubsetRatio('pow'); } - protected function getClassifier($numBaseClassifiers = 50) + /** + * @return RandomForest + */ + protected function getClassifier(int $numBaseClassifiers = 50): Classifier { $classifier = new RandomForest($numBaseClassifiers); $classifier->setFeatureSubsetRatio('log'); @@ -55,7 +59,7 @@ protected function getClassifier($numBaseClassifiers = 50) return $classifier; } - protected function getAvailableBaseClassifiers() + protected function getAvailableBaseClassifiers(): array { return [DecisionTree::class => ['depth' => 5]]; } diff --git a/tests/Classification/KNearestNeighborsTest.php b/tests/Classification/KNearestNeighborsTest.php index 110d49d8..5be9a3d0 100644 --- a/tests/Classification/KNearestNeighborsTest.php +++ b/tests/Classification/KNearestNeighborsTest.php @@ -19,15 +19,15 @@ public function testPredictSingleSampleWithDefaultK(): void $classifier = new KNearestNeighbors(); $classifier->train($samples, $labels); - $this->assertEquals('b', $classifier->predict([3, 2])); - $this->assertEquals('b', $classifier->predict([5, 1])); - $this->assertEquals('b', $classifier->predict([4, 3])); - $this->assertEquals('b', $classifier->predict([4, -5])); - - $this->assertEquals('a', $classifier->predict([2, 3])); - $this->assertEquals('a', $classifier->predict([1, 2])); - $this->assertEquals('a', $classifier->predict([1, 5])); - $this->assertEquals('a', $classifier->predict([3, 10])); + self::assertEquals('b', $classifier->predict([3, 2])); + self::assertEquals('b', $classifier->predict([5, 1])); + self::assertEquals('b', $classifier->predict([4, 3])); + self::assertEquals('b', $classifier->predict([4, -5])); + + self::assertEquals('a', $classifier->predict([2, 3])); + self::assertEquals('a', $classifier->predict([1, 2])); + self::assertEquals('a', $classifier->predict([1, 5])); + self::assertEquals('a', $classifier->predict([3, 10])); } public function testPredictArrayOfSamples(): void @@ -42,7 +42,7 @@ public function testPredictArrayOfSamples(): void $classifier->train($trainSamples, $trainLabels); $predicted = $classifier->predict($testSamples); - $this->assertEquals($testLabels, $predicted); + self::assertEquals($testLabels, $predicted); } public function testPredictArrayOfSamplesUsingChebyshevDistanceMetric(): void @@ -57,7 +57,7 @@ public function testPredictArrayOfSamplesUsingChebyshevDistanceMetric(): void $classifier->train($trainSamples, $trainLabels); $predicted = $classifier->predict($testSamples); - $this->assertEquals($testLabels, $predicted); + self::assertEquals($testLabels, $predicted); } public function testSaveAndRestore(): void @@ -73,12 +73,12 @@ public function testSaveAndRestore(): void $predicted = $classifier->predict($testSamples); $filename = 'knearest-neighbors-test-'.random_int(100, 999).'-'.uniqid('', false); - $filepath = tempnam(sys_get_temp_dir(), $filename); + $filepath = (string) tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); $restoredClassifier = $modelManager->restoreFromFile($filepath); - $this->assertEquals($classifier, $restoredClassifier); - $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + self::assertEquals($classifier, $restoredClassifier); + self::assertEquals($predicted, $restoredClassifier->predict($testSamples)); } } diff --git a/tests/Classification/Linear/AdalineTest.php b/tests/Classification/Linear/AdalineTest.php index 08ad78aa..7bc8f9df 100644 --- a/tests/Classification/Linear/AdalineTest.php +++ b/tests/Classification/Linear/AdalineTest.php @@ -31,18 +31,18 @@ public function testPredictSingleSample(): void $targets = [0, 0, 0, 1]; $classifier = new Adaline(); $classifier->train($samples, $targets); - $this->assertEquals(0, $classifier->predict([0.1, 0.2])); - $this->assertEquals(0, $classifier->predict([0.1, 0.99])); - $this->assertEquals(1, $classifier->predict([1.1, 0.8])); + self::assertEquals(0, $classifier->predict([0.1, 0.2])); + self::assertEquals(0, $classifier->predict([0.1, 0.99])); + self::assertEquals(1, $classifier->predict([1.1, 0.8])); // OR problem $samples = [[0, 0], [1, 0], [0, 1], [1, 1]]; $targets = [0, 1, 1, 1]; $classifier = new Adaline(); $classifier->train($samples, $targets); - $this->assertEquals(0, $classifier->predict([0.1, 0.2])); - $this->assertEquals(1, $classifier->predict([0.1, 0.99])); - $this->assertEquals(1, $classifier->predict([1.1, 0.8])); + self::assertEquals(0, $classifier->predict([0.1, 0.2])); + self::assertEquals(1, $classifier->predict([0.1, 0.99])); + self::assertEquals(1, $classifier->predict([1.1, 0.8])); // By use of One-v-Rest, Adaline can perform multi-class classification // The samples should be separable by lines perpendicular to the dimensions @@ -55,15 +55,15 @@ public function testPredictSingleSample(): void $classifier = new Adaline(); $classifier->train($samples, $targets); - $this->assertEquals(0, $classifier->predict([0.5, 0.5])); - $this->assertEquals(1, $classifier->predict([6.0, 5.0])); - $this->assertEquals(2, $classifier->predict([3.0, 9.5])); + self::assertEquals(0, $classifier->predict([0.5, 0.5])); + self::assertEquals(1, $classifier->predict([6.0, 5.0])); + self::assertEquals(2, $classifier->predict([3.0, 9.5])); // Extra partial training should lead to the same results. $classifier->partialTrain([[0, 1], [1, 0]], [0, 0], [0, 1, 2]); - $this->assertEquals(0, $classifier->predict([0.5, 0.5])); - $this->assertEquals(1, $classifier->predict([6.0, 5.0])); - $this->assertEquals(2, $classifier->predict([3.0, 9.5])); + self::assertEquals(0, $classifier->predict([0.5, 0.5])); + self::assertEquals(1, $classifier->predict([6.0, 5.0])); + self::assertEquals(2, $classifier->predict([3.0, 9.5])); // Train should clear previous data. $samples = [ @@ -73,9 +73,9 @@ public function testPredictSingleSample(): void ]; $targets = [2, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1]; $classifier->train($samples, $targets); - $this->assertEquals(2, $classifier->predict([0.5, 0.5])); - $this->assertEquals(0, $classifier->predict([6.0, 5.0])); - $this->assertEquals(1, $classifier->predict([3.0, 9.5])); + self::assertEquals(2, $classifier->predict([0.5, 0.5])); + self::assertEquals(0, $classifier->predict([6.0, 5.0])); + self::assertEquals(1, $classifier->predict([3.0, 9.5])); } public function testSaveAndRestore(): void @@ -89,12 +89,12 @@ public function testSaveAndRestore(): void $predicted = $classifier->predict($testSamples); $filename = 'adaline-test-'.random_int(100, 999).'-'.uniqid('', false); - $filepath = tempnam(sys_get_temp_dir(), $filename); + $filepath = (string) tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); $restoredClassifier = $modelManager->restoreFromFile($filepath); - $this->assertEquals($classifier, $restoredClassifier); - $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + self::assertEquals($classifier, $restoredClassifier); + self::assertEquals($predicted, $restoredClassifier->predict($testSamples)); } } diff --git a/tests/Classification/Linear/DecisionStumpTest.php b/tests/Classification/Linear/DecisionStumpTest.php index 1295ac53..7cd82504 100644 --- a/tests/Classification/Linear/DecisionStumpTest.php +++ b/tests/Classification/Linear/DecisionStumpTest.php @@ -23,7 +23,7 @@ public function testTrainThrowWhenSample(): void $classifier->train($samples, $targets); } - public function testPredictSingleSample() + public function testPredictSingleSample(): void { // Samples should be separable with a line perpendicular // to any dimension given in the dataset @@ -33,20 +33,20 @@ public function testPredictSingleSample() $targets = [0, 0, 1, 1]; $classifier = new DecisionStump(); $classifier->train($samples, $targets); - $this->assertEquals(0, $classifier->predict([0.1, 0.2])); - $this->assertEquals(0, $classifier->predict([1.1, 0.2])); - $this->assertEquals(1, $classifier->predict([0.1, 0.99])); - $this->assertEquals(1, $classifier->predict([1.1, 0.8])); + self::assertEquals(0, $classifier->predict([0.1, 0.2])); + self::assertEquals(0, $classifier->predict([1.1, 0.2])); + self::assertEquals(1, $classifier->predict([0.1, 0.99])); + self::assertEquals(1, $classifier->predict([1.1, 0.8])); // Then: vertical test $samples = [[0, 0], [1, 0], [0, 1], [1, 1]]; $targets = [0, 1, 0, 1]; $classifier = new DecisionStump(); $classifier->train($samples, $targets); - $this->assertEquals(0, $classifier->predict([0.1, 0.2])); - $this->assertEquals(0, $classifier->predict([0.1, 1.1])); - $this->assertEquals(1, $classifier->predict([1.0, 0.99])); - $this->assertEquals(1, $classifier->predict([1.1, 0.1])); + self::assertEquals(0, $classifier->predict([0.1, 0.2])); + self::assertEquals(0, $classifier->predict([0.1, 1.1])); + self::assertEquals(1, $classifier->predict([1.0, 0.99])); + self::assertEquals(1, $classifier->predict([1.1, 0.1])); // By use of One-v-Rest, DecisionStump can perform multi-class classification // The samples should be separable by lines perpendicular to the dimensions @@ -59,11 +59,9 @@ public function testPredictSingleSample() $classifier = new DecisionStump(); $classifier->train($samples, $targets); - $this->assertEquals(0, $classifier->predict([0.5, 0.5])); - $this->assertEquals(1, $classifier->predict([6.0, 5.0])); - $this->assertEquals(2, $classifier->predict([3.5, 9.5])); - - return $classifier; + self::assertEquals(0, $classifier->predict([0.5, 0.5])); + self::assertEquals(1, $classifier->predict([6.0, 5.0])); + self::assertEquals(2, $classifier->predict([3.5, 9.5])); } public function testSaveAndRestore(): void @@ -76,13 +74,13 @@ public function testSaveAndRestore(): void $testSamples = [[0, 1], [1, 1], [0.2, 0.1]]; $predicted = $classifier->predict($testSamples); - $filename = 'dstump-test-'.random_int(100, 999).'-'.uniqid(); - $filepath = tempnam(sys_get_temp_dir(), $filename); + $filename = 'dstump-test-'.random_int(100, 999).'-'.uniqid('', false); + $filepath = (string) tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); $restoredClassifier = $modelManager->restoreFromFile($filepath); - $this->assertEquals($classifier, $restoredClassifier); - $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + self::assertEquals($classifier, $restoredClassifier); + self::assertEquals($predicted, $restoredClassifier->predict($testSamples)); } } diff --git a/tests/Classification/Linear/LogisticRegressionTest.php b/tests/Classification/Linear/LogisticRegressionTest.php index 075e1ead..9573531e 100644 --- a/tests/Classification/Linear/LogisticRegressionTest.php +++ b/tests/Classification/Linear/LogisticRegressionTest.php @@ -64,8 +64,8 @@ public function testPredictSingleSample(): void $targets = [0, 0, 0, 1, 0, 1]; $classifier = new LogisticRegression(); $classifier->train($samples, $targets); - $this->assertEquals(0, $classifier->predict([0.1, 0.1])); - $this->assertEquals(1, $classifier->predict([0.9, 0.9])); + self::assertEquals(0, $classifier->predict([0.1, 0.1])); + self::assertEquals(1, $classifier->predict([0.9, 0.9])); } public function testPredictSingleSampleWithBatchTraining(): void @@ -83,8 +83,8 @@ public function testPredictSingleSampleWithBatchTraining(): void 'L2' ); $classifier->train($samples, $targets); - $this->assertEquals(0, $classifier->predict([0.1, 0.1])); - $this->assertEquals(1, $classifier->predict([0.9, 0.9])); + self::assertEquals(0, $classifier->predict([0.1, 0.1])); + self::assertEquals(1, $classifier->predict([0.9, 0.9])); } public function testPredictSingleSampleWithOnlineTraining(): void @@ -102,8 +102,8 @@ public function testPredictSingleSampleWithOnlineTraining(): void '' ); $classifier->train($samples, $targets); - $this->assertEquals(0, $classifier->predict([0.1, 0.1])); - $this->assertEquals(1, $classifier->predict([0.9, 0.9])); + self::assertEquals(0, $classifier->predict([0.1, 0.1])); + self::assertEquals(1, $classifier->predict([0.9, 0.9])); } public function testPredictSingleSampleWithSSECost(): void @@ -118,8 +118,8 @@ public function testPredictSingleSampleWithSSECost(): void 'L2' ); $classifier->train($samples, $targets); - $this->assertEquals(0, $classifier->predict([0.1, 0.1])); - $this->assertEquals(1, $classifier->predict([0.9, 0.9])); + self::assertEquals(0, $classifier->predict([0.1, 0.1])); + self::assertEquals(1, $classifier->predict([0.9, 0.9])); } public function testPredictSingleSampleWithoutPenalty(): void @@ -134,8 +134,8 @@ public function testPredictSingleSampleWithoutPenalty(): void '' ); $classifier->train($samples, $targets); - $this->assertEquals(0, $classifier->predict([0.1, 0.1])); - $this->assertEquals(1, $classifier->predict([0.9, 0.9])); + self::assertEquals(0, $classifier->predict([0.1, 0.1])); + self::assertEquals(1, $classifier->predict([0.9, 0.9])); } public function testPredictMultiClassSample(): void @@ -151,9 +151,9 @@ public function testPredictMultiClassSample(): void $classifier = new LogisticRegression(); $classifier->train($samples, $targets); - $this->assertEquals(0, $classifier->predict([0.5, 0.5])); - $this->assertEquals(1, $classifier->predict([6.0, 5.0])); - $this->assertEquals(2, $classifier->predict([3.0, 9.5])); + self::assertEquals(0, $classifier->predict([0.5, 0.5])); + self::assertEquals(1, $classifier->predict([6.0, 5.0])); + self::assertEquals(2, $classifier->predict([3.0, 9.5])); } public function testPredictProbabilitySingleSample(): void @@ -171,13 +171,13 @@ public function testPredictProbabilitySingleSample(): void $zero = $method->invoke($predictor, [0.1, 0.1], 0); $one = $method->invoke($predictor, [0.1, 0.1], 1); - $this->assertEquals(1, $zero + $one, '', 1e-6); - $this->assertTrue($zero > $one); + self::assertEquals(1, $zero + $one, '', 1e-6); + self::assertTrue($zero > $one); $zero = $method->invoke($predictor, [0.9, 0.9], 0); $one = $method->invoke($predictor, [0.9, 0.9], 1); - $this->assertEquals(1, $zero + $one, '', 1e-6); - $this->assertTrue($zero < $one); + self::assertEquals(1, $zero + $one, '', 1e-6); + self::assertTrue($zero < $one); } public function testPredictProbabilityMultiClassSample(): void @@ -213,10 +213,10 @@ public function testPredictProbabilityMultiClassSample(): void $two = $method->invoke($predictor, [3.0, 9.5], 2); $not_two = $method->invoke($predictor, [3.0, 9.5], 'not_2'); - $this->assertEquals(1, $zero + $not_zero, '', 1e-6); - $this->assertEquals(1, $one + $not_one, '', 1e-6); - $this->assertEquals(1, $two + $not_two, '', 1e-6); - $this->assertTrue($zero < $two); - $this->assertTrue($one < $two); + self::assertEquals(1, $zero + $not_zero, '', 1e-6); + self::assertEquals(1, $one + $not_one, '', 1e-6); + self::assertEquals(1, $two + $not_two, '', 1e-6); + self::assertTrue($zero < $two); + self::assertTrue($one < $two); } } diff --git a/tests/Classification/Linear/PerceptronTest.php b/tests/Classification/Linear/PerceptronTest.php index a3958b80..fa118f3d 100644 --- a/tests/Classification/Linear/PerceptronTest.php +++ b/tests/Classification/Linear/PerceptronTest.php @@ -33,9 +33,9 @@ public function testPredictSingleSample(): void $classifier = new Perceptron(0.001, 5000); $classifier->setEarlyStop(false); $classifier->train($samples, $targets); - $this->assertEquals(0, $classifier->predict([0.1, 0.2])); - $this->assertEquals(0, $classifier->predict([0, 1])); - $this->assertEquals(1, $classifier->predict([1.1, 0.8])); + self::assertEquals(0, $classifier->predict([0.1, 0.2])); + self::assertEquals(0, $classifier->predict([0, 1])); + self::assertEquals(1, $classifier->predict([1.1, 0.8])); // OR problem $samples = [[0.1, 0.1], [0.4, 0.], [0., 0.3], [1, 0], [0, 1], [1, 1]]; @@ -43,9 +43,9 @@ public function testPredictSingleSample(): void $classifier = new Perceptron(0.001, 5000, false); $classifier->setEarlyStop(false); $classifier->train($samples, $targets); - $this->assertEquals(0, $classifier->predict([0., 0.])); - $this->assertEquals(1, $classifier->predict([0.1, 0.99])); - $this->assertEquals(1, $classifier->predict([1.1, 0.8])); + self::assertEquals(0, $classifier->predict([0., 0.])); + self::assertEquals(1, $classifier->predict([0.1, 0.99])); + self::assertEquals(1, $classifier->predict([1.1, 0.8])); // By use of One-v-Rest, Perceptron can perform multi-class classification // The samples should be separable by lines perpendicular to the dimensions @@ -59,15 +59,15 @@ public function testPredictSingleSample(): void $classifier = new Perceptron(); $classifier->setEarlyStop(false); $classifier->train($samples, $targets); - $this->assertEquals(0, $classifier->predict([0.5, 0.5])); - $this->assertEquals(1, $classifier->predict([6.0, 5.0])); - $this->assertEquals(2, $classifier->predict([3.0, 9.5])); + self::assertEquals(0, $classifier->predict([0.5, 0.5])); + self::assertEquals(1, $classifier->predict([6.0, 5.0])); + self::assertEquals(2, $classifier->predict([3.0, 9.5])); // Extra partial training should lead to the same results. $classifier->partialTrain([[0, 1], [1, 0]], [0, 0], [0, 1, 2]); - $this->assertEquals(0, $classifier->predict([0.5, 0.5])); - $this->assertEquals(1, $classifier->predict([6.0, 5.0])); - $this->assertEquals(2, $classifier->predict([3.0, 9.5])); + self::assertEquals(0, $classifier->predict([0.5, 0.5])); + self::assertEquals(1, $classifier->predict([6.0, 5.0])); + self::assertEquals(2, $classifier->predict([3.0, 9.5])); // Train should clear previous data. $samples = [ @@ -77,9 +77,9 @@ public function testPredictSingleSample(): void ]; $targets = [2, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1]; $classifier->train($samples, $targets); - $this->assertEquals(2, $classifier->predict([0.5, 0.5])); - $this->assertEquals(0, $classifier->predict([6.0, 5.0])); - $this->assertEquals(1, $classifier->predict([3.0, 9.5])); + self::assertEquals(2, $classifier->predict([0.5, 0.5])); + self::assertEquals(0, $classifier->predict([6.0, 5.0])); + self::assertEquals(1, $classifier->predict([3.0, 9.5])); } public function testSaveAndRestore(): void @@ -93,12 +93,12 @@ public function testSaveAndRestore(): void $predicted = $classifier->predict($testSamples); $filename = 'perceptron-test-'.random_int(100, 999).'-'.uniqid('', false); - $filepath = tempnam(sys_get_temp_dir(), $filename); + $filepath = (string) tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); $restoredClassifier = $modelManager->restoreFromFile($filepath); - $this->assertEquals($classifier, $restoredClassifier); - $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + self::assertEquals($classifier, $restoredClassifier); + self::assertEquals($predicted, $restoredClassifier->predict($testSamples)); } } diff --git a/tests/Classification/MLPClassifierTest.php b/tests/Classification/MLPClassifierTest.php index ae0871dc..2998ac91 100644 --- a/tests/Classification/MLPClassifierTest.php +++ b/tests/Classification/MLPClassifierTest.php @@ -21,21 +21,21 @@ public function testMLPClassifierLayersInitialization(): void { $mlp = new MLPClassifier(2, [2], [0, 1]); - $this->assertCount(3, $mlp->getLayers()); + self::assertCount(3, $mlp->getLayers()); $layers = $mlp->getLayers(); // input layer - $this->assertCount(3, $layers[0]->getNodes()); - $this->assertNotContainsOnly(Neuron::class, $layers[0]->getNodes()); + self::assertCount(3, $layers[0]->getNodes()); + self::assertNotContainsOnly(Neuron::class, $layers[0]->getNodes()); // hidden layer - $this->assertCount(3, $layers[1]->getNodes()); - $this->assertNotContainsOnly(Neuron::class, $layers[1]->getNodes()); + self::assertCount(3, $layers[1]->getNodes()); + self::assertNotContainsOnly(Neuron::class, $layers[1]->getNodes()); // output layer - $this->assertCount(2, $layers[2]->getNodes()); - $this->assertContainsOnly(Neuron::class, $layers[2]->getNodes()); + self::assertCount(2, $layers[2]->getNodes()); + self::assertContainsOnly(Neuron::class, $layers[2]->getNodes()); } public function testSynapsesGeneration(): void @@ -46,11 +46,11 @@ public function testSynapsesGeneration(): void foreach ($layers[1]->getNodes() as $node) { if ($node instanceof Neuron) { $synapses = $node->getSynapses(); - $this->assertCount(3, $synapses); + self::assertCount(3, $synapses); $synapsesNodes = $this->getSynapsesNodes($synapses); foreach ($layers[0]->getNodes() as $prevNode) { - $this->assertContains($prevNode, $synapsesNodes); + self::assertContains($prevNode, $synapsesNodes); } } } @@ -65,10 +65,10 @@ public function testBackpropagationLearning(): void ['a', 'b', 'a', 'b'] ); - $this->assertEquals('a', $network->predict([1, 0])); - $this->assertEquals('b', $network->predict([0, 1])); - $this->assertEquals('a', $network->predict([1, 1])); - $this->assertEquals('b', $network->predict([0, 0])); + self::assertEquals('a', $network->predict([1, 0])); + self::assertEquals('b', $network->predict([0, 1])); + self::assertEquals('a', $network->predict([1, 1])); + self::assertEquals('b', $network->predict([0, 0])); } public function testBackpropagationTrainingReset(): void @@ -80,16 +80,16 @@ public function testBackpropagationTrainingReset(): void ['a', 'b'] ); - $this->assertEquals('a', $network->predict([1, 0])); - $this->assertEquals('b', $network->predict([0, 1])); + self::assertEquals('a', $network->predict([1, 0])); + self::assertEquals('b', $network->predict([0, 1])); $network->train( [[1, 0], [0, 1]], ['b', 'a'] ); - $this->assertEquals('b', $network->predict([1, 0])); - $this->assertEquals('a', $network->predict([0, 1])); + self::assertEquals('b', $network->predict([1, 0])); + self::assertEquals('a', $network->predict([0, 1])); } public function testBackpropagationPartialTraining(): void @@ -101,18 +101,18 @@ public function testBackpropagationPartialTraining(): void ['a', 'b'] ); - $this->assertEquals('a', $network->predict([1, 0])); - $this->assertEquals('b', $network->predict([0, 1])); + self::assertEquals('a', $network->predict([1, 0])); + self::assertEquals('b', $network->predict([0, 1])); $network->partialTrain( [[1, 1], [0, 0]], ['a', 'b'] ); - $this->assertEquals('a', $network->predict([1, 0])); - $this->assertEquals('b', $network->predict([0, 1])); - $this->assertEquals('a', $network->predict([1, 1])); - $this->assertEquals('b', $network->predict([0, 0])); + self::assertEquals('a', $network->predict([1, 0])); + self::assertEquals('b', $network->predict([0, 1])); + self::assertEquals('a', $network->predict([1, 1])); + self::assertEquals('b', $network->predict([0, 0])); } public function testBackpropagationLearningMultilayer(): void @@ -124,10 +124,10 @@ public function testBackpropagationLearningMultilayer(): void ['a', 'b', 'a', 'c'] ); - $this->assertEquals('a', $network->predict([1, 0, 0, 0, 0])); - $this->assertEquals('b', $network->predict([0, 1, 1, 0, 0])); - $this->assertEquals('a', $network->predict([1, 1, 1, 1, 1])); - $this->assertEquals('c', $network->predict([0, 0, 0, 0, 0])); + self::assertEquals('a', $network->predict([1, 0, 0, 0, 0])); + self::assertEquals('b', $network->predict([0, 1, 1, 0, 0])); + self::assertEquals('a', $network->predict([1, 1, 1, 1, 1])); + self::assertEquals('c', $network->predict([0, 0, 0, 0, 0])); } public function testBackpropagationLearningMulticlass(): void @@ -139,11 +139,11 @@ public function testBackpropagationLearningMulticlass(): void ['a', 'b', 'a', 'a', 4] ); - $this->assertEquals('a', $network->predict([1, 0, 0, 0, 0])); - $this->assertEquals('b', $network->predict([0, 1, 0, 0, 0])); - $this->assertEquals('a', $network->predict([0, 0, 1, 1, 0])); - $this->assertEquals('a', $network->predict([1, 1, 1, 1, 1])); - $this->assertEquals(4, $network->predict([0, 0, 0, 0, 0])); + self::assertEquals('a', $network->predict([1, 0, 0, 0, 0])); + self::assertEquals('b', $network->predict([0, 1, 0, 0, 0])); + self::assertEquals('a', $network->predict([0, 0, 1, 1, 0])); + self::assertEquals('a', $network->predict([1, 1, 1, 1, 1])); + self::assertEquals(4, $network->predict([0, 0, 0, 0, 0])); } /** @@ -157,10 +157,10 @@ public function testBackpropagationActivationFunctions(ActivationFunction $activ ['a', 'b', 'a', 'a'] ); - $this->assertEquals('a', $network->predict([1, 0, 0, 0, 0])); - $this->assertEquals('b', $network->predict([0, 1, 0, 0, 0])); - $this->assertEquals('a', $network->predict([0, 0, 1, 1, 0])); - $this->assertEquals('a', $network->predict([1, 1, 1, 1, 1])); + self::assertEquals('a', $network->predict([1, 0, 0, 0, 0])); + self::assertEquals('b', $network->predict([0, 1, 0, 0, 0])); + self::assertEquals('a', $network->predict([0, 0, 1, 1, 0])); + self::assertEquals('a', $network->predict([1, 1, 1, 1, 1])); } public function activationFunctionsProvider(): array @@ -184,13 +184,13 @@ public function testSaveAndRestore(): void $predicted = $classifier->predict($testSamples); $filename = 'perceptron-test-'.random_int(100, 999).'-'.uniqid('', false); - $filepath = tempnam(sys_get_temp_dir(), $filename); + $filepath = (string) tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); $restoredClassifier = $modelManager->restoreFromFile($filepath); - $this->assertEquals($classifier, $restoredClassifier); - $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + self::assertEquals($classifier, $restoredClassifier); + self::assertEquals($predicted, $restoredClassifier->predict($testSamples)); } public function testSaveAndRestoreWithPartialTraining(): void @@ -201,11 +201,11 @@ public function testSaveAndRestoreWithPartialTraining(): void ['a', 'b'] ); - $this->assertEquals('a', $network->predict([1, 0])); - $this->assertEquals('b', $network->predict([0, 1])); + self::assertEquals('a', $network->predict([1, 0])); + self::assertEquals('b', $network->predict([0, 1])); $filename = 'perceptron-test-'.random_int(100, 999).'-'.uniqid('', false); - $filepath = tempnam(sys_get_temp_dir(), $filename); + $filepath = (string) tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($network, $filepath); @@ -216,10 +216,10 @@ public function testSaveAndRestoreWithPartialTraining(): void ['a', 'b'] ); - $this->assertEquals('a', $restoredNetwork->predict([1, 0])); - $this->assertEquals('b', $restoredNetwork->predict([0, 1])); - $this->assertEquals('a', $restoredNetwork->predict([1, 1])); - $this->assertEquals('b', $restoredNetwork->predict([0, 0])); + self::assertEquals('a', $restoredNetwork->predict([1, 0])); + self::assertEquals('b', $restoredNetwork->predict([0, 1])); + self::assertEquals('a', $restoredNetwork->predict([1, 1])); + self::assertEquals('b', $restoredNetwork->predict([0, 0])); } public function testThrowExceptionOnInvalidLayersNumber(): void @@ -249,7 +249,7 @@ public function testOutputWithLabels(): void { $output = (new MLPClassifier(2, [2, 2], ['T', 'F']))->getOutput(); - $this->assertEquals(['T', 'F'], array_keys($output)); + self::assertEquals(['T', 'F'], array_keys($output)); } private function getSynapsesNodes(array $synapses): array diff --git a/tests/Classification/NaiveBayesTest.php b/tests/Classification/NaiveBayesTest.php index 434c9205..4e272618 100644 --- a/tests/Classification/NaiveBayesTest.php +++ b/tests/Classification/NaiveBayesTest.php @@ -18,9 +18,9 @@ public function testPredictSingleSample(): void $classifier = new NaiveBayes(); $classifier->train($samples, $labels); - $this->assertEquals('a', $classifier->predict([3, 1, 1])); - $this->assertEquals('b', $classifier->predict([1, 4, 1])); - $this->assertEquals('c', $classifier->predict([1, 1, 6])); + self::assertEquals('a', $classifier->predict([3, 1, 1])); + self::assertEquals('b', $classifier->predict([1, 4, 1])); + self::assertEquals('c', $classifier->predict([1, 1, 6])); } public function testPredictArrayOfSamples(): void @@ -35,7 +35,7 @@ public function testPredictArrayOfSamples(): void $classifier->train($trainSamples, $trainLabels); $predicted = $classifier->predict($testSamples); - $this->assertEquals($testLabels, $predicted); + self::assertEquals($testLabels, $predicted); // Feed an extra set of training data. $samples = [[1, 1, 6]]; @@ -44,7 +44,7 @@ public function testPredictArrayOfSamples(): void $testSamples = [[1, 1, 6], [5, 1, 1]]; $testLabels = ['d', 'a']; - $this->assertEquals($testLabels, $classifier->predict($testSamples)); + self::assertEquals($testLabels, $classifier->predict($testSamples)); } public function testSaveAndRestore(): void @@ -59,13 +59,13 @@ public function testSaveAndRestore(): void $predicted = $classifier->predict($testSamples); $filename = 'naive-bayes-test-'.random_int(100, 999).'-'.uniqid('', false); - $filepath = tempnam(sys_get_temp_dir(), $filename); + $filepath = (string) tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); $restoredClassifier = $modelManager->restoreFromFile($filepath); - $this->assertEquals($classifier, $restoredClassifier); - $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + self::assertEquals($classifier, $restoredClassifier); + self::assertEquals($predicted, $restoredClassifier->predict($testSamples)); } public function testPredictSimpleNumericLabels(): void @@ -76,9 +76,9 @@ public function testPredictSimpleNumericLabels(): void $classifier = new NaiveBayes(); $classifier->train($samples, $labels); - $this->assertEquals('1996', $classifier->predict([3, 1, 1])); - $this->assertEquals('1997', $classifier->predict([1, 4, 1])); - $this->assertEquals('1998', $classifier->predict([1, 1, 6])); + self::assertEquals('1996', $classifier->predict([3, 1, 1])); + self::assertEquals('1997', $classifier->predict([1, 4, 1])); + self::assertEquals('1998', $classifier->predict([1, 1, 6])); } public function testPredictArrayOfSamplesNumericalLabels(): void @@ -93,7 +93,7 @@ public function testPredictArrayOfSamplesNumericalLabels(): void $classifier->train($trainSamples, $trainLabels); $predicted = $classifier->predict($testSamples); - $this->assertEquals($testLabels, $predicted); + self::assertEquals($testLabels, $predicted); // Feed an extra set of training data. $samples = [[1, 1, 6]]; @@ -102,7 +102,7 @@ public function testPredictArrayOfSamplesNumericalLabels(): void $testSamples = [[1, 1, 6], [5, 1, 1]]; $testLabels = ['1999', '1996']; - $this->assertEquals($testLabels, $classifier->predict($testSamples)); + self::assertEquals($testLabels, $classifier->predict($testSamples)); } public function testSaveAndRestoreNumericLabels(): void @@ -117,12 +117,12 @@ public function testSaveAndRestoreNumericLabels(): void $predicted = $classifier->predict($testSamples); $filename = 'naive-bayes-test-'.random_int(100, 999).'-'.uniqid('', false); - $filepath = tempnam(sys_get_temp_dir(), $filename); + $filepath = (string) tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); $restoredClassifier = $modelManager->restoreFromFile($filepath); - $this->assertEquals($classifier, $restoredClassifier); - $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + self::assertEquals($classifier, $restoredClassifier); + self::assertEquals($predicted, $restoredClassifier->predict($testSamples)); } } diff --git a/tests/Classification/SVCTest.php b/tests/Classification/SVCTest.php index 1ec15418..5fbcff8c 100644 --- a/tests/Classification/SVCTest.php +++ b/tests/Classification/SVCTest.php @@ -19,15 +19,15 @@ public function testPredictSingleSampleWithLinearKernel(): void $classifier = new SVC(Kernel::LINEAR, $cost = 1000); $classifier->train($samples, $labels); - $this->assertEquals('b', $classifier->predict([3, 2])); - $this->assertEquals('b', $classifier->predict([5, 1])); - $this->assertEquals('b', $classifier->predict([4, 3])); - $this->assertEquals('b', $classifier->predict([4, -5])); - - $this->assertEquals('a', $classifier->predict([2, 3])); - $this->assertEquals('a', $classifier->predict([1, 2])); - $this->assertEquals('a', $classifier->predict([1, 5])); - $this->assertEquals('a', $classifier->predict([3, 10])); + self::assertEquals('b', $classifier->predict([3, 2])); + self::assertEquals('b', $classifier->predict([5, 1])); + self::assertEquals('b', $classifier->predict([4, 3])); + self::assertEquals('b', $classifier->predict([4, -5])); + + self::assertEquals('a', $classifier->predict([2, 3])); + self::assertEquals('a', $classifier->predict([1, 2])); + self::assertEquals('a', $classifier->predict([1, 5])); + self::assertEquals('a', $classifier->predict([3, 10])); } public function testPredictArrayOfSamplesWithLinearKernel(): void @@ -42,7 +42,7 @@ public function testPredictArrayOfSamplesWithLinearKernel(): void $classifier->train($trainSamples, $trainLabels); $predictions = $classifier->predict($testSamples); - $this->assertEquals($testLabels, $predictions); + self::assertEquals($testLabels, $predictions); } public function testSaveAndRestore(): void @@ -57,14 +57,14 @@ public function testSaveAndRestore(): void $classifier->train($trainSamples, $trainLabels); $predicted = $classifier->predict($testSamples); - $filepath = tempnam(sys_get_temp_dir(), uniqid('svc-test', true)); + $filepath = (string) tempnam(sys_get_temp_dir(), uniqid('svc-test', true)); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); $restoredClassifier = $modelManager->restoreFromFile($filepath); - $this->assertEquals($classifier, $restoredClassifier); - $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); - $this->assertEquals($predicted, $testLabels); + self::assertEquals($classifier, $restoredClassifier); + self::assertEquals($predicted, $restoredClassifier->predict($testSamples)); + self::assertEquals($predicted, $testLabels); } public function testWithNonDotDecimalLocale(): void @@ -81,8 +81,8 @@ public function testWithNonDotDecimalLocale(): void $classifier = new SVC(Kernel::LINEAR, $cost = 1000); $classifier->train($trainSamples, $trainLabels); - $this->assertEquals($classifier->predict($testSamples), $testLabels); + self::assertEquals($classifier->predict($testSamples), $testLabels); - setlocale(LC_NUMERIC, $currentLocale); + setlocale(LC_NUMERIC, (string) $currentLocale); } } diff --git a/tests/Clustering/DBSCANTest.php b/tests/Clustering/DBSCANTest.php index 3c6d08d8..11322036 100644 --- a/tests/Clustering/DBSCANTest.php +++ b/tests/Clustering/DBSCANTest.php @@ -19,7 +19,7 @@ public function testDBSCANSamplesClustering(): void $dbscan = new DBSCAN($epsilon = 2, $minSamples = 3); - $this->assertEquals($clustered, $dbscan->cluster($samples)); + self::assertEquals($clustered, $dbscan->cluster($samples)); $samples = [[1, 1], [6, 6], [1, -1], [5, 6], [-1, -1], [7, 8], [-1, 1], [7, 7]]; $clustered = [ @@ -29,7 +29,7 @@ public function testDBSCANSamplesClustering(): void $dbscan = new DBSCAN($epsilon = 3, $minSamples = 4); - $this->assertEquals($clustered, $dbscan->cluster($samples)); + self::assertEquals($clustered, $dbscan->cluster($samples)); } public function testDBSCANSamplesClusteringAssociative(): void @@ -57,7 +57,7 @@ public function testDBSCANSamplesClusteringAssociative(): void $dbscan = new DBSCAN($epsilon = 3, $minSamples = 2); - $this->assertEquals($clustered, $dbscan->cluster($samples)); + self::assertEquals($clustered, $dbscan->cluster($samples)); } public function testClusterEpsilonSmall(): void @@ -68,7 +68,7 @@ public function testClusterEpsilonSmall(): void $dbscan = new DBSCAN($epsilon = 0.5, $minSamples = 2); - $this->assertEquals($clustered, $dbscan->cluster($samples)); + self::assertEquals($clustered, $dbscan->cluster($samples)); } public function testClusterEpsilonBoundary(): void @@ -79,7 +79,7 @@ public function testClusterEpsilonBoundary(): void $dbscan = new DBSCAN($epsilon = 1.0, $minSamples = 2); - $this->assertEquals($clustered, $dbscan->cluster($samples)); + self::assertEquals($clustered, $dbscan->cluster($samples)); } public function testClusterEpsilonLarge(): void @@ -91,6 +91,6 @@ public function testClusterEpsilonLarge(): void $dbscan = new DBSCAN($epsilon = 1.5, $minSamples = 2); - $this->assertEquals($clustered, $dbscan->cluster($samples)); + self::assertEquals($clustered, $dbscan->cluster($samples)); } } diff --git a/tests/Clustering/FuzzyCMeansTest.php b/tests/Clustering/FuzzyCMeansTest.php index b2005a1d..638e99c3 100644 --- a/tests/Clustering/FuzzyCMeansTest.php +++ b/tests/Clustering/FuzzyCMeansTest.php @@ -10,40 +10,40 @@ class FuzzyCMeansTest extends TestCase { - public function testFCMSamplesClustering() + public function testFCMSamplesClustering(): void { $samples = [[1, 1], [8, 7], [1, 2], [7, 8], [2, 1], [8, 9]]; $fcm = new FuzzyCMeans(2); $clusters = $fcm->cluster($samples); - $this->assertCount(2, $clusters); + self::assertCount(2, $clusters); foreach ($samples as $index => $sample) { if (in_array($sample, $clusters[0], true) || in_array($sample, $clusters[1], true)) { unset($samples[$index]); } } - $this->assertCount(0, $samples); - - return $fcm; + self::assertCount(0, $samples); } public function testMembershipMatrix(): void { - $fcm = $this->testFCMSamplesClustering(); + $fcm = new FuzzyCMeans(2); + $fcm->cluster([[1, 1], [8, 7], [1, 2], [7, 8], [2, 1], [8, 9]]); + $clusterCount = 2; $sampleCount = 6; $matrix = $fcm->getMembershipMatrix(); - $this->assertCount($clusterCount, $matrix); + self::assertCount($clusterCount, $matrix); foreach ($matrix as $row) { - $this->assertCount($sampleCount, $row); + self::assertCount($sampleCount, $row); } // Transpose of the matrix array_unshift($matrix, null); - $matrix = call_user_func_array('array_map', $matrix); + $matrix = array_map(...$matrix); // All column totals should be equal to 1 (100% membership) foreach ($matrix as $col) { - $this->assertEquals(1, array_sum($col)); + self::assertEquals(1, array_sum($col)); } } diff --git a/tests/Clustering/KMeans/ClusterTest.php b/tests/Clustering/KMeans/ClusterTest.php index 80b98373..2b57d0b8 100644 --- a/tests/Clustering/KMeans/ClusterTest.php +++ b/tests/Clustering/KMeans/ClusterTest.php @@ -26,7 +26,7 @@ public function testToArray(): void $cluster = new Cluster(new Space(2), [1, 2]); $cluster->attach(new Point([1, 1])); - $this->assertSame([ + self::assertSame([ 'centroid' => [1, 2], 'points' => [ [1, 1], @@ -42,8 +42,8 @@ public function testDetach(): void $detachedPoint = $cluster->detach($point); - $this->assertSame($detachedPoint, $point); - $this->assertNotContains($point, $cluster->getPoints()); - $this->assertCount(1, $cluster); + self::assertSame($detachedPoint, $point); + self::assertNotContains($point, $cluster->getPoints()); + self::assertCount(1, $cluster); } } diff --git a/tests/Clustering/KMeansTest.php b/tests/Clustering/KMeansTest.php index ba36bc63..0265f7d5 100644 --- a/tests/Clustering/KMeansTest.php +++ b/tests/Clustering/KMeansTest.php @@ -17,7 +17,7 @@ public function testKMeansSamplesClustering(): void $kmeans = new KMeans(2); $clusters = $kmeans->cluster($samples); - $this->assertCount(2, $clusters); + self::assertCount(2, $clusters); foreach ($samples as $index => $sample) { if (in_array($sample, $clusters[0], true) || in_array($sample, $clusters[1], true)) { @@ -25,7 +25,7 @@ public function testKMeansSamplesClustering(): void } } - $this->assertCount(0, $samples); + self::assertCount(0, $samples); } public function testKMeansSamplesLabeledClustering(): void @@ -42,16 +42,16 @@ public function testKMeansSamplesLabeledClustering(): void $kmeans = new KMeans(2); $clusters = $kmeans->cluster($samples); - $this->assertCount(2, $clusters); + self::assertCount(2, $clusters); foreach ($samples as $index => $sample) { if (in_array($sample, $clusters[0], true) || in_array($sample, $clusters[1], true)) { - $this->assertArrayHasKey($index, $clusters[0] + $clusters[1]); + self::assertArrayHasKey($index, $clusters[0] + $clusters[1]); unset($samples[$index]); } } - $this->assertCount(0, $samples); + self::assertCount(0, $samples); } public function testKMeansInitializationMethods(): void @@ -71,11 +71,11 @@ public function testKMeansInitializationMethods(): void $kmeans = new KMeans(4, KMeans::INIT_KMEANS_PLUS_PLUS); $clusters = $kmeans->cluster($samples); - $this->assertCount(4, $clusters); + self::assertCount(4, $clusters); $kmeans = new KMeans(4, KMeans::INIT_RANDOM); $clusters = $kmeans->cluster($samples); - $this->assertCount(4, $clusters); + self::assertCount(4, $clusters); } public function testThrowExceptionOnInvalidClusterNumber(): void diff --git a/tests/CrossValidation/RandomSplitTest.php b/tests/CrossValidation/RandomSplitTest.php index 65e8d8b9..88928cca 100644 --- a/tests/CrossValidation/RandomSplitTest.php +++ b/tests/CrossValidation/RandomSplitTest.php @@ -32,13 +32,13 @@ public function testDatasetRandomSplitWithoutSeed(): void $randomSplit = new RandomSplit($dataset, 0.5); - $this->assertCount(2, $randomSplit->getTestSamples()); - $this->assertCount(2, $randomSplit->getTrainSamples()); + self::assertCount(2, $randomSplit->getTestSamples()); + self::assertCount(2, $randomSplit->getTrainSamples()); $randomSplit2 = new RandomSplit($dataset, 0.25); - $this->assertCount(1, $randomSplit2->getTestSamples()); - $this->assertCount(3, $randomSplit2->getTrainSamples()); + self::assertCount(1, $randomSplit2->getTestSamples()); + self::assertCount(3, $randomSplit2->getTrainSamples()); } public function testDatasetRandomSplitWithSameSeed(): void @@ -53,10 +53,10 @@ public function testDatasetRandomSplitWithSameSeed(): void $randomSplit1 = new RandomSplit($dataset, 0.5, $seed); $randomSplit2 = new RandomSplit($dataset, 0.5, $seed); - $this->assertEquals($randomSplit1->getTestLabels(), $randomSplit2->getTestLabels()); - $this->assertEquals($randomSplit1->getTestSamples(), $randomSplit2->getTestSamples()); - $this->assertEquals($randomSplit1->getTrainLabels(), $randomSplit2->getTrainLabels()); - $this->assertEquals($randomSplit1->getTrainSamples(), $randomSplit2->getTrainSamples()); + self::assertEquals($randomSplit1->getTestLabels(), $randomSplit2->getTestLabels()); + self::assertEquals($randomSplit1->getTestSamples(), $randomSplit2->getTestSamples()); + self::assertEquals($randomSplit1->getTrainLabels(), $randomSplit2->getTrainLabels()); + self::assertEquals($randomSplit1->getTrainSamples(), $randomSplit2->getTrainSamples()); } public function testDatasetRandomSplitWithDifferentSeed(): void @@ -69,10 +69,10 @@ public function testDatasetRandomSplitWithDifferentSeed(): void $randomSplit1 = new RandomSplit($dataset, 0.5, 4321); $randomSplit2 = new RandomSplit($dataset, 0.5, 1234); - $this->assertNotEquals($randomSplit1->getTestLabels(), $randomSplit2->getTestLabels()); - $this->assertNotEquals($randomSplit1->getTestSamples(), $randomSplit2->getTestSamples()); - $this->assertNotEquals($randomSplit1->getTrainLabels(), $randomSplit2->getTrainLabels()); - $this->assertNotEquals($randomSplit1->getTrainSamples(), $randomSplit2->getTrainSamples()); + self::assertNotEquals($randomSplit1->getTestLabels(), $randomSplit2->getTestLabels()); + self::assertNotEquals($randomSplit1->getTestSamples(), $randomSplit2->getTestSamples()); + self::assertNotEquals($randomSplit1->getTrainLabels(), $randomSplit2->getTrainLabels()); + self::assertNotEquals($randomSplit1->getTrainSamples(), $randomSplit2->getTrainSamples()); } public function testRandomSplitCorrectSampleAndLabelPosition(): void @@ -84,9 +84,9 @@ public function testRandomSplitCorrectSampleAndLabelPosition(): void $randomSplit = new RandomSplit($dataset, 0.5); - $this->assertEquals($randomSplit->getTestSamples()[0][0], $randomSplit->getTestLabels()[0]); - $this->assertEquals($randomSplit->getTestSamples()[1][0], $randomSplit->getTestLabels()[1]); - $this->assertEquals($randomSplit->getTrainSamples()[0][0], $randomSplit->getTrainLabels()[0]); - $this->assertEquals($randomSplit->getTrainSamples()[1][0], $randomSplit->getTrainLabels()[1]); + self::assertEquals($randomSplit->getTestSamples()[0][0], $randomSplit->getTestLabels()[0]); + self::assertEquals($randomSplit->getTestSamples()[1][0], $randomSplit->getTestLabels()[1]); + self::assertEquals($randomSplit->getTrainSamples()[0][0], $randomSplit->getTrainLabels()[0]); + self::assertEquals($randomSplit->getTrainSamples()[1][0], $randomSplit->getTrainLabels()[1]); } } diff --git a/tests/CrossValidation/StratifiedRandomSplitTest.php b/tests/CrossValidation/StratifiedRandomSplitTest.php index 5309dc69..909f15fb 100644 --- a/tests/CrossValidation/StratifiedRandomSplitTest.php +++ b/tests/CrossValidation/StratifiedRandomSplitTest.php @@ -19,13 +19,13 @@ public function testDatasetStratifiedRandomSplitWithEvenDistribution(): void $split = new StratifiedRandomSplit($dataset, 0.5); - $this->assertEquals(2, $this->countSamplesByTarget($split->getTestLabels(), 'a')); - $this->assertEquals(2, $this->countSamplesByTarget($split->getTestLabels(), 'b')); + self::assertEquals(2, $this->countSamplesByTarget($split->getTestLabels(), 'a')); + self::assertEquals(2, $this->countSamplesByTarget($split->getTestLabels(), 'b')); $split = new StratifiedRandomSplit($dataset, 0.25); - $this->assertEquals(1, $this->countSamplesByTarget($split->getTestLabels(), 'a')); - $this->assertEquals(1, $this->countSamplesByTarget($split->getTestLabels(), 'b')); + self::assertEquals(1, $this->countSamplesByTarget($split->getTestLabels(), 'a')); + self::assertEquals(1, $this->countSamplesByTarget($split->getTestLabels(), 'b')); } public function testDatasetStratifiedRandomSplitWithEvenDistributionAndNumericTargets(): void @@ -37,16 +37,19 @@ public function testDatasetStratifiedRandomSplitWithEvenDistributionAndNumericTa $split = new StratifiedRandomSplit($dataset, 0.5); - $this->assertEquals(2, $this->countSamplesByTarget($split->getTestLabels(), 1)); - $this->assertEquals(2, $this->countSamplesByTarget($split->getTestLabels(), 2)); + self::assertEquals(2, $this->countSamplesByTarget($split->getTestLabels(), 1)); + self::assertEquals(2, $this->countSamplesByTarget($split->getTestLabels(), 2)); $split = new StratifiedRandomSplit($dataset, 0.25); - $this->assertEquals(1, $this->countSamplesByTarget($split->getTestLabels(), 1)); - $this->assertEquals(1, $this->countSamplesByTarget($split->getTestLabels(), 2)); + self::assertEquals(1, $this->countSamplesByTarget($split->getTestLabels(), 1)); + self::assertEquals(1, $this->countSamplesByTarget($split->getTestLabels(), 2)); } - private function countSamplesByTarget($splitTargets, $countTarget): int + /** + * @param string|int $countTarget + */ + private function countSamplesByTarget(array $splitTargets, $countTarget): int { $count = 0; foreach ($splitTargets as $target) { diff --git a/tests/Dataset/ArrayDatasetTest.php b/tests/Dataset/ArrayDatasetTest.php index 04319596..98792c71 100644 --- a/tests/Dataset/ArrayDatasetTest.php +++ b/tests/Dataset/ArrayDatasetTest.php @@ -23,8 +23,8 @@ public function testArrayDataset(): void $labels = ['a', 'a', 'b', 'b'] ); - $this->assertEquals($samples, $dataset->getSamples()); - $this->assertEquals($labels, $dataset->getTargets()); + self::assertEquals($samples, $dataset->getSamples()); + self::assertEquals($labels, $dataset->getTargets()); } public function testRemoveColumns(): void @@ -35,6 +35,6 @@ public function testRemoveColumns(): void ); $dataset->removeColumns([0, 2]); - $this->assertEquals([[2, 4], [3, 5], [4, 6], [5, 7]], $dataset->getSamples()); + self::assertEquals([[2, 4], [3, 5], [4, 6], [5, 7]], $dataset->getSamples()); } } diff --git a/tests/Dataset/CsvDatasetTest.php b/tests/Dataset/CsvDatasetTest.php index 90eb1d09..a1787262 100644 --- a/tests/Dataset/CsvDatasetTest.php +++ b/tests/Dataset/CsvDatasetTest.php @@ -22,8 +22,8 @@ public function testSampleCsvDatasetWithHeaderRow(): void $dataset = new CsvDataset($filePath, 2, true); - $this->assertCount(10, $dataset->getSamples()); - $this->assertCount(10, $dataset->getTargets()); + self::assertCount(10, $dataset->getSamples()); + self::assertCount(10, $dataset->getTargets()); } public function testSampleCsvDatasetWithoutHeaderRow(): void @@ -32,8 +32,8 @@ public function testSampleCsvDatasetWithoutHeaderRow(): void $dataset = new CsvDataset($filePath, 2, false); - $this->assertCount(11, $dataset->getSamples()); - $this->assertCount(11, $dataset->getTargets()); + self::assertCount(11, $dataset->getSamples()); + self::assertCount(11, $dataset->getTargets()); } public function testLongCsvDataset(): void @@ -42,7 +42,7 @@ public function testLongCsvDataset(): void $dataset = new CsvDataset($filePath, 1000, false); - $this->assertCount(1000, $dataset->getSamples()[0]); - $this->assertEquals('label', $dataset->getTargets()[0]); + self::assertCount(1000, $dataset->getSamples()[0]); + self::assertEquals('label', $dataset->getTargets()[0]); } } diff --git a/tests/Dataset/Demo/GlassDatasetTest.php b/tests/Dataset/Demo/GlassDatasetTest.php index 0d873e65..3ef182a8 100644 --- a/tests/Dataset/Demo/GlassDatasetTest.php +++ b/tests/Dataset/Demo/GlassDatasetTest.php @@ -14,10 +14,10 @@ public function testLoadingWineDataset(): void $glass = new GlassDataset(); // whole dataset - $this->assertCount(214, $glass->getSamples()); - $this->assertCount(214, $glass->getTargets()); + self::assertCount(214, $glass->getSamples()); + self::assertCount(214, $glass->getTargets()); // one sample features count - $this->assertCount(9, $glass->getSamples()[0]); + self::assertCount(9, $glass->getSamples()[0]); } } diff --git a/tests/Dataset/Demo/IrisDatasetTest.php b/tests/Dataset/Demo/IrisDatasetTest.php index 4fb2ee23..171bc38b 100644 --- a/tests/Dataset/Demo/IrisDatasetTest.php +++ b/tests/Dataset/Demo/IrisDatasetTest.php @@ -14,10 +14,10 @@ public function testLoadingIrisDataset(): void $iris = new IrisDataset(); // whole dataset - $this->assertCount(150, $iris->getSamples()); - $this->assertCount(150, $iris->getTargets()); + self::assertCount(150, $iris->getSamples()); + self::assertCount(150, $iris->getTargets()); // one sample features count - $this->assertCount(4, $iris->getSamples()[0]); + self::assertCount(4, $iris->getSamples()[0]); } } diff --git a/tests/Dataset/Demo/WineDatasetTest.php b/tests/Dataset/Demo/WineDatasetTest.php index 1b7a982f..01192941 100644 --- a/tests/Dataset/Demo/WineDatasetTest.php +++ b/tests/Dataset/Demo/WineDatasetTest.php @@ -14,10 +14,10 @@ public function testLoadingWineDataset(): void $wine = new WineDataset(); // whole dataset - $this->assertCount(178, $wine->getSamples()); - $this->assertCount(178, $wine->getTargets()); + self::assertCount(178, $wine->getSamples()); + self::assertCount(178, $wine->getTargets()); // one sample features count - $this->assertCount(13, $wine->getSamples()[0]); + self::assertCount(13, $wine->getSamples()[0]); } } diff --git a/tests/Dataset/FilesDatasetTest.php b/tests/Dataset/FilesDatasetTest.php index 08b568d9..ec30f91b 100644 --- a/tests/Dataset/FilesDatasetTest.php +++ b/tests/Dataset/FilesDatasetTest.php @@ -22,22 +22,22 @@ public function testLoadFilesDatasetWithBBCData(): void $dataset = new FilesDataset($rootPath); - $this->assertCount(50, $dataset->getSamples()); - $this->assertCount(50, $dataset->getTargets()); + self::assertCount(50, $dataset->getSamples()); + self::assertCount(50, $dataset->getTargets()); $targets = ['business', 'entertainment', 'politics', 'sport', 'tech']; - $this->assertEquals($targets, array_values(array_unique($dataset->getTargets()))); + self::assertEquals($targets, array_values(array_unique($dataset->getTargets()))); $firstSample = file_get_contents($rootPath.'/business/001.txt'); - $this->assertEquals($firstSample, $dataset->getSamples()[0][0]); + self::assertEquals($firstSample, $dataset->getSamples()[0][0]); $firstTarget = 'business'; - $this->assertEquals($firstTarget, $dataset->getTargets()[0]); + self::assertEquals($firstTarget, $dataset->getTargets()[0]); $lastSample = file_get_contents($rootPath.'/tech/010.txt'); - $this->assertEquals($lastSample, $dataset->getSamples()[49][0]); + self::assertEquals($lastSample, $dataset->getSamples()[49][0]); $lastTarget = 'tech'; - $this->assertEquals($lastTarget, $dataset->getTargets()[49]); + self::assertEquals($lastTarget, $dataset->getTargets()[49]); } } diff --git a/tests/Dataset/SvmDatasetTest.php b/tests/Dataset/SvmDatasetTest.php index 700055c8..437e3d20 100644 --- a/tests/Dataset/SvmDatasetTest.php +++ b/tests/Dataset/SvmDatasetTest.php @@ -21,8 +21,8 @@ public function testSvmDatasetEmpty(): void $expectedTargets = [ ]; - $this->assertEquals($expectedSamples, $dataset->getSamples()); - $this->assertEquals($expectedTargets, $dataset->getTargets()); + self::assertEquals($expectedSamples, $dataset->getSamples()); + self::assertEquals($expectedTargets, $dataset->getTargets()); } public function testSvmDataset1x1(): void @@ -37,8 +37,8 @@ public function testSvmDataset1x1(): void 0, ]; - $this->assertEquals($expectedSamples, $dataset->getSamples()); - $this->assertEquals($expectedTargets, $dataset->getTargets()); + self::assertEquals($expectedSamples, $dataset->getSamples()); + self::assertEquals($expectedTargets, $dataset->getTargets()); } public function testSvmDataset3x1(): void @@ -57,8 +57,8 @@ public function testSvmDataset3x1(): void 1, ]; - $this->assertEquals($expectedSamples, $dataset->getSamples()); - $this->assertEquals($expectedTargets, $dataset->getTargets()); + self::assertEquals($expectedSamples, $dataset->getSamples()); + self::assertEquals($expectedTargets, $dataset->getTargets()); } public function testSvmDataset3x4(): void @@ -77,8 +77,8 @@ public function testSvmDataset3x4(): void 0, ]; - $this->assertEquals($expectedSamples, $dataset->getSamples()); - $this->assertEquals($expectedTargets, $dataset->getTargets()); + self::assertEquals($expectedSamples, $dataset->getSamples()); + self::assertEquals($expectedTargets, $dataset->getTargets()); } public function testSvmDatasetSparse(): void @@ -95,8 +95,8 @@ public function testSvmDatasetSparse(): void 1, ]; - $this->assertEquals($expectedSamples, $dataset->getSamples()); - $this->assertEquals($expectedTargets, $dataset->getTargets()); + self::assertEquals($expectedSamples, $dataset->getSamples()); + self::assertEquals($expectedTargets, $dataset->getTargets()); } public function testSvmDatasetComments(): void @@ -113,8 +113,8 @@ public function testSvmDatasetComments(): void 1, ]; - $this->assertEquals($expectedSamples, $dataset->getSamples()); - $this->assertEquals($expectedTargets, $dataset->getTargets()); + self::assertEquals($expectedSamples, $dataset->getSamples()); + self::assertEquals($expectedTargets, $dataset->getTargets()); } public function testSvmDatasetTabs(): void @@ -129,8 +129,8 @@ public function testSvmDatasetTabs(): void 1, ]; - $this->assertEquals($expectedSamples, $dataset->getSamples()); - $this->assertEquals($expectedTargets, $dataset->getTargets()); + self::assertEquals($expectedSamples, $dataset->getSamples()); + self::assertEquals($expectedTargets, $dataset->getTargets()); } public function testSvmDatasetMissingFile(): void diff --git a/tests/DimensionReduction/KernelPCATest.php b/tests/DimensionReduction/KernelPCATest.php index cca0d537..cd04260b 100644 --- a/tests/DimensionReduction/KernelPCATest.php +++ b/tests/DimensionReduction/KernelPCATest.php @@ -40,7 +40,7 @@ public function testKernelPCA(): void // during the calculation of eigenValues, we have to compare // absolute value of the values array_map(function ($val1, $val2) use ($epsilon): void { - $this->assertEquals(abs($val1), abs($val2), '', $epsilon); + self::assertEquals(abs($val1), abs($val2), '', $epsilon); }, $transformed, $reducedData); // Fitted KernelPCA object can also transform an arbitrary sample of the @@ -48,7 +48,7 @@ public function testKernelPCA(): void $newData = [1.25, 2.25]; $newTransformed = [0.18956227539216]; $newTransformed2 = $kpca->transform($newData); - $this->assertEquals(abs($newTransformed[0]), abs($newTransformed2[0]), '', $epsilon); + self::assertEquals(abs($newTransformed[0]), abs($newTransformed2[0]), '', $epsilon); } public function testKernelPCAThrowWhenKernelInvalid(): void diff --git a/tests/DimensionReduction/LDATest.php b/tests/DimensionReduction/LDATest.php index 79e925f6..101d2f96 100644 --- a/tests/DimensionReduction/LDATest.php +++ b/tests/DimensionReduction/LDATest.php @@ -51,7 +51,7 @@ public function testLDA(): void // absolute value of the values $row1 = array_map('abs', $row1); $row2 = array_map('abs', $row2); - $this->assertEquals($row1, $row2, '', $epsilon); + self::assertEquals($row1, $row2, '', $epsilon); }; array_map($check, $control, $transformed2); diff --git a/tests/DimensionReduction/PCATest.php b/tests/DimensionReduction/PCATest.php index ba25268b..ebb5b013 100644 --- a/tests/DimensionReduction/PCATest.php +++ b/tests/DimensionReduction/PCATest.php @@ -42,7 +42,7 @@ public function testPCA(): void // during the calculation of eigenValues, we have to compare // absolute value of the values array_map(function ($val1, $val2) use ($epsilon): void { - $this->assertEquals(abs($val1), abs($val2), '', $epsilon); + self::assertEquals(abs($val1), abs($val2), '', $epsilon); }, $transformed, $reducedData); // Test fitted PCA object to transform an arbitrary sample of the @@ -52,7 +52,7 @@ public function testPCA(): void $newRow2 = $pca->transform($row); array_map(function ($val1, $val2) use ($epsilon): void { - $this->assertEquals(abs($val1), abs($val2), '', $epsilon); + self::assertEquals(abs($val1), abs($val2), '', $epsilon); }, $newRow, $newRow2); } } diff --git a/tests/FeatureExtraction/StopWordsTest.php b/tests/FeatureExtraction/StopWordsTest.php index 5780c393..112fa0f4 100644 --- a/tests/FeatureExtraction/StopWordsTest.php +++ b/tests/FeatureExtraction/StopWordsTest.php @@ -14,13 +14,13 @@ public function testCustomStopWords(): void { $stopWords = new StopWords(['lorem', 'ipsum', 'dolor']); - $this->assertTrue($stopWords->isStopWord('lorem')); - $this->assertTrue($stopWords->isStopWord('ipsum')); - $this->assertTrue($stopWords->isStopWord('dolor')); + self::assertTrue($stopWords->isStopWord('lorem')); + self::assertTrue($stopWords->isStopWord('ipsum')); + self::assertTrue($stopWords->isStopWord('dolor')); - $this->assertFalse($stopWords->isStopWord('consectetur')); - $this->assertFalse($stopWords->isStopWord('adipiscing')); - $this->assertFalse($stopWords->isStopWord('amet')); + self::assertFalse($stopWords->isStopWord('consectetur')); + self::assertFalse($stopWords->isStopWord('adipiscing')); + self::assertFalse($stopWords->isStopWord('amet')); } public function testThrowExceptionOnInvalidLanguage(): void @@ -33,23 +33,23 @@ public function testEnglishStopWords(): void { $stopWords = StopWords::factory('English'); - $this->assertTrue($stopWords->isStopWord('again')); - $this->assertFalse($stopWords->isStopWord('strategy')); + self::assertTrue($stopWords->isStopWord('again')); + self::assertFalse($stopWords->isStopWord('strategy')); } public function testPolishStopWords(): void { $stopWords = StopWords::factory('Polish'); - $this->assertTrue($stopWords->isStopWord('wam')); - $this->assertFalse($stopWords->isStopWord('transhumanizm')); + self::assertTrue($stopWords->isStopWord('wam')); + self::assertFalse($stopWords->isStopWord('transhumanizm')); } public function testFrenchStopWords(): void { $stopWords = StopWords::factory('French'); - $this->assertTrue($stopWords->isStopWord('alors')); - $this->assertFalse($stopWords->isStopWord('carte')); + self::assertTrue($stopWords->isStopWord('alors')); + self::assertFalse($stopWords->isStopWord('carte')); } } diff --git a/tests/FeatureExtraction/TfIdfTransformerTest.php b/tests/FeatureExtraction/TfIdfTransformerTest.php index 30bc0e57..b0cf9f63 100644 --- a/tests/FeatureExtraction/TfIdfTransformerTest.php +++ b/tests/FeatureExtraction/TfIdfTransformerTest.php @@ -54,6 +54,6 @@ public function testTfIdfTransformation(): void $transformer = new TfIdfTransformer($samples); $transformer->transform($samples); - $this->assertEquals($tfIdfSamples, $samples, '', 0.001); + self::assertEquals($tfIdfSamples, $samples, '', 0.001); } } diff --git a/tests/FeatureExtraction/TokenCountVectorizerTest.php b/tests/FeatureExtraction/TokenCountVectorizerTest.php index aaba5fc1..dff9436a 100644 --- a/tests/FeatureExtraction/TokenCountVectorizerTest.php +++ b/tests/FeatureExtraction/TokenCountVectorizerTest.php @@ -74,10 +74,10 @@ public function testTransformationWithWhitespaceTokenizer(): void $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer()); $vectorizer->fit($samples); - $this->assertSame($vocabulary, $vectorizer->getVocabulary()); + self::assertSame($vocabulary, $vectorizer->getVocabulary()); $vectorizer->transform($samples); - $this->assertSame($tokensCounts, $samples); + self::assertSame($tokensCounts, $samples); } public function testTransformationWithMinimumDocumentTokenCountFrequency(): void @@ -132,10 +132,10 @@ public function testTransformationWithMinimumDocumentTokenCountFrequency(): void $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), null, 0.5); $vectorizer->fit($samples); - $this->assertSame($vocabulary, $vectorizer->getVocabulary()); + self::assertSame($vocabulary, $vectorizer->getVocabulary()); $vectorizer->transform($samples); - $this->assertSame($tokensCounts, $samples); + self::assertSame($tokensCounts, $samples); // word at least once in all samples $samples = [ @@ -184,7 +184,7 @@ public function testTransformationWithMinimumDocumentTokenCountFrequency(): void $vectorizer->fit($samples); $vectorizer->transform($samples); - $this->assertSame($tokensCounts, $samples); + self::assertSame($tokensCounts, $samples); } public function testTransformationWithStopWords(): void @@ -246,9 +246,9 @@ public function testTransformationWithStopWords(): void $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), $stopWords); $vectorizer->fit($samples); - $this->assertSame($vocabulary, $vectorizer->getVocabulary()); + self::assertSame($vocabulary, $vectorizer->getVocabulary()); $vectorizer->transform($samples); - $this->assertSame($tokensCounts, $samples); + self::assertSame($tokensCounts, $samples); } } diff --git a/tests/Helper/Optimizer/ConjugateGradientTest.php b/tests/Helper/Optimizer/ConjugateGradientTest.php index 09c250c8..2080019a 100644 --- a/tests/Helper/Optimizer/ConjugateGradientTest.php +++ b/tests/Helper/Optimizer/ConjugateGradientTest.php @@ -33,7 +33,7 @@ public function testRunOptimization(): void $theta = $optimizer->runOptimization($samples, $targets, $callback); - $this->assertEquals([-1, 2], $theta, '', 0.1); + self::assertEquals([-1, 2], $theta, '', 0.1); } public function testRunOptimizationWithCustomInitialTheta(): void @@ -61,7 +61,7 @@ public function testRunOptimizationWithCustomInitialTheta(): void $theta = $optimizer->runOptimization($samples, $targets, $callback); - $this->assertEquals([-1.087708, 2.212034], $theta, '', 0.000001); + self::assertEquals([-1.087708, 2.212034], $theta, '', 0.000001); } public function testRunOptimization2Dim(): void @@ -89,7 +89,7 @@ public function testRunOptimization2Dim(): void $theta = $optimizer->runOptimization($samples, $targets, $callback); - $this->assertEquals([-1, 2, -3], $theta, '', 0.1); + self::assertEquals([-1, 2, -3], $theta, '', 0.1); } public function testThrowExceptionOnInvalidTheta(): void diff --git a/tests/Helper/Optimizer/GDTest.php b/tests/Helper/Optimizer/GDTest.php index c68e3185..ecbd5152 100644 --- a/tests/Helper/Optimizer/GDTest.php +++ b/tests/Helper/Optimizer/GDTest.php @@ -32,7 +32,7 @@ public function testRunOptimization(): void $theta = $optimizer->runOptimization($samples, $targets, $callback); - $this->assertEquals([-1, 2], $theta, '', 0.1); + self::assertEquals([-1, 2], $theta, '', 0.1); } public function testRunOptimization2Dim(): void @@ -60,6 +60,6 @@ public function testRunOptimization2Dim(): void $theta = $optimizer->runOptimization($samples, $targets, $callback); - $this->assertEquals([-1, 2, -3], $theta, '', 0.1); + self::assertEquals([-1, 2, -3], $theta, '', 0.1); } } diff --git a/tests/Helper/Optimizer/OptimizerTest.php b/tests/Helper/Optimizer/OptimizerTest.php index 22efdd22..97af2d24 100644 --- a/tests/Helper/Optimizer/OptimizerTest.php +++ b/tests/Helper/Optimizer/OptimizerTest.php @@ -26,9 +26,9 @@ public function testSetTheta(): void $optimizer = $this->getMockForAbstractClass(Optimizer::class, [2]); $object = $optimizer->setTheta([0.3, 1]); - $theta = $this->getObjectAttribute($optimizer, 'theta'); + $theta = self::getObjectAttribute($optimizer, 'theta'); - $this->assertSame($object, $optimizer); - $this->assertSame([0.3, 1], $theta); + self::assertSame($object, $optimizer); + self::assertSame([0.3, 1], $theta); } } diff --git a/tests/Helper/Optimizer/StochasticGDTest.php b/tests/Helper/Optimizer/StochasticGDTest.php index 6f6e469b..ba3430bf 100644 --- a/tests/Helper/Optimizer/StochasticGDTest.php +++ b/tests/Helper/Optimizer/StochasticGDTest.php @@ -32,7 +32,7 @@ public function testRunOptimization(): void $theta = $optimizer->runOptimization($samples, $targets, $callback); - $this->assertEquals([-1, 2], $theta, '', 0.1); + self::assertEquals([-1, 2], $theta, '', 0.1); } public function testRunOptimization2Dim(): void @@ -60,6 +60,6 @@ public function testRunOptimization2Dim(): void $theta = $optimizer->runOptimization($samples, $targets, $callback); - $this->assertEquals([-1, 2, -3], $theta, '', 0.1); + self::assertEquals([-1, 2, -3], $theta, '', 0.1); } } diff --git a/tests/Math/ComparisonTest.php b/tests/Math/ComparisonTest.php index 0118ee46..338055b6 100644 --- a/tests/Math/ComparisonTest.php +++ b/tests/Math/ComparisonTest.php @@ -20,7 +20,7 @@ public function testResult($a, $b, string $operator, bool $expected): void { $result = Comparison::compare($a, $b, $operator); - $this->assertEquals($expected, $result); + self::assertEquals($expected, $result); } public function testThrowExceptionWhenOperatorIsInvalid(): void diff --git a/tests/Math/Distance/ChebyshevTest.php b/tests/Math/Distance/ChebyshevTest.php index 1a437314..a59edbf9 100644 --- a/tests/Math/Distance/ChebyshevTest.php +++ b/tests/Math/Distance/ChebyshevTest.php @@ -36,7 +36,7 @@ public function testCalculateDistanceForOneDimension(): void $expectedDistance = 2; $actualDistance = $this->distanceMetric->distance($a, $b); - $this->assertEquals($expectedDistance, $actualDistance); + self::assertEquals($expectedDistance, $actualDistance); } public function testCalculateDistanceForTwoDimensions(): void @@ -47,7 +47,7 @@ public function testCalculateDistanceForTwoDimensions(): void $expectedDistance = 2; $actualDistance = $this->distanceMetric->distance($a, $b); - $this->assertEquals($expectedDistance, $actualDistance); + self::assertEquals($expectedDistance, $actualDistance); } public function testCalculateDistanceForThreeDimensions(): void @@ -58,6 +58,6 @@ public function testCalculateDistanceForThreeDimensions(): void $expectedDistance = 5; $actualDistance = $this->distanceMetric->distance($a, $b); - $this->assertEquals($expectedDistance, $actualDistance); + self::assertEquals($expectedDistance, $actualDistance); } } diff --git a/tests/Math/Distance/EuclideanTest.php b/tests/Math/Distance/EuclideanTest.php index 4be96d31..979a3785 100644 --- a/tests/Math/Distance/EuclideanTest.php +++ b/tests/Math/Distance/EuclideanTest.php @@ -36,7 +36,7 @@ public function testCalculateDistanceForOneDimension(): void $expectedDistance = 2; $actualDistance = $this->distanceMetric->distance($a, $b); - $this->assertEquals($expectedDistance, $actualDistance); + self::assertEquals($expectedDistance, $actualDistance); } public function testCalculateDistanceForTwoDimensions(): void @@ -47,7 +47,7 @@ public function testCalculateDistanceForTwoDimensions(): void $expectedDistance = 2.2360679774998; $actualDistance = $this->distanceMetric->distance($a, $b); - $this->assertEquals($expectedDistance, $actualDistance); + self::assertEquals($expectedDistance, $actualDistance); } public function testCalculateDistanceForThreeDimensions(): void @@ -58,6 +58,6 @@ public function testCalculateDistanceForThreeDimensions(): void $expectedDistance = 6.7082039324993694; $actualDistance = $this->distanceMetric->distance($a, $b); - $this->assertEquals($expectedDistance, $actualDistance); + self::assertEquals($expectedDistance, $actualDistance); } } diff --git a/tests/Math/Distance/ManhattanTest.php b/tests/Math/Distance/ManhattanTest.php index 1dd5e46a..c189f263 100644 --- a/tests/Math/Distance/ManhattanTest.php +++ b/tests/Math/Distance/ManhattanTest.php @@ -36,7 +36,7 @@ public function testCalculateDistanceForOneDimension(): void $expectedDistance = 2; $actualDistance = $this->distanceMetric->distance($a, $b); - $this->assertEquals($expectedDistance, $actualDistance); + self::assertEquals($expectedDistance, $actualDistance); } public function testCalculateDistanceForTwoDimensions(): void @@ -47,7 +47,7 @@ public function testCalculateDistanceForTwoDimensions(): void $expectedDistance = 3; $actualDistance = $this->distanceMetric->distance($a, $b); - $this->assertEquals($expectedDistance, $actualDistance); + self::assertEquals($expectedDistance, $actualDistance); } public function testCalculateDistanceForThreeDimensions(): void @@ -58,6 +58,6 @@ public function testCalculateDistanceForThreeDimensions(): void $expectedDistance = 11; $actualDistance = $this->distanceMetric->distance($a, $b); - $this->assertEquals($expectedDistance, $actualDistance); + self::assertEquals($expectedDistance, $actualDistance); } } diff --git a/tests/Math/Distance/MinkowskiTest.php b/tests/Math/Distance/MinkowskiTest.php index 558c31f4..770bf153 100644 --- a/tests/Math/Distance/MinkowskiTest.php +++ b/tests/Math/Distance/MinkowskiTest.php @@ -36,7 +36,7 @@ public function testCalculateDistanceForOneDimension(): void $expectedDistance = 2; $actualDistance = $this->distanceMetric->distance($a, $b); - $this->assertEquals($expectedDistance, $actualDistance); + self::assertEquals($expectedDistance, $actualDistance); } public function testCalculateDistanceForTwoDimensions(): void @@ -47,7 +47,7 @@ public function testCalculateDistanceForTwoDimensions(): void $expectedDistance = 2.080; $actualDistance = $this->distanceMetric->distance($a, $b); - $this->assertEquals($expectedDistance, $actualDistance, '', $delta = 0.001); + self::assertEquals($expectedDistance, $actualDistance, '', $delta = 0.001); } public function testCalculateDistanceForThreeDimensions(): void @@ -58,7 +58,7 @@ public function testCalculateDistanceForThreeDimensions(): void $expectedDistance = 5.819; $actualDistance = $this->distanceMetric->distance($a, $b); - $this->assertEquals($expectedDistance, $actualDistance, '', $delta = 0.001); + self::assertEquals($expectedDistance, $actualDistance, '', $delta = 0.001); } public function testCalculateDistanceForThreeDimensionsWithDifferentLambda(): void @@ -71,6 +71,6 @@ public function testCalculateDistanceForThreeDimensionsWithDifferentLambda(): vo $expectedDistance = 5.300; $actualDistance = $distanceMetric->distance($a, $b); - $this->assertEquals($expectedDistance, $actualDistance, '', $delta = 0.001); + self::assertEquals($expectedDistance, $actualDistance, '', $delta = 0.001); } } diff --git a/tests/Math/Kernel/RBFTest.php b/tests/Math/Kernel/RBFTest.php index 08f795d8..bf24aab5 100644 --- a/tests/Math/Kernel/RBFTest.php +++ b/tests/Math/Kernel/RBFTest.php @@ -13,14 +13,14 @@ public function testComputeRBFKernelFunction(): void { $rbf = new RBF($gamma = 0.001); - $this->assertEquals(1, $rbf->compute([1, 2], [1, 2])); - $this->assertEquals(0.97336, $rbf->compute([1, 2, 3], [4, 5, 6]), '', $delta = 0.0001); - $this->assertEquals(0.00011, $rbf->compute([4, 5], [1, 100]), '', $delta = 0.0001); + self::assertEquals(1, $rbf->compute([1, 2], [1, 2])); + self::assertEquals(0.97336, $rbf->compute([1, 2, 3], [4, 5, 6]), '', $delta = 0.0001); + self::assertEquals(0.00011, $rbf->compute([4, 5], [1, 100]), '', $delta = 0.0001); $rbf = new RBF($gamma = 0.2); - $this->assertEquals(1, $rbf->compute([1, 2], [1, 2])); - $this->assertEquals(0.00451, $rbf->compute([1, 2, 3], [4, 5, 6]), '', $delta = 0.0001); - $this->assertEquals(0, $rbf->compute([4, 5], [1, 100])); + self::assertEquals(1, $rbf->compute([1, 2], [1, 2])); + self::assertEquals(0.00451, $rbf->compute([1, 2, 3], [4, 5, 6]), '', $delta = 0.0001); + self::assertEquals(0, $rbf->compute([4, 5], [1, 100])); } } diff --git a/tests/Math/LinearAlgebra/LUDecompositionTest.php b/tests/Math/LinearAlgebra/LUDecompositionTest.php index ea96f77d..bd81e2e3 100644 --- a/tests/Math/LinearAlgebra/LUDecompositionTest.php +++ b/tests/Math/LinearAlgebra/LUDecompositionTest.php @@ -34,8 +34,7 @@ public function testLowerTriangularFactor(): void $lu = new LUDecomposition(new Matrix([[1, 2], [3, 4]])); $L = $lu->getL(); - $this->assertInstanceOf(Matrix::class, $L); - $this->assertSame([[1.0, 0.0], [0.3333333333333333, 1.0]], $L->toArray()); + self::assertSame([[1.0, 0.0], [0.3333333333333333, 1.0]], $L->toArray()); } public function testUpperTriangularFactor(): void @@ -43,7 +42,6 @@ public function testUpperTriangularFactor(): void $lu = new LUDecomposition(new Matrix([[1, 2], [3, 4]])); $U = $lu->getU(); - $this->assertInstanceOf(Matrix::class, $U); - $this->assertSame([[3.0, 4.0], [0.0, 0.6666666666666667]], $U->toArray()); + self::assertSame([[3.0, 4.0], [0.0, 0.6666666666666667]], $U->toArray()); } } diff --git a/tests/Math/MatrixTest.php b/tests/Math/MatrixTest.php index 7adde6ce..94d47e28 100644 --- a/tests/Math/MatrixTest.php +++ b/tests/Math/MatrixTest.php @@ -22,11 +22,10 @@ public function testCreateMatrixFromFlatArray(): void $flatArray = [1, 2, 3, 4]; $matrix = Matrix::fromFlatArray($flatArray); - $this->assertInstanceOf(Matrix::class, $matrix); - $this->assertEquals([[1], [2], [3], [4]], $matrix->toArray()); - $this->assertEquals(4, $matrix->getRows()); - $this->assertEquals(1, $matrix->getColumns()); - $this->assertEquals($flatArray, $matrix->getColumnValues(0)); + self::assertEquals([[1], [2], [3], [4]], $matrix->toArray()); + self::assertEquals(4, $matrix->getRows()); + self::assertEquals(1, $matrix->getColumns()); + self::assertEquals($flatArray, $matrix->getColumnValues(0)); } public function testThrowExceptionOnInvalidColumnNumber(): void @@ -51,7 +50,7 @@ public function testGetMatrixDeterminant(): void [4, 2, 1], [5, 6, 7], ]); - $this->assertEquals(-3, $matrix->getDeterminant()); + self::assertEquals(-3, $matrix->getDeterminant()); $matrix = new Matrix([ [1, 2, 3, 3, 2, 1], @@ -61,7 +60,7 @@ public function testGetMatrixDeterminant(): void [1 / 4, 4, 1, 0, 2, 3 / 7], [1, 8, 7, 5, 4, 4 / 5], ]); - $this->assertEquals(1116.5035, $matrix->getDeterminant(), '', $delta = 0.0001); + self::assertEquals(1116.5035, $matrix->getDeterminant(), '', $delta = 0.0001); } public function testMatrixTranspose(): void @@ -78,7 +77,7 @@ public function testMatrixTranspose(): void [3, 1, 7], ]; - $this->assertEquals($transposedMatrix, $matrix->transpose()->toArray()); + self::assertEquals($transposedMatrix, $matrix->transpose()->toArray()); } public function testThrowExceptionOnMultiplyWhenInconsistentMatrixSupplied(): void @@ -107,7 +106,7 @@ public function testMatrixMultiplyByMatrix(): void [139, 154], ]; - $this->assertEquals($product, $matrix1->multiply($matrix2)->toArray()); + self::assertEquals($product, $matrix1->multiply($matrix2)->toArray()); } public function testDivideByScalar(): void @@ -122,7 +121,7 @@ public function testDivideByScalar(): void [1, 5, 10], ]; - $this->assertEquals($quotient, $matrix->divideByScalar(2)->toArray()); + self::assertEquals($quotient, $matrix->divideByScalar(2)->toArray()); } public function testThrowExceptionWhenInverseIfArrayIsNotSquare(): void @@ -158,7 +157,7 @@ public function testInverseMatrix(): void [-1 / 2, 1 / 2, -1 / 2], ]; - $this->assertEquals($inverseMatrix, $matrix->inverse()->toArray(), '', $delta = 0.0001); + self::assertEquals($inverseMatrix, $matrix->inverse()->toArray(), '', $delta = 0.0001); } public function testCrossOutMatrix(): void @@ -174,14 +173,14 @@ public function testCrossOutMatrix(): void [1, 1], ]; - $this->assertEquals($crossOuted, $matrix->crossOut(1, 1)->toArray()); + self::assertEquals($crossOuted, $matrix->crossOut(1, 1)->toArray()); } public function testToScalar(): void { $matrix = new Matrix([[1, 2, 3], [3, 2, 3]]); - $this->assertEquals($matrix->toScalar(), 1); + self::assertEquals($matrix->toScalar(), 1); } public function testMultiplyByScalar(): void @@ -196,7 +195,7 @@ public function testMultiplyByScalar(): void [-4, -20, -40], ]; - $this->assertEquals($result, $matrix->multiplyByScalar(-2)->toArray()); + self::assertEquals($result, $matrix->multiplyByScalar(-2)->toArray()); } public function testAdd(): void @@ -208,7 +207,7 @@ public function testAdd(): void $m1 = new Matrix($array1); $m2 = new Matrix($array2); - $this->assertEquals($result, $m1->add($m2)->toArray()[0]); + self::assertEquals($result, $m1->add($m2)->toArray()[0]); } public function testSubtract(): void @@ -220,7 +219,7 @@ public function testSubtract(): void $m1 = new Matrix($array1); $m2 = new Matrix($array2); - $this->assertEquals($result, $m1->subtract($m2)->toArray()[0]); + self::assertEquals($result, $m1->subtract($m2)->toArray()[0]); } public function testTransposeArray(): void @@ -235,7 +234,7 @@ public function testTransposeArray(): void [1, 2], ]; - $this->assertEquals($transposed, Matrix::transposeArray($array)); + self::assertEquals($transposed, Matrix::transposeArray($array)); } public function testDot(): void @@ -244,12 +243,12 @@ public function testDot(): void $vect2 = [3, 3, 3]; $dot = [18]; - $this->assertEquals($dot, Matrix::dot($vect1, $vect2)); + self::assertEquals($dot, Matrix::dot($vect1, $vect2)); $matrix1 = [[1, 1], [2, 2]]; $matrix2 = [[3, 3], [3, 3], [3, 3]]; $dot = [6, 12]; - $this->assertEquals($dot, Matrix::dot($matrix2, $matrix1)); + self::assertEquals($dot, Matrix::dot($matrix2, $matrix1)); } /** @@ -257,12 +256,10 @@ public function testDot(): void */ public function testFrobeniusNorm(array $matrix, float $norm): void { - $matrix = new Matrix($matrix); - - $this->assertEquals($norm, $matrix->frobeniusNorm(), '', 0.0001); + self::assertEquals($norm, (new Matrix($matrix))->frobeniusNorm(), '', 0.0001); } - public function dataProviderForFrobeniusNorm() + public function dataProviderForFrobeniusNorm(): array { return [ [ diff --git a/tests/Math/ProductTest.php b/tests/Math/ProductTest.php index da7450b9..4eaff58c 100644 --- a/tests/Math/ProductTest.php +++ b/tests/Math/ProductTest.php @@ -12,11 +12,11 @@ class ProductTest extends TestCase { public function testScalarProduct(): void { - $this->assertEquals(10, Product::scalar([2, 3], [-1, 4])); - $this->assertEquals(-0.1, Product::scalar([1, 4, 1], [-2, 0.5, -0.1])); - $this->assertEquals(8, Product::scalar([2], [4])); + self::assertEquals(10, Product::scalar([2, 3], [-1, 4])); + self::assertEquals(-0.1, Product::scalar([1, 4, 1], [-2, 0.5, -0.1])); + self::assertEquals(8, Product::scalar([2], [4])); //test for non numeric values - $this->assertEquals(0, Product::scalar(['', null, [], new stdClass()], [null])); + self::assertEquals(0, Product::scalar(['', null, [], new stdClass()], [null])); } } diff --git a/tests/Math/SetTest.php b/tests/Math/SetTest.php index 2335c73a..1f311564 100644 --- a/tests/Math/SetTest.php +++ b/tests/Math/SetTest.php @@ -13,84 +13,79 @@ public function testUnion(): void { $union = Set::union(new Set([3, 1]), new Set([3, 2, 2])); - $this->assertInstanceOf(Set::class, $union); - $this->assertEquals(new Set([1, 2, 3]), $union); - $this->assertEquals(3, $union->cardinality()); + self::assertEquals(new Set([1, 2, 3]), $union); + self::assertEquals(3, $union->cardinality()); } public function testIntersection(): void { $intersection = Set::intersection(new Set(['C', 'A']), new Set(['B', 'C'])); - $this->assertInstanceOf(Set::class, $intersection); - $this->assertEquals(new Set(['C']), $intersection); - $this->assertEquals(1, $intersection->cardinality()); + self::assertEquals(new Set(['C']), $intersection); + self::assertEquals(1, $intersection->cardinality()); } public function testDifference(): void { $difference = Set::difference(new Set(['C', 'A', 'B']), new Set(['A'])); - $this->assertInstanceOf(Set::class, $difference); - $this->assertEquals(new Set(['B', 'C']), $difference); - $this->assertEquals(2, $difference->cardinality()); + self::assertEquals(new Set(['B', 'C']), $difference); + self::assertEquals(2, $difference->cardinality()); } public function testPower(): void { $power = Set::power(new Set(['A', 'B'])); - $this->assertInternalType('array', $power); - $this->assertEquals([new Set(), new Set(['A']), new Set(['B']), new Set(['A', 'B'])], $power); - $this->assertCount(4, $power); + self::assertEquals([new Set(), new Set(['A']), new Set(['B']), new Set(['A', 'B'])], $power); + self::assertCount(4, $power); } public function testCartesian(): void { $cartesian = Set::cartesian(new Set(['A']), new Set([1, 2])); - $this->assertInternalType('array', $cartesian); - $this->assertEquals([new Set(['A', 1]), new Set(['A', 2])], $cartesian); - $this->assertCount(2, $cartesian); + self::assertEquals([new Set(['A', 1]), new Set(['A', 2])], $cartesian); + self::assertCount(2, $cartesian); } public function testContains(): void { $set = new Set(['B', 'A', 2, 1]); - $this->assertTrue($set->contains('B')); - $this->assertTrue($set->containsAll(['A', 'B'])); + self::assertTrue($set->contains('B')); + self::assertTrue($set->containsAll(['A', 'B'])); - $this->assertFalse($set->contains('C')); - $this->assertFalse($set->containsAll(['A', 'B', 'C'])); + self::assertFalse($set->contains('C')); + self::assertFalse($set->containsAll(['A', 'B', 'C'])); } public function testRemove(): void { $set = new Set(['B', 'A', 2, 1]); - $this->assertEquals((new Set([1, 2, 2, 2, 'B']))->toArray(), $set->remove('A')->toArray()); + self::assertEquals((new Set([1, 2, 2, 2, 'B']))->toArray(), $set->remove('A')->toArray()); } public function testAdd(): void { $set = new Set(['B', 'A', 2, 1]); $set->addAll(['foo', 'bar']); - $this->assertEquals(6, $set->cardinality()); + self::assertEquals(6, $set->cardinality()); } public function testEmpty(): void { $set = new Set([1, 2]); $set->removeAll([2, 1]); - $this->assertEquals(new Set(), $set); - $this->assertTrue($set->isEmpty()); + self::assertEquals(new Set(), $set); + self::assertTrue($set->isEmpty()); } public function testToArray(): void { $set = new Set([1, 2, 2, 3, 'A', false, '', 1.1, -1, -10, 'B']); - $this->assertEquals([-10, '', -1, 'A', 'B', 1, 1.1, 2, 3], $set->toArray()); + self::assertEquals([-10, '', -1, 'A', 'B', 1, 1.1, 2, 3], $set->toArray()); } } diff --git a/tests/Math/Statistic/CorrelationTest.php b/tests/Math/Statistic/CorrelationTest.php index eebb0652..2d0334d2 100644 --- a/tests/Math/Statistic/CorrelationTest.php +++ b/tests/Math/Statistic/CorrelationTest.php @@ -16,18 +16,18 @@ public function testPearsonCorrelation(): void $delta = 0.001; $x = [9300, 10565, 15000, 15000, 17764, 57000, 65940, 73676, 77006, 93739, 146088, 153260]; $y = [7100, 15500, 4400, 4400, 5900, 4600, 8800, 2000, 2750, 2550, 960, 1025]; - $this->assertEquals(-0.641, Correlation::pearson($x, $y), '', $delta); + self::assertEquals(-0.641, Correlation::pearson($x, $y), '', $delta); //http://www.statisticshowto.com/how-to-compute-pearsons-correlation-coefficients/ $delta = 0.001; $x = [43, 21, 25, 42, 57, 59]; $y = [99, 65, 79, 75, 87, 82]; - $this->assertEquals(0.549, Correlation::pearson($x, $y), '', $delta); + self::assertEquals(0.549, Correlation::pearson($x, $y), '', $delta); $delta = 0.001; $x = [60, 61, 62, 63, 65]; $y = [3.1, 3.6, 3.8, 4, 4.1]; - $this->assertEquals(0.911, Correlation::pearson($x, $y), '', $delta); + self::assertEquals(0.911, Correlation::pearson($x, $y), '', $delta); } public function testThrowExceptionOnInvalidArgumentsForPearsonCorrelation(): void diff --git a/tests/Math/Statistic/CovarianceTest.php b/tests/Math/Statistic/CovarianceTest.php index fd7187a3..dd98aeae 100644 --- a/tests/Math/Statistic/CovarianceTest.php +++ b/tests/Math/Statistic/CovarianceTest.php @@ -38,18 +38,18 @@ public function testSimpleCovariance(): void // Calculate only one covariance value: Cov(x, y) $cov1 = Covariance::fromDataset($matrix, 0, 0); - $this->assertEquals($cov1, $knownCovariance[0][0], '', $epsilon); + self::assertEquals($cov1, $knownCovariance[0][0], '', $epsilon); $cov1 = Covariance::fromXYArrays($x, $x); - $this->assertEquals($cov1, $knownCovariance[0][0], '', $epsilon); + self::assertEquals($cov1, $knownCovariance[0][0], '', $epsilon); $cov2 = Covariance::fromDataset($matrix, 0, 1); - $this->assertEquals($cov2, $knownCovariance[0][1], '', $epsilon); + self::assertEquals($cov2, $knownCovariance[0][1], '', $epsilon); $cov2 = Covariance::fromXYArrays($x, $y); - $this->assertEquals($cov2, $knownCovariance[0][1], '', $epsilon); + self::assertEquals($cov2, $knownCovariance[0][1], '', $epsilon); // Second: calculation cov matrix with automatic means for each column $covariance = Covariance::covarianceMatrix($matrix); - $this->assertEquals($knownCovariance, $covariance, '', $epsilon); + self::assertEquals($knownCovariance, $covariance, '', $epsilon); // Thirdly, CovMatrix: Means are precalculated and given to the method $x = array_column($matrix, 0); @@ -58,7 +58,7 @@ public function testSimpleCovariance(): void $meanY = Mean::arithmetic($y); $covariance = Covariance::covarianceMatrix($matrix, [$meanX, $meanY]); - $this->assertEquals($knownCovariance, $covariance, '', $epsilon); + self::assertEquals($knownCovariance, $covariance, '', $epsilon); } public function testThrowExceptionOnEmptyX(): void diff --git a/tests/Math/Statistic/GaussianTest.php b/tests/Math/Statistic/GaussianTest.php index e4627f44..b19c8db6 100644 --- a/tests/Math/Statistic/GaussianTest.php +++ b/tests/Math/Statistic/GaussianTest.php @@ -20,9 +20,9 @@ public function testPdf(): void $x = [0, 0.1, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0]; $pdf = [0.3989, 0.3969, 0.3520, 0.2419, 0.1295, 0.0539, 0.0175, 0.0044]; foreach ($x as $i => $v) { - $this->assertEquals($pdf[$i], $g->pdf($v), '', $delta); + self::assertEquals($pdf[$i], $g->pdf($v), '', $delta); - $this->assertEquals($pdf[$i], Gaussian::distributionPdf($mean, $std, $v), '', $delta); + self::assertEquals($pdf[$i], Gaussian::distributionPdf($mean, $std, $v), '', $delta); } } } diff --git a/tests/Math/Statistic/MeanTest.php b/tests/Math/Statistic/MeanTest.php index 3b980326..6e5d8d78 100644 --- a/tests/Math/Statistic/MeanTest.php +++ b/tests/Math/Statistic/MeanTest.php @@ -19,9 +19,9 @@ public function testArithmeticThrowExceptionOnEmptyArray(): void public function testArithmeticMean(): void { $delta = 0.01; - $this->assertEquals(3.5, Mean::arithmetic([2, 5]), '', $delta); - $this->assertEquals(41.16, Mean::arithmetic([43, 21, 25, 42, 57, 59]), '', $delta); - $this->assertEquals(1.7, Mean::arithmetic([0.5, 0.5, 1.5, 2.5, 3.5]), '', $delta); + self::assertEquals(3.5, Mean::arithmetic([2, 5]), '', $delta); + self::assertEquals(41.16, Mean::arithmetic([43, 21, 25, 42, 57, 59]), '', $delta); + self::assertEquals(1.7, Mean::arithmetic([0.5, 0.5, 1.5, 2.5, 3.5]), '', $delta); } public function testMedianThrowExceptionOnEmptyArray(): void @@ -34,14 +34,14 @@ public function testMedianOnOddLengthArray(): void { $numbers = [5, 2, 6, 1, 3]; - $this->assertEquals(3, Mean::median($numbers)); + self::assertEquals(3, Mean::median($numbers)); } public function testMedianOnEvenLengthArray(): void { $numbers = [5, 2, 6, 1, 3, 4]; - $this->assertEquals(3.5, Mean::median($numbers)); + self::assertEquals(3.5, Mean::median($numbers)); } public function testModeThrowExceptionOnEmptyArray(): void @@ -54,6 +54,6 @@ public function testModeOnArray(): void { $numbers = [5, 2, 6, 1, 3, 4, 6, 6, 5]; - $this->assertEquals(6, Mean::mode($numbers)); + self::assertEquals(6, Mean::mode($numbers)); } } diff --git a/tests/Math/Statistic/StandardDeviationTest.php b/tests/Math/Statistic/StandardDeviationTest.php index c6fd47e6..e18c374b 100644 --- a/tests/Math/Statistic/StandardDeviationTest.php +++ b/tests/Math/Statistic/StandardDeviationTest.php @@ -15,15 +15,15 @@ public function testStandardDeviationOfPopulationSample(): void //https://pl.wikipedia.org/wiki/Odchylenie_standardowe $delta = 0.001; $population = [5, 6, 8, 9]; - $this->assertEquals(1.825, StandardDeviation::population($population), '', $delta); + self::assertEquals(1.825, StandardDeviation::population($population), '', $delta); //http://www.stat.wmich.edu/s216/book/node126.html $delta = 0.5; $population = [7100, 15500, 4400, 4400, 5900, 4600, 8800, 2000, 2750, 2550, 960, 1025]; - $this->assertEquals(4079, StandardDeviation::population($population), '', $delta); + self::assertEquals(4079, StandardDeviation::population($population), '', $delta); $population = [9300, 10565, 15000, 15000, 17764, 57000, 65940, 73676, 77006, 93739, 146088, 153260]; - $this->assertEquals(50989, StandardDeviation::population($population), '', $delta); + self::assertEquals(50989, StandardDeviation::population($population), '', $delta); } public function testThrowExceptionOnEmptyArrayIfNotSample(): void diff --git a/tests/Math/Statistic/VarianceTest.php b/tests/Math/Statistic/VarianceTest.php index 19b2cd8a..310acb60 100644 --- a/tests/Math/Statistic/VarianceTest.php +++ b/tests/Math/Statistic/VarianceTest.php @@ -17,7 +17,7 @@ public function testVarianceFromInt(array $numbers, float $variance): void self::assertEquals($variance, Variance::population($numbers), '', 0.001); } - public function dataProviderForPopulationVariance() + public function dataProviderForPopulationVariance(): array { return [ [[0, 0, 0, 0, 0, 1], 0.138], diff --git a/tests/Metric/AccuracyTest.php b/tests/Metric/AccuracyTest.php index e2260c45..792dd2fb 100644 --- a/tests/Metric/AccuracyTest.php +++ b/tests/Metric/AccuracyTest.php @@ -27,7 +27,7 @@ public function testCalculateNormalizedScore(): void $actualLabels = ['a', 'b', 'a', 'b']; $predictedLabels = ['a', 'a', 'b', 'b']; - $this->assertEquals(0.5, Accuracy::score($actualLabels, $predictedLabels)); + self::assertEquals(0.5, Accuracy::score($actualLabels, $predictedLabels)); } public function testCalculateNotNormalizedScore(): void @@ -35,7 +35,7 @@ public function testCalculateNotNormalizedScore(): void $actualLabels = ['a', 'b', 'a', 'b']; $predictedLabels = ['a', 'b', 'b', 'b']; - $this->assertEquals(3, Accuracy::score($actualLabels, $predictedLabels, false)); + self::assertEquals(3, Accuracy::score($actualLabels, $predictedLabels, false)); } public function testAccuracyOnDemoDataset(): void @@ -51,6 +51,6 @@ public function testAccuracyOnDemoDataset(): void $expected = PHP_VERSION_ID >= 70100 ? 1 : 0.959; - $this->assertEquals($expected, $accuracy, '', 0.01); + self::assertEquals($expected, $accuracy, '', 0.01); } } diff --git a/tests/Metric/ClassificationReportTest.php b/tests/Metric/ClassificationReportTest.php index fa9080b3..3258bc18 100644 --- a/tests/Metric/ClassificationReportTest.php +++ b/tests/Metric/ClassificationReportTest.php @@ -45,11 +45,11 @@ public function testClassificationReportGenerateWithStringLabels(): void 'f1score' => 0.49, // (2/3 + 0 + 4/5) / 3 = 22/45 ]; - $this->assertEquals($precision, $report->getPrecision(), '', 0.01); - $this->assertEquals($recall, $report->getRecall(), '', 0.01); - $this->assertEquals($f1score, $report->getF1score(), '', 0.01); - $this->assertEquals($support, $report->getSupport(), '', 0.01); - $this->assertEquals($average, $report->getAverage(), '', 0.01); + self::assertEquals($precision, $report->getPrecision(), '', 0.01); + self::assertEquals($recall, $report->getRecall(), '', 0.01); + self::assertEquals($f1score, $report->getF1score(), '', 0.01); + self::assertEquals($support, $report->getSupport(), '', 0.01); + self::assertEquals($average, $report->getAverage(), '', 0.01); } public function testClassificationReportGenerateWithNumericLabels(): void @@ -85,11 +85,11 @@ public function testClassificationReportGenerateWithNumericLabels(): void 'f1score' => 0.49, ]; - $this->assertEquals($precision, $report->getPrecision(), '', 0.01); - $this->assertEquals($recall, $report->getRecall(), '', 0.01); - $this->assertEquals($f1score, $report->getF1score(), '', 0.01); - $this->assertEquals($support, $report->getSupport(), '', 0.01); - $this->assertEquals($average, $report->getAverage(), '', 0.01); + self::assertEquals($precision, $report->getPrecision(), '', 0.01); + self::assertEquals($recall, $report->getRecall(), '', 0.01); + self::assertEquals($f1score, $report->getF1score(), '', 0.01); + self::assertEquals($support, $report->getSupport(), '', 0.01); + self::assertEquals($average, $report->getAverage(), '', 0.01); } public function testClassificationReportAverageOutOfRange(): void @@ -114,7 +114,7 @@ public function testClassificationReportMicroAverage(): void 'f1score' => 0.6, // Harmonic mean of precision and recall ]; - $this->assertEquals($average, $report->getAverage(), '', 0.01); + self::assertEquals($average, $report->getAverage(), '', 0.01); } public function testClassificationReportMacroAverage(): void @@ -130,7 +130,7 @@ public function testClassificationReportMacroAverage(): void 'f1score' => 0.49, // (2/3 + 0 + 4/5) / 3 = 22/45 ]; - $this->assertEquals($average, $report->getAverage(), '', 0.01); + self::assertEquals($average, $report->getAverage(), '', 0.01); } public function testClassificationReportWeightedAverage(): void @@ -146,7 +146,7 @@ public function testClassificationReportWeightedAverage(): void 'f1score' => 0.61, // (2/3 * 1 + 0 * 1 + 4/5 * 3) / 5 = 46/75 ]; - $this->assertEquals($average, $report->getAverage(), '', 0.01); + self::assertEquals($average, $report->getAverage(), '', 0.01); } public function testPreventDivideByZeroWhenTruePositiveAndFalsePositiveSumEqualsZero(): void @@ -156,7 +156,7 @@ public function testPreventDivideByZeroWhenTruePositiveAndFalsePositiveSumEquals $report = new ClassificationReport($labels, $predicted); - $this->assertEquals([ + self::assertEquals([ 1 => 0.0, 2 => 0.5, ], $report->getPrecision(), '', 0.01); @@ -169,7 +169,7 @@ public function testPreventDivideByZeroWhenTruePositiveAndFalseNegativeSumEquals $report = new ClassificationReport($labels, $predicted); - $this->assertEquals([ + self::assertEquals([ 1 => 0.0, 2 => 1, 3 => 0, @@ -183,7 +183,7 @@ public function testPreventDividedByZeroWhenPredictedLabelsAllNotMatch(): void $report = new ClassificationReport($labels, $predicted); - $this->assertEquals([ + self::assertEquals([ 'precision' => 0, 'recall' => 0, 'f1score' => 0, @@ -197,7 +197,7 @@ public function testPreventDividedByZeroWhenLabelsAreEmpty(): void $report = new ClassificationReport($labels, $predicted); - $this->assertEquals([ + self::assertEquals([ 'precision' => 0, 'recall' => 0, 'f1score' => 0, diff --git a/tests/Metric/ConfusionMatrixTest.php b/tests/Metric/ConfusionMatrixTest.php index 590aff89..36518a33 100644 --- a/tests/Metric/ConfusionMatrixTest.php +++ b/tests/Metric/ConfusionMatrixTest.php @@ -20,7 +20,7 @@ public function testComputeConfusionMatrixOnNumericLabels(): void [1, 0, 2], ]; - $this->assertEquals($confusionMatrix, ConfusionMatrix::compute($actualLabels, $predictedLabels)); + self::assertEquals($confusionMatrix, ConfusionMatrix::compute($actualLabels, $predictedLabels)); } public function testComputeConfusionMatrixOnStringLabels(): void @@ -34,7 +34,7 @@ public function testComputeConfusionMatrixOnStringLabels(): void [1, 0, 2], ]; - $this->assertEquals($confusionMatrix, ConfusionMatrix::compute($actualLabels, $predictedLabels)); + self::assertEquals($confusionMatrix, ConfusionMatrix::compute($actualLabels, $predictedLabels)); } public function testComputeConfusionMatrixOnLabelsWithSubset(): void @@ -48,7 +48,7 @@ public function testComputeConfusionMatrixOnLabelsWithSubset(): void [0, 0], ]; - $this->assertEquals($confusionMatrix, ConfusionMatrix::compute($actualLabels, $predictedLabels, $labels)); + self::assertEquals($confusionMatrix, ConfusionMatrix::compute($actualLabels, $predictedLabels, $labels)); $labels = ['bird', 'ant']; @@ -57,6 +57,6 @@ public function testComputeConfusionMatrixOnLabelsWithSubset(): void [0, 2], ]; - $this->assertEquals($confusionMatrix, ConfusionMatrix::compute($actualLabels, $predictedLabels, $labels)); + self::assertEquals($confusionMatrix, ConfusionMatrix::compute($actualLabels, $predictedLabels, $labels)); } } diff --git a/tests/ModelManagerTest.php b/tests/ModelManagerTest.php index 48ab7479..fab34d7b 100644 --- a/tests/ModelManagerTest.php +++ b/tests/ModelManagerTest.php @@ -13,7 +13,7 @@ class ModelManagerTest extends TestCase { public function testSaveAndRestore(): void { - $filename = uniqid(); + $filename = uniqid('', false); $filepath = sys_get_temp_dir().DIRECTORY_SEPARATOR.$filename; $estimator = new LeastSquares(); @@ -21,7 +21,7 @@ public function testSaveAndRestore(): void $modelManager->saveToFile($estimator, $filepath); $restored = $modelManager->restoreFromFile($filepath); - $this->assertEquals($estimator, $restored); + self::assertEquals($estimator, $restored); } public function testRestoreWrongFile(): void diff --git a/tests/NeuralNetwork/ActivationFunction/BinaryStepTest.php b/tests/NeuralNetwork/ActivationFunction/BinaryStepTest.php index 4e854786..699c7084 100644 --- a/tests/NeuralNetwork/ActivationFunction/BinaryStepTest.php +++ b/tests/NeuralNetwork/ActivationFunction/BinaryStepTest.php @@ -11,12 +11,14 @@ class BinaryStepTest extends TestCase { /** * @dataProvider binaryStepProvider + * + * @param float|int $value */ - public function testBinaryStepActivationFunction($expected, $value): void + public function testBinaryStepActivationFunction(float $expected, $value): void { $binaryStep = new BinaryStep(); - $this->assertEquals($expected, $binaryStep->compute($value)); + self::assertEquals($expected, $binaryStep->compute($value)); } public function binaryStepProvider(): array @@ -30,12 +32,14 @@ public function binaryStepProvider(): array /** * @dataProvider binaryStepDerivativeProvider + * + * @param float|int $value */ - public function testBinaryStepDerivative($expected, $value): void + public function testBinaryStepDerivative(float $expected, $value): void { $binaryStep = new BinaryStep(); $activatedValue = $binaryStep->compute($value); - $this->assertEquals($expected, $binaryStep->differentiate($value, $activatedValue)); + self::assertEquals($expected, $binaryStep->differentiate($value, $activatedValue)); } public function binaryStepDerivativeProvider(): array diff --git a/tests/NeuralNetwork/ActivationFunction/GaussianTest.php b/tests/NeuralNetwork/ActivationFunction/GaussianTest.php index aace8bcb..6876fd87 100644 --- a/tests/NeuralNetwork/ActivationFunction/GaussianTest.php +++ b/tests/NeuralNetwork/ActivationFunction/GaussianTest.php @@ -11,12 +11,14 @@ class GaussianTest extends TestCase { /** * @dataProvider gaussianProvider + * + * @param float|int $value */ - public function testGaussianActivationFunction($expected, $value): void + public function testGaussianActivationFunction(float $expected, $value): void { $gaussian = new Gaussian(); - $this->assertEquals($expected, $gaussian->compute($value), '', 0.001); + self::assertEquals($expected, $gaussian->compute($value), '', 0.001); } public function gaussianProvider(): array @@ -32,12 +34,14 @@ public function gaussianProvider(): array /** * @dataProvider gaussianDerivativeProvider + * + * @param float|int $value */ - public function testGaussianDerivative($expected, $value): void + public function testGaussianDerivative(float $expected, $value): void { $gaussian = new Gaussian(); $activatedValue = $gaussian->compute($value); - $this->assertEquals($expected, $gaussian->differentiate($value, $activatedValue), '', 0.001); + self::assertEquals($expected, $gaussian->differentiate($value, $activatedValue), '', 0.001); } public function gaussianDerivativeProvider(): array diff --git a/tests/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php b/tests/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php index 629200e9..a6a244ff 100644 --- a/tests/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php +++ b/tests/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php @@ -11,12 +11,14 @@ class HyperboliTangentTest extends TestCase { /** * @dataProvider tanhProvider + * + * @param float|int $value */ - public function testHyperbolicTangentActivationFunction($beta, $expected, $value): void + public function testHyperbolicTangentActivationFunction(float $beta, float $expected, $value): void { $tanh = new HyperbolicTangent($beta); - $this->assertEquals($expected, $tanh->compute($value), '', 0.001); + self::assertEquals($expected, $tanh->compute($value), '', 0.001); } public function tanhProvider(): array @@ -33,12 +35,14 @@ public function tanhProvider(): array /** * @dataProvider tanhDerivativeProvider + * + * @param float|int $value */ - public function testHyperbolicTangentDerivative($beta, $expected, $value): void + public function testHyperbolicTangentDerivative(float $beta, float $expected, $value): void { $tanh = new HyperbolicTangent($beta); $activatedValue = $tanh->compute($value); - $this->assertEquals($expected, $tanh->differentiate($value, $activatedValue), '', 0.001); + self::assertEquals($expected, $tanh->differentiate($value, $activatedValue), '', 0.001); } public function tanhDerivativeProvider(): array diff --git a/tests/NeuralNetwork/ActivationFunction/PReLUTest.php b/tests/NeuralNetwork/ActivationFunction/PReLUTest.php index c9f565d3..a659204a 100644 --- a/tests/NeuralNetwork/ActivationFunction/PReLUTest.php +++ b/tests/NeuralNetwork/ActivationFunction/PReLUTest.php @@ -11,12 +11,14 @@ class PReLUTest extends TestCase { /** * @dataProvider preluProvider + * + * @param float|int $value */ - public function testPReLUActivationFunction($beta, $expected, $value): void + public function testPReLUActivationFunction(float $beta, float $expected, $value): void { $prelu = new PReLU($beta); - $this->assertEquals($expected, $prelu->compute($value), '', 0.001); + self::assertEquals($expected, $prelu->compute($value), '', 0.001); } public function preluProvider(): array @@ -32,12 +34,14 @@ public function preluProvider(): array /** * @dataProvider preluDerivativeProvider + * + * @param float|int $value */ - public function testPReLUDerivative($beta, $expected, $value): void + public function testPReLUDerivative(float $beta, float $expected, $value): void { $prelu = new PReLU($beta); $activatedValue = $prelu->compute($value); - $this->assertEquals($expected, $prelu->differentiate($value, $activatedValue)); + self::assertEquals($expected, $prelu->differentiate($value, $activatedValue)); } public function preluDerivativeProvider(): array diff --git a/tests/NeuralNetwork/ActivationFunction/SigmoidTest.php b/tests/NeuralNetwork/ActivationFunction/SigmoidTest.php index 1028fb3b..d98b39c5 100644 --- a/tests/NeuralNetwork/ActivationFunction/SigmoidTest.php +++ b/tests/NeuralNetwork/ActivationFunction/SigmoidTest.php @@ -11,12 +11,14 @@ class SigmoidTest extends TestCase { /** * @dataProvider sigmoidProvider + * + * @param float|int $value */ - public function testSigmoidActivationFunction($beta, $expected, $value): void + public function testSigmoidActivationFunction(float $beta, float $expected, $value): void { $sigmoid = new Sigmoid($beta); - $this->assertEquals($expected, $sigmoid->compute($value), '', 0.001); + self::assertEquals($expected, $sigmoid->compute($value), '', 0.001); } public function sigmoidProvider(): array @@ -33,12 +35,14 @@ public function sigmoidProvider(): array /** * @dataProvider sigmoidDerivativeProvider + * + * @param float|int $value */ - public function testSigmoidDerivative($beta, $expected, $value): void + public function testSigmoidDerivative(float $beta, float $expected, $value): void { $sigmoid = new Sigmoid($beta); $activatedValue = $sigmoid->compute($value); - $this->assertEquals($expected, $sigmoid->differentiate($value, $activatedValue), '', 0.001); + self::assertEquals($expected, $sigmoid->differentiate($value, $activatedValue), '', 0.001); } public function sigmoidDerivativeProvider(): array diff --git a/tests/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php b/tests/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php index 4db0418e..19b00394 100644 --- a/tests/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php +++ b/tests/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php @@ -11,12 +11,14 @@ class ThresholdedReLUTest extends TestCase { /** * @dataProvider thresholdProvider + * + * @param float|int $value */ - public function testThresholdedReLUActivationFunction($theta, $expected, $value): void + public function testThresholdedReLUActivationFunction(float $theta, float $expected, $value): void { $thresholdedReLU = new ThresholdedReLU($theta); - $this->assertEquals($expected, $thresholdedReLU->compute($value)); + self::assertEquals($expected, $thresholdedReLU->compute($value)); } public function thresholdProvider(): array @@ -31,12 +33,14 @@ public function thresholdProvider(): array /** * @dataProvider thresholdDerivativeProvider + * + * @param float|int $value */ - public function testThresholdedReLUDerivative($theta, $expected, $value): void + public function testThresholdedReLUDerivative(float $theta, float $expected, $value): void { $thresholdedReLU = new ThresholdedReLU($theta); $activatedValue = $thresholdedReLU->compute($value); - $this->assertEquals($expected, $thresholdedReLU->differentiate($value, $activatedValue)); + self::assertEquals($expected, $thresholdedReLU->differentiate($value, $activatedValue)); } public function thresholdDerivativeProvider(): array diff --git a/tests/NeuralNetwork/LayerTest.php b/tests/NeuralNetwork/LayerTest.php index baa3f9a3..87809b8c 100644 --- a/tests/NeuralNetwork/LayerTest.php +++ b/tests/NeuralNetwork/LayerTest.php @@ -17,16 +17,16 @@ public function testLayerInitialization(): void { $layer = new Layer(); - $this->assertEquals([], $layer->getNodes()); + self::assertEquals([], $layer->getNodes()); } public function testLayerInitializationWithDefaultNodesType(): void { $layer = new Layer($number = 5); - $this->assertCount($number, $layer->getNodes()); + self::assertCount($number, $layer->getNodes()); foreach ($layer->getNodes() as $node) { - $this->assertInstanceOf(Neuron::class, $node); + self::assertInstanceOf(Neuron::class, $node); } } @@ -34,9 +34,9 @@ public function testLayerInitializationWithExplicitNodesType(): void { $layer = new Layer($number = 5, $class = Bias::class); - $this->assertCount($number, $layer->getNodes()); + self::assertCount($number, $layer->getNodes()); foreach ($layer->getNodes() as $node) { - $this->assertInstanceOf($class, $node); + self::assertInstanceOf($class, $node); } } @@ -52,6 +52,6 @@ public function testAddNodesToLayer(): void $layer->addNode($node1 = new Neuron()); $layer->addNode($node2 = new Neuron()); - $this->assertEquals([$node1, $node2], $layer->getNodes()); + self::assertEquals([$node1, $node2], $layer->getNodes()); } } diff --git a/tests/NeuralNetwork/Network/LayeredNetworkTest.php b/tests/NeuralNetwork/Network/LayeredNetworkTest.php index 2a7e6966..a8658311 100644 --- a/tests/NeuralNetwork/Network/LayeredNetworkTest.php +++ b/tests/NeuralNetwork/Network/LayeredNetworkTest.php @@ -20,7 +20,7 @@ public function testLayersSettersAndGetters(): void $network->addLayer($layer1 = new Layer()); $network->addLayer($layer2 = new Layer()); - $this->assertEquals([$layer1, $layer2], $network->getLayers()); + self::assertEquals([$layer1, $layer2], $network->getLayers()); } public function testGetLastLayerAsOutputLayer(): void @@ -28,10 +28,10 @@ public function testGetLastLayerAsOutputLayer(): void $network = $this->getLayeredNetworkMock(); $network->addLayer($layer1 = new Layer()); - $this->assertEquals($layer1, $network->getOutputLayer()); + self::assertEquals($layer1, $network->getOutputLayer()); $network->addLayer($layer2 = new Layer()); - $this->assertEquals($layer2, $network->getOutputLayer()); + self::assertEquals($layer2, $network->getOutputLayer()); } public function testSetInputAndGetOutput(): void @@ -40,10 +40,10 @@ public function testSetInputAndGetOutput(): void $network->addLayer(new Layer(2, Input::class)); $network->setInput($input = [34, 43]); - $this->assertEquals($input, $network->getOutput()); + self::assertEquals($input, $network->getOutput()); $network->addLayer(new Layer(1)); - $this->assertEquals([0.5], $network->getOutput()); + self::assertEquals([0.5], $network->getOutput()); } public function testSetInputAndGetOutputWithCustomActivationFunctions(): void @@ -52,7 +52,7 @@ public function testSetInputAndGetOutputWithCustomActivationFunctions(): void $network->addLayer(new Layer(2, Input::class, $this->getActivationFunctionMock())); $network->setInput($input = [34, 43]); - $this->assertEquals($input, $network->getOutput()); + self::assertEquals($input, $network->getOutput()); } /** diff --git a/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php b/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php index cea2a2f2..d7bf7e55 100644 --- a/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php +++ b/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php @@ -55,14 +55,14 @@ public function testLearningRateSetter(): void [5, [3], [0, 1], 1000, null, 0.42] ); - $this->assertEquals(0.42, $this->readAttribute($mlp, 'learningRate')); - $backprop = $this->readAttribute($mlp, 'backpropagation'); - $this->assertEquals(0.42, $this->readAttribute($backprop, 'learningRate')); + self::assertEquals(0.42, self::readAttribute($mlp, 'learningRate')); + $backprop = self::readAttribute($mlp, 'backpropagation'); + self::assertEquals(0.42, self::readAttribute($backprop, 'learningRate')); $mlp->setLearningRate(0.24); - $this->assertEquals(0.24, $this->readAttribute($mlp, 'learningRate')); - $backprop = $this->readAttribute($mlp, 'backpropagation'); - $this->assertEquals(0.24, $this->readAttribute($backprop, 'learningRate')); + self::assertEquals(0.24, self::readAttribute($mlp, 'learningRate')); + $backprop = self::readAttribute($mlp, 'backpropagation'); + self::assertEquals(0.24, self::readAttribute($backprop, 'learningRate')); } public function testLearningRateSetterWithCustomActivationFunctions(): void @@ -75,14 +75,14 @@ public function testLearningRateSetterWithCustomActivationFunctions(): void [5, [[3, $activation_function], [5, $activation_function]], [0, 1], 1000, null, 0.42] ); - $this->assertEquals(0.42, $this->readAttribute($mlp, 'learningRate')); - $backprop = $this->readAttribute($mlp, 'backpropagation'); - $this->assertEquals(0.42, $this->readAttribute($backprop, 'learningRate')); + self::assertEquals(0.42, self::readAttribute($mlp, 'learningRate')); + $backprop = self::readAttribute($mlp, 'backpropagation'); + self::assertEquals(0.42, self::readAttribute($backprop, 'learningRate')); $mlp->setLearningRate(0.24); - $this->assertEquals(0.24, $this->readAttribute($mlp, 'learningRate')); - $backprop = $this->readAttribute($mlp, 'backpropagation'); - $this->assertEquals(0.24, $this->readAttribute($backprop, 'learningRate')); + self::assertEquals(0.24, self::readAttribute($mlp, 'learningRate')); + $backprop = self::readAttribute($mlp, 'backpropagation'); + self::assertEquals(0.24, self::readAttribute($backprop, 'learningRate')); } public function testLearningRateSetterWithLayerObject(): void @@ -95,14 +95,14 @@ public function testLearningRateSetterWithLayerObject(): void [5, [new Layer(3, Neuron::class, $activation_function), new Layer(5, Neuron::class, $activation_function)], [0, 1], 1000, null, 0.42] ); - $this->assertEquals(0.42, $this->readAttribute($mlp, 'learningRate')); - $backprop = $this->readAttribute($mlp, 'backpropagation'); - $this->assertEquals(0.42, $this->readAttribute($backprop, 'learningRate')); + self::assertEquals(0.42, self::readAttribute($mlp, 'learningRate')); + $backprop = self::readAttribute($mlp, 'backpropagation'); + self::assertEquals(0.42, self::readAttribute($backprop, 'learningRate')); $mlp->setLearningRate(0.24); - $this->assertEquals(0.24, $this->readAttribute($mlp, 'learningRate')); - $backprop = $this->readAttribute($mlp, 'backpropagation'); - $this->assertEquals(0.24, $this->readAttribute($backprop, 'learningRate')); + self::assertEquals(0.24, self::readAttribute($mlp, 'learningRate')); + $backprop = self::readAttribute($mlp, 'backpropagation'); + self::assertEquals(0.24, self::readAttribute($backprop, 'learningRate')); } /** diff --git a/tests/NeuralNetwork/Node/BiasTest.php b/tests/NeuralNetwork/Node/BiasTest.php index e42a737c..bb9650c8 100644 --- a/tests/NeuralNetwork/Node/BiasTest.php +++ b/tests/NeuralNetwork/Node/BiasTest.php @@ -13,6 +13,6 @@ public function testBiasOutput(): void { $bias = new Bias(); - $this->assertEquals(1.0, $bias->getOutput()); + self::assertEquals(1.0, $bias->getOutput()); } } diff --git a/tests/NeuralNetwork/Node/InputTest.php b/tests/NeuralNetwork/Node/InputTest.php index 09ca8319..8304d3e0 100644 --- a/tests/NeuralNetwork/Node/InputTest.php +++ b/tests/NeuralNetwork/Node/InputTest.php @@ -12,10 +12,10 @@ class InputTest extends TestCase public function testInputInitialization(): void { $input = new Input(); - $this->assertEquals(0.0, $input->getOutput()); + self::assertEquals(0.0, $input->getOutput()); $input = new Input($value = 9.6); - $this->assertEquals($value, $input->getOutput()); + self::assertEquals($value, $input->getOutput()); } public function testSetInput(): void @@ -23,6 +23,6 @@ public function testSetInput(): void $input = new Input(); $input->setInput($value = 6.9); - $this->assertEquals($value, $input->getOutput()); + self::assertEquals($value, $input->getOutput()); } } diff --git a/tests/NeuralNetwork/Node/Neuron/SynapseTest.php b/tests/NeuralNetwork/Node/Neuron/SynapseTest.php index 973d2161..1e33f348 100644 --- a/tests/NeuralNetwork/Node/Neuron/SynapseTest.php +++ b/tests/NeuralNetwork/Node/Neuron/SynapseTest.php @@ -17,13 +17,14 @@ public function testSynapseInitialization(): void $synapse = new Synapse($node, $weight = 0.75); - $this->assertEquals($node, $synapse->getNode()); - $this->assertEquals($weight, $synapse->getWeight()); - $this->assertEquals($weight * $nodeOutput, $synapse->getOutput()); + self::assertEquals($node, $synapse->getNode()); + self::assertEquals($weight, $synapse->getWeight()); + self::assertEquals($weight * $nodeOutput, $synapse->getOutput()); $synapse = new Synapse($node); + $weight = $synapse->getWeight(); - $this->assertInternalType('float', $synapse->getWeight()); + self::assertTrue($weight === -1. || $weight === 1.); } public function testSynapseWeightChange(): void @@ -32,11 +33,11 @@ public function testSynapseWeightChange(): void $synapse = new Synapse($node, $weight = 0.75); $synapse->changeWeight(1.0); - $this->assertEquals(1.75, $synapse->getWeight()); + self::assertEquals(1.75, $synapse->getWeight()); $synapse->changeWeight(-2.0); - $this->assertEquals(-0.25, $synapse->getWeight()); + self::assertEquals(-0.25, $synapse->getWeight()); } /** diff --git a/tests/NeuralNetwork/Node/NeuronTest.php b/tests/NeuralNetwork/Node/NeuronTest.php index 03e309d1..b1a77a80 100644 --- a/tests/NeuralNetwork/Node/NeuronTest.php +++ b/tests/NeuralNetwork/Node/NeuronTest.php @@ -16,8 +16,8 @@ public function testNeuronInitialization(): void { $neuron = new Neuron(); - $this->assertEquals([], $neuron->getSynapses()); - $this->assertEquals(0.5, $neuron->getOutput()); + self::assertEquals([], $neuron->getSynapses()); + self::assertEquals(0.5, $neuron->getOutput()); } public function testNeuronActivationFunction(): void @@ -28,7 +28,7 @@ public function testNeuronActivationFunction(): void $neuron = new Neuron($activationFunction); - $this->assertEquals($output, $neuron->getOutput()); + self::assertEquals($output, $neuron->getOutput()); } public function testNeuronWithSynapse(): void @@ -36,8 +36,8 @@ public function testNeuronWithSynapse(): void $neuron = new Neuron(); $neuron->addSynapse($synapse = $this->getSynapseMock()); - $this->assertEquals([$synapse], $neuron->getSynapses()); - $this->assertEquals(0.88, $neuron->getOutput(), '', 0.01); + self::assertEquals([$synapse], $neuron->getSynapses()); + self::assertEquals(0.88, $neuron->getOutput(), '', 0.01); } public function testNeuronRefresh(): void @@ -46,11 +46,11 @@ public function testNeuronRefresh(): void $neuron->getOutput(); $neuron->addSynapse($this->getSynapseMock()); - $this->assertEquals(0.5, $neuron->getOutput(), '', 0.01); + self::assertEquals(0.5, $neuron->getOutput(), '', 0.01); $neuron->reset(); - $this->assertEquals(0.88, $neuron->getOutput(), '', 0.01); + self::assertEquals(0.88, $neuron->getOutput(), '', 0.01); } /** diff --git a/tests/PipelineTest.php b/tests/PipelineTest.php index d72e0c81..0ba91c66 100644 --- a/tests/PipelineTest.php +++ b/tests/PipelineTest.php @@ -28,8 +28,8 @@ public function testPipelineConstruction(): void $pipeline = new Pipeline($transformers, $estimator); - $this->assertEquals($transformers, $pipeline->getTransformers()); - $this->assertEquals($estimator, $pipeline->getEstimator()); + self::assertEquals($transformers, $pipeline->getTransformers()); + self::assertEquals($estimator, $pipeline->getEstimator()); } public function testPipelineEstimatorSetter(): void @@ -39,7 +39,7 @@ public function testPipelineEstimatorSetter(): void $estimator = new SVR(); $pipeline->setEstimator($estimator); - $this->assertEquals($estimator, $pipeline->getEstimator()); + self::assertEquals($estimator, $pipeline->getEstimator()); } public function testPipelineWorkflow(): void @@ -67,7 +67,7 @@ public function testPipelineWorkflow(): void $predicted = $pipeline->predict([[0, 0, 0]]); - $this->assertEquals(4, $predicted[0]); + self::assertEquals(4, $predicted[0]); } public function testPipelineTransformers(): void @@ -104,7 +104,7 @@ public function testPipelineTransformers(): void $predicted = $pipeline->predict(['Hello Max', 'Goodbye Mark']); - $this->assertEquals($expected, $predicted); + self::assertEquals($expected, $predicted); } public function testPipelineTransformersWithTargets(): void @@ -145,13 +145,13 @@ public function testSaveAndRestore(): void $testSamples = ['Hello Max', 'Goodbye Mark']; $predicted = $pipeline->predict($testSamples); - $filepath = tempnam(sys_get_temp_dir(), uniqid('pipeline-test', true)); + $filepath = (string) tempnam(sys_get_temp_dir(), uniqid('pipeline-test', true)); $modelManager = new ModelManager(); $modelManager->saveToFile($pipeline, $filepath); $restoredClassifier = $modelManager->restoreFromFile($filepath); - $this->assertEquals($pipeline, $restoredClassifier); - $this->assertEquals($predicted, $restoredClassifier->predict($testSamples)); + self::assertEquals($pipeline, $restoredClassifier); + self::assertEquals($predicted, $restoredClassifier->predict($testSamples)); unlink($filepath); } } diff --git a/tests/Preprocessing/ImputerTest.php b/tests/Preprocessing/ImputerTest.php index 1078e547..dcbb8073 100644 --- a/tests/Preprocessing/ImputerTest.php +++ b/tests/Preprocessing/ImputerTest.php @@ -32,7 +32,7 @@ public function testComplementsMissingValuesWithMeanStrategyOnColumnAxis(): void $imputer = new Imputer(null, new MeanStrategy(), Imputer::AXIS_COLUMN, $data); $imputer->transform($data); - $this->assertEquals($imputeData, $data, '', $delta = 0.01); + self::assertEquals($imputeData, $data, '', $delta = 0.01); } public function testComplementsMissingValuesWithMeanStrategyOnRowAxis(): void @@ -54,7 +54,7 @@ public function testComplementsMissingValuesWithMeanStrategyOnRowAxis(): void $imputer = new Imputer(null, new MeanStrategy(), Imputer::AXIS_ROW, $data); $imputer->transform($data); - $this->assertEquals($imputeData, $data, '', $delta = 0.01); + self::assertEquals($imputeData, $data, '', $delta = 0.01); } public function testComplementsMissingValuesWithMediaStrategyOnColumnAxis(): void @@ -76,7 +76,7 @@ public function testComplementsMissingValuesWithMediaStrategyOnColumnAxis(): voi $imputer = new Imputer(null, new MedianStrategy(), Imputer::AXIS_COLUMN, $data); $imputer->transform($data); - $this->assertEquals($imputeData, $data, '', $delta = 0.01); + self::assertEquals($imputeData, $data, '', $delta = 0.01); } public function testComplementsMissingValuesWithMediaStrategyOnRowAxis(): void @@ -98,7 +98,7 @@ public function testComplementsMissingValuesWithMediaStrategyOnRowAxis(): void $imputer = new Imputer(null, new MedianStrategy(), Imputer::AXIS_ROW, $data); $imputer->transform($data); - $this->assertEquals($imputeData, $data, '', $delta = 0.01); + self::assertEquals($imputeData, $data, '', $delta = 0.01); } public function testComplementsMissingValuesWithMostFrequentStrategyOnColumnAxis(): void @@ -122,7 +122,7 @@ public function testComplementsMissingValuesWithMostFrequentStrategyOnColumnAxis $imputer = new Imputer(null, new MostFrequentStrategy(), Imputer::AXIS_COLUMN, $data); $imputer->transform($data); - $this->assertEquals($imputeData, $data); + self::assertEquals($imputeData, $data); } public function testComplementsMissingValuesWithMostFrequentStrategyOnRowAxis(): void @@ -146,7 +146,7 @@ public function testComplementsMissingValuesWithMostFrequentStrategyOnRowAxis(): $imputer = new Imputer(null, new MostFrequentStrategy(), Imputer::AXIS_ROW, $data); $imputer->transform($data); - $this->assertEquals($imputeData, $data); + self::assertEquals($imputeData, $data); } public function testImputerWorksOnFitSamples(): void @@ -172,7 +172,7 @@ public function testImputerWorksOnFitSamples(): void $imputer = new Imputer(null, new MeanStrategy(), Imputer::AXIS_COLUMN, $trainData); $imputer->transform($data); - $this->assertEquals($imputeData, $data, '', $delta = 0.01); + self::assertEquals($imputeData, $data, '', $delta = 0.01); } public function testThrowExceptionWhenTryingToTransformWithoutTrainSamples(): void diff --git a/tests/Preprocessing/NormalizerTest.php b/tests/Preprocessing/NormalizerTest.php index ea762da0..53b07d86 100644 --- a/tests/Preprocessing/NormalizerTest.php +++ b/tests/Preprocessing/NormalizerTest.php @@ -33,7 +33,7 @@ public function testNormalizeSamplesWithL2Norm(): void $normalizer = new Normalizer(); $normalizer->transform($samples); - $this->assertEquals($normalized, $samples, '', $delta = 0.01); + self::assertEquals($normalized, $samples, '', $delta = 0.01); } public function testNormalizeSamplesWithL1Norm(): void @@ -53,7 +53,7 @@ public function testNormalizeSamplesWithL1Norm(): void $normalizer = new Normalizer(Normalizer::NORM_L1); $normalizer->transform($samples); - $this->assertEquals($normalized, $samples, '', $delta = 0.01); + self::assertEquals($normalized, $samples, '', $delta = 0.01); } public function testFitNotChangeNormalizerBehavior(): void @@ -73,11 +73,11 @@ public function testFitNotChangeNormalizerBehavior(): void $normalizer = new Normalizer(); $normalizer->transform($samples); - $this->assertEquals($normalized, $samples, '', $delta = 0.01); + self::assertEquals($normalized, $samples, '', $delta = 0.01); $normalizer->fit($samples); - $this->assertEquals($normalized, $samples, '', $delta = 0.01); + self::assertEquals($normalized, $samples, '', $delta = 0.01); } public function testL1NormWithZeroSumCondition(): void @@ -97,7 +97,7 @@ public function testL1NormWithZeroSumCondition(): void $normalizer = new Normalizer(Normalizer::NORM_L1); $normalizer->transform($samples); - $this->assertEquals($normalized, $samples, '', $delta = 0.01); + self::assertEquals($normalized, $samples, '', $delta = 0.01); } public function testStandardNorm(): void @@ -122,7 +122,7 @@ public function testStandardNorm(): void $normalizer->transform($samples); // Values in the vector should be some value between -3 and +3 - $this->assertCount(10, $samples); + self::assertCount(10, $samples); foreach ($samples as $sample) { $errors = array_filter( $sample, @@ -130,8 +130,8 @@ function ($element) { return $element < -3 || $element > 3; } ); - $this->assertCount(0, $errors); - $this->assertEquals(0, $sample[3]); + self::assertCount(0, $errors); + self::assertEquals(0, $sample[3]); } } } diff --git a/tests/Regression/LeastSquaresTest.php b/tests/Regression/LeastSquaresTest.php index 71215bc6..1142a37e 100644 --- a/tests/Regression/LeastSquaresTest.php +++ b/tests/Regression/LeastSquaresTest.php @@ -21,7 +21,7 @@ public function testPredictSingleFeatureSamples(): void $regression = new LeastSquares(); $regression->train($samples, $targets); - $this->assertEquals(4.06, $regression->predict([64]), '', $delta); + self::assertEquals(4.06, $regression->predict([64]), '', $delta); //http://www.stat.wmich.edu/s216/book/node127.html $samples = [[9300], [10565], [15000], [15000], [17764], [57000], [65940], [73676], [77006], [93739], [146088], [153260]]; @@ -30,11 +30,11 @@ public function testPredictSingleFeatureSamples(): void $regression = new LeastSquares(); $regression->train($samples, $targets); - $this->assertEquals(7659.35, $regression->predict([9300]), '', $delta); - $this->assertEquals(5213.81, $regression->predict([57000]), '', $delta); - $this->assertEquals(4188.13, $regression->predict([77006]), '', $delta); - $this->assertEquals(7659.35, $regression->predict([9300]), '', $delta); - $this->assertEquals(278.66, $regression->predict([153260]), '', $delta); + self::assertEquals(7659.35, $regression->predict([9300]), '', $delta); + self::assertEquals(5213.81, $regression->predict([57000]), '', $delta); + self::assertEquals(4188.13, $regression->predict([77006]), '', $delta); + self::assertEquals(7659.35, $regression->predict([9300]), '', $delta); + self::assertEquals(278.66, $regression->predict([153260]), '', $delta); } public function testPredictSingleFeatureSamplesWithMatrixTargets(): void @@ -48,7 +48,7 @@ public function testPredictSingleFeatureSamplesWithMatrixTargets(): void $regression = new LeastSquares(); $regression->train($samples, $targets); - $this->assertEquals(4.06, $regression->predict([64]), '', $delta); + self::assertEquals(4.06, $regression->predict([64]), '', $delta); } public function testPredictMultiFeaturesSamples(): void @@ -62,10 +62,10 @@ public function testPredictMultiFeaturesSamples(): void $regression = new LeastSquares(); $regression->train($samples, $targets); - $this->assertEquals(-800614.957, $regression->getIntercept(), '', $delta); - $this->assertEquals([-0.0327, 404.14], $regression->getCoefficients(), '', $delta); - $this->assertEquals(4094.82, $regression->predict([60000, 1996]), '', $delta); - $this->assertEquals(5711.40, $regression->predict([60000, 2000]), '', $delta); + self::assertEquals(-800614.957, $regression->getIntercept(), '', $delta); + self::assertEquals([-0.0327, 404.14], $regression->getCoefficients(), '', $delta); + self::assertEquals(4094.82, $regression->predict([60000, 1996]), '', $delta); + self::assertEquals(5711.40, $regression->predict([60000, 2000]), '', $delta); } public function testSaveAndRestore(): void @@ -81,13 +81,13 @@ public function testSaveAndRestore(): void $testSamples = [[9300], [10565], [15000]]; $predicted = $regression->predict($testSamples); - $filename = 'least-squares-test-'.random_int(100, 999).'-'.uniqid(); - $filepath = tempnam(sys_get_temp_dir(), $filename); + $filename = 'least-squares-test-'.random_int(100, 999).'-'.uniqid('', false); + $filepath = (string) tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($regression, $filepath); $restoredRegression = $modelManager->restoreFromFile($filepath); - $this->assertEquals($regression, $restoredRegression); - $this->assertEquals($predicted, $restoredRegression->predict($testSamples)); + self::assertEquals($regression, $restoredRegression); + self::assertEquals($predicted, $restoredRegression->predict($testSamples)); } } diff --git a/tests/Regression/SVRTest.php b/tests/Regression/SVRTest.php index d2fdefbb..89099c0b 100644 --- a/tests/Regression/SVRTest.php +++ b/tests/Regression/SVRTest.php @@ -21,7 +21,7 @@ public function testPredictSingleFeatureSamples(): void $regression = new SVR(Kernel::LINEAR); $regression->train($samples, $targets); - $this->assertEquals(4.03, $regression->predict([64]), '', $delta); + self::assertEquals(4.03, $regression->predict([64]), '', $delta); } public function testPredictMultiFeaturesSamples(): void @@ -34,7 +34,7 @@ public function testPredictMultiFeaturesSamples(): void $regression = new SVR(Kernel::LINEAR); $regression->train($samples, $targets); - $this->assertEquals([4109.82, 4112.28], $regression->predict([[60000, 1996], [60000, 2000]]), '', $delta); + self::assertEquals([4109.82, 4112.28], $regression->predict([[60000, 1996], [60000, 2000]]), '', $delta); } public function testSaveAndRestore(): void @@ -48,13 +48,13 @@ public function testSaveAndRestore(): void $testSamples = [64]; $predicted = $regression->predict($testSamples); - $filename = 'svr-test'.random_int(100, 999).'-'.uniqid(); - $filepath = tempnam(sys_get_temp_dir(), $filename); + $filename = 'svr-test'.random_int(100, 999).'-'.uniqid('', false); + $filepath = (string) tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($regression, $filepath); $restoredRegression = $modelManager->restoreFromFile($filepath); - $this->assertEquals($regression, $restoredRegression); - $this->assertEquals($predicted, $restoredRegression->predict($testSamples)); + self::assertEquals($regression, $restoredRegression); + self::assertEquals($predicted, $restoredRegression->predict($testSamples)); } } diff --git a/tests/SupportVectorMachine/DataTransformerTest.php b/tests/SupportVectorMachine/DataTransformerTest.php index df298062..32d7d324 100644 --- a/tests/SupportVectorMachine/DataTransformerTest.php +++ b/tests/SupportVectorMachine/DataTransformerTest.php @@ -22,7 +22,7 @@ public function testTransformDatasetToTrainingSet(): void '1 1:4.000000 2:5.000000 '.PHP_EOL ; - $this->assertEquals($trainingSet, DataTransformer::trainingSet($samples, $labels)); + self::assertEquals($trainingSet, DataTransformer::trainingSet($samples, $labels)); } public function testTransformSamplesToTestSet(): void @@ -36,7 +36,7 @@ public function testTransformSamplesToTestSet(): void '0 1:4.000000 2:5.000000 '.PHP_EOL ; - $this->assertEquals($testSet, DataTransformer::testSet($samples)); + self::assertEquals($testSet, DataTransformer::testSet($samples)); } public function testPredictions(): void @@ -46,7 +46,7 @@ public function testPredictions(): void $predictions = ['a', 'b', 'a', 'a']; - $this->assertEquals($predictions, DataTransformer::predictions($rawPredictions, $labels)); + self::assertEquals($predictions, DataTransformer::predictions($rawPredictions, $labels)); } public function testProbabilities(): void @@ -77,7 +77,7 @@ public function testProbabilities(): void ], ]; - $this->assertEquals($probabilities, DataTransformer::probabilities($rawPredictions, $labels)); + self::assertEquals($probabilities, DataTransformer::probabilities($rawPredictions, $labels)); } public function testThrowExceptionWhenTestSetIsEmpty(): void diff --git a/tests/SupportVectorMachine/SupportVectorMachineTest.php b/tests/SupportVectorMachine/SupportVectorMachineTest.php index 088d37b0..e3bbc85c 100644 --- a/tests/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/SupportVectorMachine/SupportVectorMachineTest.php @@ -35,7 +35,7 @@ public function testTrainCSVCModelWithLinearKernel(): void $svm = new SupportVectorMachine(Type::C_SVC, Kernel::LINEAR, 100.0); $svm->train($samples, $labels); - $this->assertEquals($model, $svm->getModel()); + self::assertEquals($model, $svm->getModel()); } public function testTrainCSVCModelWithProbabilityEstimate(): void @@ -59,8 +59,8 @@ public function testTrainCSVCModelWithProbabilityEstimate(): void ); $svm->train($samples, $labels); - $this->assertContains(PHP_EOL.'probA ', $svm->getModel()); - $this->assertContains(PHP_EOL.'probB ', $svm->getModel()); + self::assertContains(PHP_EOL.'probA ', $svm->getModel()); + self::assertContains(PHP_EOL.'probB ', $svm->getModel()); } public function testPredictSampleWithLinearKernel(): void @@ -77,9 +77,9 @@ public function testPredictSampleWithLinearKernel(): void [4, -5], ]); - $this->assertEquals('b', $predictions[0]); - $this->assertEquals('a', $predictions[1]); - $this->assertEquals('b', $predictions[2]); + self::assertEquals('b', $predictions[0]); + self::assertEquals('a', $predictions[1]); + self::assertEquals('b', $predictions[2]); } public function testPredictSampleFromMultipleClassWithRbfKernel(): void @@ -104,9 +104,9 @@ public function testPredictSampleFromMultipleClassWithRbfKernel(): void [-4, -3], ]); - $this->assertEquals('a', $predictions[0]); - $this->assertEquals('b', $predictions[1]); - $this->assertEquals('c', $predictions[2]); + self::assertEquals('a', $predictions[0]); + self::assertEquals('b', $predictions[1]); + self::assertEquals('c', $predictions[2]); } public function testPredictProbability(): void @@ -136,12 +136,12 @@ public function testPredictProbability(): void [4, -5], ]); - $this->assertTrue($predictions[0]['a'] < $predictions[0]['b']); - $this->assertTrue($predictions[1]['a'] > $predictions[1]['b']); - $this->assertTrue($predictions[2]['a'] < $predictions[2]['b']); + self::assertTrue($predictions[0]['a'] < $predictions[0]['b']); + self::assertTrue($predictions[1]['a'] > $predictions[1]['b']); + self::assertTrue($predictions[2]['a'] < $predictions[2]['b']); // Should be true because the latter is farther from the decision boundary - $this->assertTrue($predictions[0]['b'] < $predictions[2]['b']); + self::assertTrue($predictions[0]['b'] < $predictions[2]['b']); } public function testThrowExceptionWhenVarPathIsNotWritable(): void diff --git a/tests/Tokenization/WhitespaceTokenizerTest.php b/tests/Tokenization/WhitespaceTokenizerTest.php index 03e8f7ea..d6dd4cf7 100644 --- a/tests/Tokenization/WhitespaceTokenizerTest.php +++ b/tests/Tokenization/WhitespaceTokenizerTest.php @@ -21,7 +21,7 @@ public function testTokenizationOnAscii(): void 'Cras', 'consectetur,', 'dui', 'et', 'lobortis', 'auctor.', 'Nulla', 'vitae', 'congue', 'lorem.', ]; - $this->assertEquals($tokens, $tokenizer->tokenize($text)); + self::assertEquals($tokens, $tokenizer->tokenize($text)); } public function testTokenizationOnUtf8(): void @@ -36,6 +36,6 @@ public function testTokenizationOnUtf8(): void '剆坲', '煘煓瑐', '鬐鶤鶐', '飹勫嫢', '銪', '餀', '枲柊氠', '鍎鞚韕', '焲犈,', '殍涾烰', '齞齝囃', '蹅輶', '鄜,', '孻憵', '擙樲橚', '藒襓謥', '岯岪弨', '蒮', '廞徲', '孻憵懥', '趡趛踠', '槏', ]; - $this->assertEquals($tokens, $tokenizer->tokenize($text)); + self::assertEquals($tokens, $tokenizer->tokenize($text)); } } diff --git a/tests/Tokenization/WordTokenizerTest.php b/tests/Tokenization/WordTokenizerTest.php index 387e0f8b..39448b78 100644 --- a/tests/Tokenization/WordTokenizerTest.php +++ b/tests/Tokenization/WordTokenizerTest.php @@ -21,7 +21,7 @@ public function testTokenizationOnAscii(): void 'Cras', 'consectetur', 'dui', 'et', 'lobortis', 'auctor', 'Nulla', 'vitae', 'congue', 'lorem', ]; - $this->assertEquals($tokens, $tokenizer->tokenize($text)); + self::assertEquals($tokens, $tokenizer->tokenize($text)); } public function testTokenizationOnUtf8(): void @@ -36,6 +36,6 @@ public function testTokenizationOnUtf8(): void '剆坲', '煘煓瑐', '鬐鶤鶐', '飹勫嫢', '枲柊氠', '鍎鞚韕', '焲犈', '殍涾烰', '齞齝囃', '蹅輶', '孻憵', '擙樲橚', '藒襓謥', '岯岪弨', '廞徲', '孻憵懥', '趡趛踠', ]; - $this->assertEquals($tokens, $tokenizer->tokenize($text)); + self::assertEquals($tokens, $tokenizer->tokenize($text)); } } From f2dd40cb6f6350e7eaaff0beae1131ae37b0a8c4 Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Mon, 29 Oct 2018 20:04:06 +0100 Subject: [PATCH 288/328] Properly check cluster points label (#323) --- src/Clustering/KMeans/Cluster.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Clustering/KMeans/Cluster.php b/src/Clustering/KMeans/Cluster.php index fa73e4bb..f4c3d3ee 100644 --- a/src/Clustering/KMeans/Cluster.php +++ b/src/Clustering/KMeans/Cluster.php @@ -31,7 +31,7 @@ public function getPoints(): array { $points = []; foreach ($this->points as $point) { - if (count($point->label) === 0) { + if ($point->label === null) { $points[] = $point->toArray(); } else { $points[$point->label] = $point->toArray(); From 8ac013b2e49fb069002b1698dccf4da539a02f58 Mon Sep 17 00:00:00 2001 From: Andrey Bolonin Date: Sat, 3 Nov 2018 12:48:08 +0200 Subject: [PATCH 289/328] Add PHP 7.3 to Travis CI build (#322) * Update .travis.yml * remove coverage-clover for 7.2 * move coverage-clover to 7.2 --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 58ccbc60..77f956c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,9 @@ matrix: - os: linux php: '7.2' env: PHPUNIT_FLAGS="--coverage-clover build/logs/clover.xml" + + - os: linux + php: '7.3' - os: osx osx_image: xcode7.3 From 18c36b971ff2e4529368d3df9807457874521e6f Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 7 Nov 2018 08:02:56 +0100 Subject: [PATCH 290/328] Mnist Dataset (#326) * Implement MnistDataset * Add MNIST dataset documentation --- README.md | 1 + docs/index.md | 1 + .../datasets/mnist-dataset.md | 26 +++++ mkdocs.yml | 1 + phpstan.neon | 2 +- src/Dataset/MnistDataset.php | 101 ++++++++++++++++++ tests/Dataset/MnistDatasetTest.php | 33 ++++++ .../Dataset/Resources/mnist/images-idx-ubyte | Bin 0 -> 7856 bytes .../Resources/mnist/labels-11-idx-ubyte | Bin 0 -> 19 bytes .../Dataset/Resources/mnist/labels-idx-ubyte | Bin 0 -> 18 bytes 10 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 docs/machine-learning/datasets/mnist-dataset.md create mode 100644 src/Dataset/MnistDataset.php create mode 100644 tests/Dataset/MnistDatasetTest.php create mode 100644 tests/Dataset/Resources/mnist/images-idx-ubyte create mode 100644 tests/Dataset/Resources/mnist/labels-11-idx-ubyte create mode 100644 tests/Dataset/Resources/mnist/labels-idx-ubyte diff --git a/README.md b/README.md index d93996f7..f518fd0d 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,7 @@ Public datasets are available in a separate repository [php-ai/php-ml-datasets]( * [CSV](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/csv-dataset/) * [Files](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/files-dataset/) * [SVM](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/svm-dataset/) + * [MNIST](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/mnist-dataset.md) * Ready to use: * [Iris](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/demo/iris/) * [Wine](http://php-ml.readthedocs.io/en/latest/machine-learning/datasets/demo/wine/) diff --git a/docs/index.md b/docs/index.md index 12cbbd5f..3c6ede22 100644 --- a/docs/index.md +++ b/docs/index.md @@ -93,6 +93,7 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * [CSV](machine-learning/datasets/csv-dataset.md) * [Files](machine-learning/datasets/files-dataset.md) * [SVM](machine-learning/datasets/svm-dataset.md) + * [MNIST](machine-learning/datasets/mnist-dataset.md) * Ready to use: * [Iris](machine-learning/datasets/demo/iris.md) * [Wine](machine-learning/datasets/demo/wine.md) diff --git a/docs/machine-learning/datasets/mnist-dataset.md b/docs/machine-learning/datasets/mnist-dataset.md new file mode 100644 index 00000000..1ed50816 --- /dev/null +++ b/docs/machine-learning/datasets/mnist-dataset.md @@ -0,0 +1,26 @@ +# MnistDataset + +Helper class that load data from MNIST dataset: [http://yann.lecun.com/exdb/mnist/](http://yann.lecun.com/exdb/mnist/) + +> The MNIST database of handwritten digits, available from this page, has a training set of 60,000 examples, and a test set of 10,000 examples. It is a subset of a larger set available from NIST. The digits have been size-normalized and centered in a fixed-size image. + It is a good database for people who want to try learning techniques and pattern recognition methods on real-world data while spending minimal efforts on preprocessing and formatting. + +### Constructors Parameters + +* $imagePath - (string) path to image file +* $labelPath - (string) path to label file + +``` +use Phpml\Dataset\MnistDataset; + +$trainDataset = new MnistDataset('train-images-idx3-ubyte', 'train-labels-idx1-ubyte'); +``` + +### Samples and labels + +To get samples or labels you can use getters: + +``` +$dataset->getSamples(); +$dataset->getTargets(); +``` diff --git a/mkdocs.yml b/mkdocs.yml index 490e5dc0..451d6e90 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -39,6 +39,7 @@ pages: - CSV Dataset: machine-learning/datasets/csv-dataset.md - Files Dataset: machine-learning/datasets/files-dataset.md - SVM Dataset: machine-learning/datasets/svm-dataset.md + - MNIST Dataset: machine-learning/datasets/mnist-dataset.md - Ready to use datasets: - Iris: machine-learning/datasets/demo/iris.md - Wine: machine-learning/datasets/demo/wine.md diff --git a/phpstan.neon b/phpstan.neon index 7a676fa0..0ee43c49 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -6,7 +6,7 @@ includes: parameters: ignoreErrors: - '#Property Phpml\\Clustering\\KMeans\\Cluster\:\:\$points \(iterable\\&SplObjectStorage\) does not accept SplObjectStorage#' - - '#Phpml\\Dataset\\FilesDataset::__construct\(\) does not call parent constructor from Phpml\\Dataset\\ArrayDataset#' + - '#Phpml\\Dataset\\(.*)Dataset::__construct\(\) does not call parent constructor from Phpml\\Dataset\\ArrayDataset#' # wide range cases - '#Parameter \#1 \$coordinates of class Phpml\\Clustering\\KMeans\\Point constructor expects array, array\|Phpml\\Clustering\\KMeans\\Point given#' diff --git a/src/Dataset/MnistDataset.php b/src/Dataset/MnistDataset.php new file mode 100644 index 00000000..59a3a26d --- /dev/null +++ b/src/Dataset/MnistDataset.php @@ -0,0 +1,101 @@ +samples = $this->readImages($imagePath); + $this->targets = $this->readLabels($labelPath); + + if (count($this->samples) !== count($this->targets)) { + throw new InvalidArgumentException('Must have the same number of images and labels'); + } + } + + private function readImages(string $imagePath): array + { + $stream = fopen($imagePath, 'rb'); + + if ($stream === false) { + throw new InvalidArgumentException('Could not open file: '.$imagePath); + } + + $images = []; + + try { + $header = fread($stream, 16); + + $fields = unpack('Nmagic/Nsize/Nrows/Ncols', (string) $header); + + if ($fields['magic'] !== self::MAGIC_IMAGE) { + throw new InvalidArgumentException('Invalid magic number: '.$imagePath); + } + + if ($fields['rows'] != self::IMAGE_ROWS) { + throw new InvalidArgumentException('Invalid number of image rows: '.$imagePath); + } + + if ($fields['cols'] != self::IMAGE_COLS) { + throw new InvalidArgumentException('Invalid number of image cols: '.$imagePath); + } + + for ($i = 0; $i < $fields['size']; $i++) { + $imageBytes = fread($stream, $fields['rows'] * $fields['cols']); + + // Convert to float between 0 and 1 + $images[] = array_map(function ($b) { + return $b / 255; + }, array_values(unpack('C*', (string) $imageBytes))); + } + } finally { + fclose($stream); + } + + return $images; + } + + private function readLabels(string $labelPath): array + { + $stream = fopen($labelPath, 'rb'); + + if ($stream === false) { + throw new InvalidArgumentException('Could not open file: '.$labelPath); + } + + $labels = []; + + try { + $header = fread($stream, 8); + + $fields = unpack('Nmagic/Nsize', (string) $header); + + if ($fields['magic'] !== self::MAGIC_LABEL) { + throw new InvalidArgumentException('Invalid magic number: '.$labelPath); + } + + $labels = fread($stream, $fields['size']); + } finally { + fclose($stream); + } + + return array_values(unpack('C*', (string) $labels)); + } +} diff --git a/tests/Dataset/MnistDatasetTest.php b/tests/Dataset/MnistDatasetTest.php new file mode 100644 index 00000000..5fc73744 --- /dev/null +++ b/tests/Dataset/MnistDatasetTest.php @@ -0,0 +1,33 @@ +getSamples()); + self::assertCount(10, $dataset->getTargets()); + } + + public function testCheckSamplesAndTargetsCountMatch(): void + { + $this->expectException(InvalidArgumentException::class); + + new MnistDataset( + __DIR__.'/Resources/mnist/images-idx-ubyte', + __DIR__.'/Resources/mnist/labels-11-idx-ubyte' + ); + } +} diff --git a/tests/Dataset/Resources/mnist/images-idx-ubyte b/tests/Dataset/Resources/mnist/images-idx-ubyte new file mode 100644 index 0000000000000000000000000000000000000000..40b870a36999319757f59edae7335dcfe73a7dae GIT binary patch literal 7856 zcmeHMYfKeK6rM^|@GSyrV}(>Pz7XH3Pij$%v1qMeo2t>Y{_s(2d`847M!`~*+NvlT z@r|NYs5K;3@evg9S;QC>MWmn?P>Co4vU|?$?B1QdyR$oO`lm^)C%Jdd`Mz_`&YYc{ znZ1_~PY*&!Bl>BleQ5JKNW}3Sn+JEaAIW{BQkkvE!M;YX(0O6=_7r0g^{9J@xYo$2 zddm-+g@%R>@_m9cEu037hwI(~&acclUOY;q|CWG@MKs+UgWHhjVlE2tZfAq9QH{di zMfb+&_OIv*zCGl0CQi1yRw}5H-`+|p)Xju+fF%~LZyn?XG$9SHfpy-7OL=W+z>-=R zW{D&IYE7<#9jAIPtbuH0m9UYIYrqj=gDX%Bzoy)suH;_EP6QrdrxVIL?A;K%3HAB8n`=9YSt1 zCJ4C_{7}RVk;L3Oys8{S?4;2xjIzTOtbTU7`;ikly|#GcM7ZQH7+s(w!7O{u_q>q% z=9EOa)Y*1N4WN`nzwY(1q|IXB*F)w%U_ zBn3lQ4z*=mMgirHnJAJEFltl@fLa_gMdqrdbbW2$I9d%?tsiA}u*~kE2`OGhI|7rGqppARvyOsh(IW%bF0iJt1i*Zx)__cn79H+U@lYPyBK~L_mPivYx>O`N{edkSgzIZm!nr z3VTQK1Itp8Dqo1CvSM$RtY4XhcRBb{rc&yVWZ8>>dfO|43L(2>y;nNLl;yG<#B{!; z=S3dUZ5pTRys}!Z;vwBGeKJSadF5AgF?ss6w!?9jXwz*~jA*^Lu4;@!+$VTnYj_!P zl%Qt)==-#QB5{b^;_3E0hqXQ3mPs3k_|ya#j5&y_45mrlgB8k;9d!NwwP z6>&5rd$4(zb~5Ji7_%oJu4Fpj&cJdX9%FVIUNJ8ajG$+t8+gubnKh4O+-XJ();rtn zHx+}+YpVL4i3?yuYaVVYY|F=WsXBG9GLY%C^~nVH*1ZO%87MLm+>3iRJlxd4`6aJrAJw*A?D}9zjq!pV2SN z&oeED#barFe0+C+U8buUjQ8-Gd80CLJ7I*uGO3YR+1msdjAYzru*|BXN*FdBOU=Fp zD;Kttrh=1CE^M86MKHcxT9>WXQ-PX!$f=mt4Ghw&|=k5iRwY_yC}l1yzA| zkLmnN@wO$`s}rs_Jr^LWSGutmpoW%x5E{^&_}vO?3#ii;nfoItzb zl_q}3$mj5fbrf3mdMU6>zYwqVdL_>=Jphs1cjpW4oQR~f+*z?sb1bo zz$$OG(uRpqkzKn+^2yg|gI1bS?ybtBU;cfG@$ynj_Rui;ORXwt7LJg`@M=q+y#RJx zZyI%y-iVDhabEkClyzxbsa>9}k2#x+ye?#+*p-B;<7G?CL`Ip^FgDWq@Td>J889Hj?Hw z9Ad&=U`c-^k4M0QqUY$`ZP?I8Kf}V=-yc1inkPAERH<=s^-8M(C!(Zu6XiqPKLahk z%S!(#DM#TFTbO#ujNSBK$FJ<)xfazSe!=|A?KJvN*Nui|Otbn|B0e8vOOcg`RUg<> vO|u&?lM=*ST2DI}L&q+9U%rkQZx^Fr#m+LWRBq3?&ad6Z-)nyYgxQHU literal 0 HcmV?d00001 diff --git a/tests/Dataset/Resources/mnist/labels-11-idx-ubyte b/tests/Dataset/Resources/mnist/labels-11-idx-ubyte new file mode 100644 index 0000000000000000000000000000000000000000..db9362d1c9efd60e2a836a33f8834979d23196e1 GIT binary patch literal 19 acmZQz;9z86P#0ilVq{=pWZ`7xWB>pJO8{#C literal 0 HcmV?d00001 diff --git a/tests/Dataset/Resources/mnist/labels-idx-ubyte b/tests/Dataset/Resources/mnist/labels-idx-ubyte new file mode 100644 index 0000000000000000000000000000000000000000..eca5265988e2688261819cf634b7be2e5089d7bd GIT binary patch literal 18 ZcmZQz;9z86P#0ilVq{=pWZ`7x1ONlz0BZmM literal 0 HcmV?d00001 From d30c212f3bedb802d4d1acaf9eecf2fe18aaf097 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 7 Nov 2018 09:39:51 +0100 Subject: [PATCH 291/328] Check if feature exist when predict target in NaiveBayes (#327) * Check if feature exist when predict target in NaiveBayes * Fix typo --- src/Classification/NaiveBayes.php | 5 +++++ tests/Classification/NaiveBayesTest.php | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/Classification/NaiveBayes.php b/src/Classification/NaiveBayes.php index f14ada73..45075a42 100644 --- a/src/Classification/NaiveBayes.php +++ b/src/Classification/NaiveBayes.php @@ -4,6 +4,7 @@ namespace Phpml\Classification; +use Phpml\Exception\InvalidArgumentException; use Phpml\Helper\Predictable; use Phpml\Helper\Trainable; use Phpml\Math\Statistic\Mean; @@ -137,6 +138,10 @@ private function calculateStatistics(string $label, array $samples): void */ private function sampleProbability(array $sample, int $feature, string $label): float { + if (!isset($sample[$feature])) { + throw new InvalidArgumentException('Missing feature. All samples must have equal number of features'); + } + $value = $sample[$feature]; if ($this->dataType[$label][$feature] == self::NOMINAL) { if (!isset($this->discreteProb[$label][$feature][$value]) || diff --git a/tests/Classification/NaiveBayesTest.php b/tests/Classification/NaiveBayesTest.php index 4e272618..076a70d9 100644 --- a/tests/Classification/NaiveBayesTest.php +++ b/tests/Classification/NaiveBayesTest.php @@ -5,6 +5,7 @@ namespace Phpml\Tests\Classification; use Phpml\Classification\NaiveBayes; +use Phpml\Exception\InvalidArgumentException; use Phpml\ModelManager; use PHPUnit\Framework\TestCase; @@ -125,4 +126,19 @@ public function testSaveAndRestoreNumericLabels(): void self::assertEquals($classifier, $restoredClassifier); self::assertEquals($predicted, $restoredClassifier->predict($testSamples)); } + + public function testInconsistentFeaturesInSamples(): void + { + $trainSamples = [[5, 1, 1], [1, 5, 1], [1, 1, 5]]; + $trainLabels = ['1996', '1997', '1998']; + + $testSamples = [[3, 1, 1], [5, 1], [4, 3, 8]]; + + $classifier = new NaiveBayes(); + $classifier->train($trainSamples, $trainLabels); + + $this->expectException(InvalidArgumentException::class); + + $classifier->predict($testSamples); + } } From e5189dfe178493d03763c39ec674bfb1f86ff48c Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 7 Nov 2018 17:58:40 +0100 Subject: [PATCH 292/328] Remove enforceTimeLimit flag from phpunit and update dependencies (#328) --- composer.lock | 287 +++++++++++++++++++++++++------------------------- phpunit.xml | 1 - 2 files changed, 141 insertions(+), 147 deletions(-) diff --git a/composer.lock b/composer.lock index 70324ee0..e0a85c6f 100644 --- a/composer.lock +++ b/composer.lock @@ -346,16 +346,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v2.13.0", + "version": "v2.13.1", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "7136aa4e0c5f912e8af82383775460d906168a10" + "reference": "54814c62d5beef3ba55297b9b3186ed8b8a1b161" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/7136aa4e0c5f912e8af82383775460d906168a10", - "reference": "7136aa4e0c5f912e8af82383775460d906168a10", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/54814c62d5beef3ba55297b9b3186ed8b8a1b161", + "reference": "54814c62d5beef3ba55297b9b3186ed8b8a1b161", "shasum": "" }, "require": { @@ -366,7 +366,7 @@ "ext-tokenizer": "*", "php": "^5.6 || >=7.0 <7.3", "php-cs-fixer/diff": "^1.3", - "symfony/console": "^3.2 || ^4.0", + "symfony/console": "^3.4.17 || ^4.1.6", "symfony/event-dispatcher": "^3.0 || ^4.0", "symfony/filesystem": "^3.0 || ^4.0", "symfony/finder": "^3.0 || ^4.0", @@ -402,11 +402,6 @@ "php-cs-fixer" ], "type": "application", - "extra": { - "branch-alias": { - "dev-master": "2.13-dev" - } - }, "autoload": { "psr-4": { "PhpCsFixer\\": "src/" @@ -438,7 +433,7 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2018-08-23T13:15:44+00:00" + "time": "2018-10-21T00:32:10+00:00" }, { "name": "jean85/pretty-package-versions", @@ -1653,16 +1648,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "6.1.0", + "version": "6.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "0685fb6a43aed1b2e09804d1aaf17144c82861f8" + "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/0685fb6a43aed1b2e09804d1aaf17144c82861f8", - "reference": "0685fb6a43aed1b2e09804d1aaf17144c82861f8", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", + "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", "shasum": "" }, "require": { @@ -1673,7 +1668,7 @@ "phpunit/php-text-template": "^1.2.1", "phpunit/php-token-stream": "^3.0", "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^3.1", + "sebastian/environment": "^3.1 || ^4.0", "sebastian/version": "^2.0.1", "theseer/tokenizer": "^1.1" }, @@ -1712,7 +1707,7 @@ "testing", "xunit" ], - "time": "2018-10-16T05:37:37+00:00" + "time": "2018-10-31T16:06:48+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1856,16 +1851,16 @@ }, { "name": "phpunit/php-token-stream", - "version": "3.0.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "21ad88bbba7c3d93530d93994e0a33cd45f02ace" + "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/21ad88bbba7c3d93530d93994e0a33cd45f02ace", - "reference": "21ad88bbba7c3d93530d93994e0a33cd45f02ace", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/c99e3be9d3e85f60646f152f9002d46ed7770d18", + "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18", "shasum": "" }, "require": { @@ -1901,20 +1896,20 @@ "keywords": [ "tokenizer" ], - "time": "2018-02-01T13:16:43+00:00" + "time": "2018-10-30T05:52:18+00:00" }, { "name": "phpunit/phpunit", - "version": "7.4.0", + "version": "7.4.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "f3837fa1e07758057ae06e8ddec6d06ba183f126" + "reference": "c151651fb6ed264038d486ea262e243af72e5e64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f3837fa1e07758057ae06e8ddec6d06ba183f126", - "reference": "f3837fa1e07758057ae06e8ddec6d06ba183f126", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c151651fb6ed264038d486ea262e243af72e5e64", + "reference": "c151651fb6ed264038d486ea262e243af72e5e64", "shasum": "" }, "require": { @@ -1935,7 +1930,7 @@ "phpunit/php-timer": "^2.0", "sebastian/comparator": "^3.0", "sebastian/diff": "^3.0", - "sebastian/environment": "^3.1", + "sebastian/environment": "^3.1 || ^4.0", "sebastian/exporter": "^3.1", "sebastian/global-state": "^2.0", "sebastian/object-enumerator": "^3.0.3", @@ -1985,7 +1980,7 @@ "testing", "xunit" ], - "time": "2018-10-05T04:05:24+00:00" + "time": "2018-10-23T05:57:41+00:00" }, { "name": "psr/cache", @@ -2838,21 +2833,21 @@ }, { "name": "slevomat/coding-standard", - "version": "4.8.5", + "version": "4.8.6", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "057f3f154cf4888b60eb4cdffadc509a3ae9dccd" + "reference": "af0c0c99e84106525484ef25f15144b9831522bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/057f3f154cf4888b60eb4cdffadc509a3ae9dccd", - "reference": "057f3f154cf4888b60eb4cdffadc509a3ae9dccd", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/af0c0c99e84106525484ef25f15144b9831522bb", + "reference": "af0c0c99e84106525484ef25f15144b9831522bb", "shasum": "" }, "require": { "php": "^7.1", - "squizlabs/php_codesniffer": "^3.3.0" + "squizlabs/php_codesniffer": "^3.3.1" }, "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", @@ -2873,7 +2868,7 @@ "MIT" ], "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "time": "2018-10-05T12:10:21+00:00" + "time": "2018-11-03T21:28:29+00:00" }, { "name": "squizlabs/php_codesniffer", @@ -2928,7 +2923,7 @@ }, { "name": "symfony/cache", - "version": "v4.1.6", + "version": "v4.1.7", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", @@ -2997,16 +2992,16 @@ }, { "name": "symfony/config", - "version": "v4.1.6", + "version": "v4.1.7", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "b3d4d7b567d7a49e6dfafb6d4760abc921177c96" + "reference": "991fec8bbe77367fc8b48ecbaa8a4bd6e905a238" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/b3d4d7b567d7a49e6dfafb6d4760abc921177c96", - "reference": "b3d4d7b567d7a49e6dfafb6d4760abc921177c96", + "url": "https://api.github.com/repos/symfony/config/zipball/991fec8bbe77367fc8b48ecbaa8a4bd6e905a238", + "reference": "991fec8bbe77367fc8b48ecbaa8a4bd6e905a238", "shasum": "" }, "require": { @@ -3056,20 +3051,20 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2018-09-08T13:24:10+00:00" + "time": "2018-10-31T09:09:42+00:00" }, { "name": "symfony/console", - "version": "v4.1.6", + "version": "v4.1.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "dc7122fe5f6113cfaba3b3de575d31112c9aa60b" + "reference": "432122af37d8cd52fba1b294b11976e0d20df595" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/dc7122fe5f6113cfaba3b3de575d31112c9aa60b", - "reference": "dc7122fe5f6113cfaba3b3de575d31112c9aa60b", + "url": "https://api.github.com/repos/symfony/console/zipball/432122af37d8cd52fba1b294b11976e0d20df595", + "reference": "432122af37d8cd52fba1b294b11976e0d20df595", "shasum": "" }, "require": { @@ -3124,20 +3119,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-10-03T08:15:46+00:00" + "time": "2018-10-31T09:30:44+00:00" }, { "name": "symfony/debug", - "version": "v4.1.6", + "version": "v4.1.7", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "e3f76ce6198f81994e019bb2b4e533e9de1b9b90" + "reference": "19090917b848a799cbae4800abf740fe4eb71c1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/e3f76ce6198f81994e019bb2b4e533e9de1b9b90", - "reference": "e3f76ce6198f81994e019bb2b4e533e9de1b9b90", + "url": "https://api.github.com/repos/symfony/debug/zipball/19090917b848a799cbae4800abf740fe4eb71c1d", + "reference": "19090917b848a799cbae4800abf740fe4eb71c1d", "shasum": "" }, "require": { @@ -3180,20 +3175,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2018-10-02T16:36:10+00:00" + "time": "2018-10-31T09:09:42+00:00" }, { "name": "symfony/dependency-injection", - "version": "v4.1.6", + "version": "v4.1.7", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "f6b9d893ad28aefd8942dc0469c8397e2216fe30" + "reference": "e72ee2c23d952e4c368ee98610fa22b79b89b483" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f6b9d893ad28aefd8942dc0469c8397e2216fe30", - "reference": "f6b9d893ad28aefd8942dc0469c8397e2216fe30", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e72ee2c23d952e4c368ee98610fa22b79b89b483", + "reference": "e72ee2c23d952e4c368ee98610fa22b79b89b483", "shasum": "" }, "require": { @@ -3251,20 +3246,20 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2018-10-02T12:40:59+00:00" + "time": "2018-10-31T10:54:16+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.1.6", + "version": "v4.1.7", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "bfb30c2ad377615a463ebbc875eba64a99f6aa3e" + "reference": "552541dad078c85d9414b09c041ede488b456cd5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/bfb30c2ad377615a463ebbc875eba64a99f6aa3e", - "reference": "bfb30c2ad377615a463ebbc875eba64a99f6aa3e", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/552541dad078c85d9414b09c041ede488b456cd5", + "reference": "552541dad078c85d9414b09c041ede488b456cd5", "shasum": "" }, "require": { @@ -3314,20 +3309,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2018-07-26T09:10:45+00:00" + "time": "2018-10-10T13:52:42+00:00" }, { "name": "symfony/filesystem", - "version": "v4.1.6", + "version": "v4.1.7", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "596d12b40624055c300c8b619755b748ca5cf0b5" + "reference": "fd7bd6535beb1f0a0a9e3ee960666d0598546981" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/596d12b40624055c300c8b619755b748ca5cf0b5", - "reference": "596d12b40624055c300c8b619755b748ca5cf0b5", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/fd7bd6535beb1f0a0a9e3ee960666d0598546981", + "reference": "fd7bd6535beb1f0a0a9e3ee960666d0598546981", "shasum": "" }, "require": { @@ -3364,11 +3359,11 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-10-02T12:40:59+00:00" + "time": "2018-10-30T13:18:25+00:00" }, { "name": "symfony/finder", - "version": "v4.1.6", + "version": "v4.1.7", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", @@ -3417,16 +3412,16 @@ }, { "name": "symfony/http-foundation", - "version": "v4.1.6", + "version": "v4.1.7", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "d528136617ff24f530e70df9605acc1b788b08d4" + "reference": "82d494c1492b0dd24bbc5c2d963fb02eb44491af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d528136617ff24f530e70df9605acc1b788b08d4", - "reference": "d528136617ff24f530e70df9605acc1b788b08d4", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/82d494c1492b0dd24bbc5c2d963fb02eb44491af", + "reference": "82d494c1492b0dd24bbc5c2d963fb02eb44491af", "shasum": "" }, "require": { @@ -3467,20 +3462,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2018-10-03T08:48:45+00:00" + "time": "2018-10-31T09:09:42+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.1.6", + "version": "v4.1.7", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "f5e7c15a5d010be0e16ce798594c5960451d4220" + "reference": "958be64ab13b65172ad646ef5ae20364c2305fae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f5e7c15a5d010be0e16ce798594c5960451d4220", - "reference": "f5e7c15a5d010be0e16ce798594c5960451d4220", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/958be64ab13b65172ad646ef5ae20364c2305fae", + "reference": "958be64ab13b65172ad646ef5ae20364c2305fae", "shasum": "" }, "require": { @@ -3554,11 +3549,11 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2018-10-03T12:53:38+00:00" + "time": "2018-11-03T11:11:23+00:00" }, { "name": "symfony/options-resolver", - "version": "v4.1.6", + "version": "v4.1.7", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", @@ -3612,7 +3607,7 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.9.0", + "version": "v1.10.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -3670,16 +3665,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.9.0", + "version": "v1.10.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8" + "reference": "c79c051f5b3a46be09205c73b80b346e4153e494" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d0cd638f4634c16d8df4508e847f14e9e43168b8", - "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494", + "reference": "c79c051f5b3a46be09205c73b80b346e4153e494", "shasum": "" }, "require": { @@ -3725,20 +3720,20 @@ "portable", "shim" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2018-09-21T13:07:52+00:00" }, { "name": "symfony/polyfill-php70", - "version": "v1.9.0", + "version": "v1.10.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "1e24b0c4a56d55aaf368763a06c6d1c7d3194934" + "reference": "6b88000cdd431cd2e940caa2cb569201f3f84224" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/1e24b0c4a56d55aaf368763a06c6d1c7d3194934", - "reference": "1e24b0c4a56d55aaf368763a06c6d1c7d3194934", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/6b88000cdd431cd2e940caa2cb569201f3f84224", + "reference": "6b88000cdd431cd2e940caa2cb569201f3f84224", "shasum": "" }, "require": { @@ -3784,20 +3779,20 @@ "portable", "shim" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2018-09-21T06:26:08+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.9.0", + "version": "v1.10.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "95c50420b0baed23852452a7f0c7b527303ed5ae" + "reference": "9050816e2ca34a8e916c3a0ae8b9c2fccf68b631" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/95c50420b0baed23852452a7f0c7b527303ed5ae", - "reference": "95c50420b0baed23852452a7f0c7b527303ed5ae", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9050816e2ca34a8e916c3a0ae8b9c2fccf68b631", + "reference": "9050816e2ca34a8e916c3a0ae8b9c2fccf68b631", "shasum": "" }, "require": { @@ -3839,20 +3834,20 @@ "portable", "shim" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2018-09-21T13:07:52+00:00" }, { "name": "symfony/process", - "version": "v4.1.6", + "version": "v4.1.7", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "ee33c0322a8fee0855afcc11fff81e6b1011b529" + "reference": "3e83acef94d979b1de946599ef86b3a352abcdc9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/ee33c0322a8fee0855afcc11fff81e6b1011b529", - "reference": "ee33c0322a8fee0855afcc11fff81e6b1011b529", + "url": "https://api.github.com/repos/symfony/process/zipball/3e83acef94d979b1de946599ef86b3a352abcdc9", + "reference": "3e83acef94d979b1de946599ef86b3a352abcdc9", "shasum": "" }, "require": { @@ -3888,11 +3883,11 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-10-02T12:40:59+00:00" + "time": "2018-10-14T20:48:13+00:00" }, { "name": "symfony/stopwatch", - "version": "v4.1.6", + "version": "v4.1.7", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", @@ -3941,7 +3936,7 @@ }, { "name": "symfony/yaml", - "version": "v4.1.6", + "version": "v4.1.7", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", @@ -4000,24 +3995,24 @@ }, { "name": "symplify/better-phpdoc-parser", - "version": "v5.1.2", + "version": "v5.2.0", "source": { "type": "git", "url": "https://github.com/Symplify/BetterPhpDocParser.git", - "reference": "0f276093a76f45bb07d74c3dfa4b3c9fa4a30805" + "reference": "9b3bf0de89bff4818b2178ff62bb63de19ba6119" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/BetterPhpDocParser/zipball/0f276093a76f45bb07d74c3dfa4b3c9fa4a30805", - "reference": "0f276093a76f45bb07d74c3dfa4b3c9fa4a30805", + "url": "https://api.github.com/repos/Symplify/BetterPhpDocParser/zipball/9b3bf0de89bff4818b2178ff62bb63de19ba6119", + "reference": "9b3bf0de89bff4818b2178ff62bb63de19ba6119", "shasum": "" }, "require": { "nette/utils": "^2.5", "php": "^7.1", - "phpstan/phpdoc-parser": "^0.2|^0.3", - "symplify/package-builder": "^5.1", - "thecodingmachine/safe": "^0.1.3" + "phpstan/phpdoc-parser": "^0.3", + "symplify/package-builder": "^5.2", + "thecodingmachine/safe": "^0.1.6" }, "require-dev": { "phpunit/phpunit": "^7.3" @@ -4038,20 +4033,20 @@ "MIT" ], "description": "Slim wrapper around phpstan/phpdoc-parser with format preserving printer", - "time": "2018-10-11T10:23:49+00:00" + "time": "2018-10-31T09:23:48+00:00" }, { "name": "symplify/coding-standard", - "version": "v5.1.2", + "version": "v5.2.0", "source": { "type": "git", "url": "https://github.com/Symplify/CodingStandard.git", - "reference": "e293e5038ab95f229e48d7396b09a91c6e0a8d90" + "reference": "d057df9d605d664a37cdc00e7dbe5afa90f0dd17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/CodingStandard/zipball/e293e5038ab95f229e48d7396b09a91c6e0a8d90", - "reference": "e293e5038ab95f229e48d7396b09a91c6e0a8d90", + "url": "https://api.github.com/repos/Symplify/CodingStandard/zipball/d057df9d605d664a37cdc00e7dbe5afa90f0dd17", + "reference": "d057df9d605d664a37cdc00e7dbe5afa90f0dd17", "shasum": "" }, "require": { @@ -4061,14 +4056,14 @@ "php": "^7.1", "slam/php-cs-fixer-extensions": "^1.17", "squizlabs/php_codesniffer": "^3.3", - "symplify/token-runner": "^5.1", - "thecodingmachine/safe": "^0.1.3" + "symplify/token-runner": "^5.2", + "thecodingmachine/safe": "^0.1.6" }, "require-dev": { "nette/application": "^2.4", "phpunit/phpunit": "^7.3", - "symplify/easy-coding-standard-tester": "^5.1", - "symplify/package-builder": "^5.1" + "symplify/easy-coding-standard-tester": "^5.2", + "symplify/package-builder": "^5.2" }, "type": "library", "extra": { @@ -4086,20 +4081,20 @@ "MIT" ], "description": "Set of Symplify rules for PHP_CodeSniffer and PHP CS Fixer.", - "time": "2018-10-11T10:23:49+00:00" + "time": "2018-10-31T15:24:17+00:00" }, { "name": "symplify/easy-coding-standard", - "version": "v5.1.2", + "version": "v5.2.0", "source": { "type": "git", "url": "https://github.com/Symplify/EasyCodingStandard.git", - "reference": "deb7f7f363491ea58ec2b1780d9b625ddeab2214" + "reference": "fc3e2f6749835628a125ee0d5d8db39294718ce3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/EasyCodingStandard/zipball/deb7f7f363491ea58ec2b1780d9b625ddeab2214", - "reference": "deb7f7f363491ea58ec2b1780d9b625ddeab2214", + "url": "https://api.github.com/repos/Symplify/EasyCodingStandard/zipball/fc3e2f6749835628a125ee0d5d8db39294718ce3", + "reference": "fc3e2f6749835628a125ee0d5d8db39294718ce3", "shasum": "" }, "require": { @@ -4118,14 +4113,14 @@ "symfony/finder": "^3.4|^4.1", "symfony/http-kernel": "^3.4|^4.1", "symfony/yaml": "^3.4|^4.1", - "symplify/coding-standard": "^5.1", - "symplify/package-builder": "^5.1", - "symplify/token-runner": "^5.1", - "thecodingmachine/safe": "^0.1.3" + "symplify/coding-standard": "^5.2", + "symplify/package-builder": "^5.2", + "symplify/token-runner": "^5.2", + "thecodingmachine/safe": "^0.1.6" }, "require-dev": { "phpunit/phpunit": "^7.3", - "symplify/easy-coding-standard-tester": "^5.1" + "symplify/easy-coding-standard-tester": "^5.2" }, "bin": [ "bin/ecs" @@ -4150,20 +4145,20 @@ "MIT" ], "description": "Use Coding Standard with 0-knowledge of PHP-CS-Fixer and PHP_CodeSniffer.", - "time": "2018-10-11T10:23:49+00:00" + "time": "2018-11-03T10:43:05+00:00" }, { "name": "symplify/package-builder", - "version": "v5.1.2", + "version": "v5.2.0", "source": { "type": "git", "url": "https://github.com/Symplify/PackageBuilder.git", - "reference": "095533bf1dddd1ab1a24b453a76f9f222cbf90e9" + "reference": "0b88fb5038d015e00179286d55445695372245aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/PackageBuilder/zipball/095533bf1dddd1ab1a24b453a76f9f222cbf90e9", - "reference": "095533bf1dddd1ab1a24b453a76f9f222cbf90e9", + "url": "https://api.github.com/repos/Symplify/PackageBuilder/zipball/0b88fb5038d015e00179286d55445695372245aa", + "reference": "0b88fb5038d015e00179286d55445695372245aa", "shasum": "" }, "require": { @@ -4177,7 +4172,7 @@ "symfony/finder": "^3.4|^4.1", "symfony/http-kernel": "^3.4|^4.1", "symfony/yaml": "^3.4|^4.1", - "thecodingmachine/safe": "^0.1.3" + "thecodingmachine/safe": "^0.1.6" }, "require-dev": { "phpunit/phpunit": "^7.3" @@ -4198,20 +4193,20 @@ "MIT" ], "description": "Dependency Injection, Console and Kernel toolkit for Symplify packages.", - "time": "2018-10-09T10:35:39+00:00" + "time": "2018-11-03T13:34:54+00:00" }, { "name": "symplify/token-runner", - "version": "v5.1.2", + "version": "v5.2.0", "source": { "type": "git", "url": "https://github.com/Symplify/TokenRunner.git", - "reference": "995a3127fb98791475f77882a0b3e028f88a11e9" + "reference": "88747d0c1b8021106d62fe1c8bc15284224d48a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/TokenRunner/zipball/995a3127fb98791475f77882a0b3e028f88a11e9", - "reference": "995a3127fb98791475f77882a0b3e028f88a11e9", + "url": "https://api.github.com/repos/Symplify/TokenRunner/zipball/88747d0c1b8021106d62fe1c8bc15284224d48a7", + "reference": "88747d0c1b8021106d62fe1c8bc15284224d48a7", "shasum": "" }, "require": { @@ -4220,9 +4215,9 @@ "nette/utils": "^2.5", "php": "^7.1", "squizlabs/php_codesniffer": "^3.3", - "symplify/better-phpdoc-parser": "^5.1", - "symplify/package-builder": "^5.1", - "thecodingmachine/safe": "^0.1.3" + "symplify/better-phpdoc-parser": "^5.2", + "symplify/package-builder": "^5.2", + "thecodingmachine/safe": "^0.1.6" }, "require-dev": { "phpunit/phpunit": "^7.3" @@ -4243,20 +4238,20 @@ "MIT" ], "description": "Set of utils for PHP_CodeSniffer and PHP CS Fixer.", - "time": "2018-10-11T10:23:49+00:00" + "time": "2018-10-31T09:23:48+00:00" }, { "name": "thecodingmachine/safe", - "version": "v0.1.5", + "version": "v0.1.6", "source": { "type": "git", "url": "https://github.com/thecodingmachine/safe.git", - "reference": "56fcae888155f6ae0070545c5f80505c511598d5" + "reference": "31d2c13b9e67674614df33ecb3dc0f20f8733749" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/56fcae888155f6ae0070545c5f80505c511598d5", - "reference": "56fcae888155f6ae0070545c5f80505c511598d5", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/31d2c13b9e67674614df33ecb3dc0f20f8733749", + "reference": "31d2c13b9e67674614df33ecb3dc0f20f8733749", "shasum": "" }, "require": { @@ -4373,7 +4368,7 @@ "MIT" ], "description": "PHP core functions that throw exceptions instead of returning FALSE on error", - "time": "2018-09-24T20:24:12+00:00" + "time": "2018-10-30T16:49:26+00:00" }, { "name": "theseer/tokenizer", diff --git a/phpunit.xml b/phpunit.xml index e0a91daa..e0669d33 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,7 +6,6 @@ beStrictAboutTestsThatDoNotTestAnything="true" beStrictAboutOutputDuringTests="true" beStrictAboutChangesToGlobalState="true" - enforceTimeLimit="true" > tests From db95db3e57475df53d602b32c4451438c21e46c3 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 7 Nov 2018 19:40:31 +0100 Subject: [PATCH 293/328] Implement Keep a changelog format --- CHANGELOG.md | 217 +++++++++++++++++++++++++++------------------------ 1 file changed, 114 insertions(+), 103 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c1e0970..7e00fb13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,116 +1,127 @@ -CHANGELOG -========= +# Changelog +All notable changes to this project will be documented in this file. -This changelog references the relevant changes done in PHP-ML library. +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -* Unreleased - * feature [Dataset] changed the default kernel type in SVC to Kernel::RBF (#267) - * feature [Clustering] added KMeans associative clustering (#262) - * feature [Dataset] added removeColumns function to ArrayDataset (#249) - * feature [Dataset] added a SvmDataset class for SVM-Light (or LibSVM) format files (#237) - * feature [Optimizer] removed $initialTheta property and renamed setInitialTheta method to setTheta (#252) - * change [Imputer] Throw exception when trying to transform without train data (#314) - * enhancement Add performance test for LeastSquares (#263) - * enhancement Micro optimization for matrix multiplication (#255) - * enhancement Throw proper exception (#259, #251) - * fix ensure DataTransformer::testSet samples array is not empty (#204) - * fix optimizer initial theta randomization (#239) - * fix travis build on osx (#281) - * fix SVM locale (non-locale aware) (#288) - * typo, tests, code styles and documentation fixes (#265, #261, #254, #253, #251, #250, #248, #245, #243) - * change [MLPClassifier] return labels in output (#315) - * enhancement Update phpstan to 0.10.5 (#320) +## [0.7.0] - 2018-11-07 +### Added +- [Clustering] added KMeans associative clustering (#262) +- [Dataset] added removeColumns function to ArrayDataset (#249) +- [Dataset] added a SvmDataset class for SVM-Light (or LibSVM) format files (#237) +- [Dataset] added Mnist Dataset for MNIST file format (#326) +- [Internal] Add performance test for LeastSquares (#263) -* 0.6.2 (2018-02-22) - * Fix Apriori array keys (#238) +### Changed +- [Internal] implement Keep a Changelog format +- [Classification] changed the default kernel type in SVC to Kernel::RBF (#267) +- [Optimizer] removed $initialTheta property and renamed setInitialTheta method to setTheta (#252) +- [Imputer] Throw exception when trying to transform without train data (#314) +- [Math] Micro optimization for matrix multiplication (#255) +- [Internal] Throw proper exception (#259, #251) +- [MLPClassifier] return labels in output (#315) +- [Internal] Update phpstan to 0.10.5 (#320) -* 0.6.1 (2018-02-18) - * Fix KMeans and EigenvalueDecomposition (#235) +### Fixed +- [SVM] ensure DataTransformer::testSet samples array is not empty (#204) +- [Optimizer] optimizer initial theta randomization (#239) +- [Internal] travis build on osx (#281) +- [SVM] SVM locale (non-locale aware) (#288) +- [Internal] typo, tests, code styles and documentation fixes (#265, #261, #254, #253, #251, #250, #248, #245, #243, #317, #328) +- [Classification] Check if feature exist when predict target in NaiveBayes (#327) -* 0.6.0 (2018-02-16) - * feature [FeatureSelection] implement SelectKBest with scoring functions (#232) - * feature [FeatureSelection] implement VarianceThreshold - simple baseline approach to feature selection. (#228) - * feature [Classification] support probability estimation in SVC (#218) - * feature [NeuralNetwork] configure an Activation Function per hidden layer (#208) - * feature [NeuralNetwork] Ability to update learningRate in MLP (#160) - * feature [Metric] Choose averaging method in classification report (#205) - * enhancement Add phpstan strict rules (#233) - * enhancement Flatten directory structure (#220) - * enhancement Update phpunit/phpunit (#219) - * enhancement Cache dependencies installed with composer on Travis (#215) - * enhancement Add support for coveralls.io (#153) - * enhancement Add phpstan and easy coding standards (#156, #168) - * enhancement Throw exception when libsvm command fails to run (#200, #202) - * enhancement Normalize composer.json and sort packages (#214, #210) - * enhancement Rewrite DBSCAN (#185) - * fix phpunit include tests path (#230) - * fix support of a rule in Apriori (#229) - * fix apriori generates an empty array as a part of the frequent item sets (#224) - * fix backpropagation random error (#157) - * fix logistic regression implementation (#169) - * fix activation functions support (#163) - * fix string representation of integer labels issue in NaiveBayes (#206) - * fix the implementation of conjugate gradient method (#184) - * typo, tests and documentation fixes (#234, #221, #181, #183, #155, #159, #165, #187, #154, #191, #203, #209, #213, #212, #211) +## [0.6.2] - 2018-02-22 +### Fixed +- Fix Apriori array keys (#238) -* 0.5.0 (2017-11-14) - * general [php] Upgrade to PHP 7.1 (#150) - * general [coding standard] fix imports order and drop unused docs typehints - * feature [NeuralNetwork] Add PReLU activation function (#128) - * feature [NeuralNetwork] Add ThresholdedReLU activation function (#129) - * feature [Dataset] Support CSV with long lines (#119) - * feature [NeuralNetwork] Neural networks partial training and persistency (#91) - * feature Add french stopwords (#92) - * feature New methods: setBinPath, setVarPath in SupportVectorMachine (#73) - * feature Linear Discrimant Analysis (LDA) (#82) - * feature Linear algebra operations, Dimensionality reduction and some other minor changes (#81) - * feature Partial training base (#78) - * feature Add delimiter option for CsvDataset (#66) - * feature LogisticRegression classifier & Optimization methods (#63) - * feature Additional training for SVR (#59) - * optimization Comparison - replace eval (#130) - * optimization Use C-style casts (#124) - * optimization Speed up DataTransformer (#122) - * bug DBSCAN fix for associative keys and array_merge performance optimization (#139) - * bug Ensure user-provided SupportVectorMachine paths are valid (#126) - * bug [DecisionTree] Fix string cast #120 (#121) - * bug fix invalid typehint for subs method (#110) - * bug Fix samples transformation in Pipeline training (#94) - * bug Fix division by 0 error during normalization (#83) - * bug Fix wrong docs references (#79) +## [0.6.1] - 2018-02-18 +### Fixed +- Fix KMeans and EigenvalueDecomposition (#235) -* 0.4.0 (2017-02-23) - * feature [Classification] - Ensemble Classifiers : Bagging and RandomForest by Mustafa Karabulut - * feature [Classification] - RandomForest::getFeatureImportances() method by Mustafa Karabulut - * feature [Classification] - Linear classifiers: Perceptron, Adaline, DecisionStump by Mustafa Karabulut - * feature [Classification] - AdaBoost algorithm by Mustafa Karabulut - * bug [Math] - Check if matrix is singular doing inverse by Povilas Susinskas - * optimization - Euclidean optimization by Mustafa Karabulut +## [0.6.0] - 2018-02-16 +- feature [FeatureSelection] implement SelectKBest with scoring functions (#232) +- feature [FeatureSelection] implement VarianceThreshold - simple baseline approach to feature selection. (#228) +- feature [Classification] support probability estimation in SVC (#218) +- feature [NeuralNetwork] configure an Activation Function per hidden layer (#208) +- feature [NeuralNetwork] Ability to update learningRate in MLP (#160) +- feature [Metric] Choose averaging method in classification report (#205) +- enhancement Add phpstan strict rules (#233) +- enhancement Flatten directory structure (#220) +- enhancement Update phpunit/phpunit (#219) +- enhancement Cache dependencies installed with composer on Travis (#215) +- enhancement Add support for coveralls.io (#153) +- enhancement Add phpstan and easy coding standards (#156, #168) +- enhancement Throw exception when libsvm command fails to run (#200, #202) +- enhancement Normalize composer.json and sort packages (#214, #210) +- enhancement Rewrite DBSCAN (#185) +- fix phpunit include tests path (#230) +- fix support of a rule in Apriori (#229) +- fix apriori generates an empty array as a part of the frequent item sets (#224) +- fix backpropagation random error (#157) +- fix logistic regression implementation (#169) +- fix activation functions support (#163) +- fix string representation of integer labels issue in NaiveBayes (#206) +- fix the implementation of conjugate gradient method (#184) +- typo, tests and documentation fixes (#234, #221, #181, #183, #155, #159, #165, #187, #154, #191, #203, #209, #213, #212, #211) -* 0.3.0 (2017-02-04) - * feature [Persistency] - ModelManager - save and restore trained models by David Monllaó - * feature [Classification] - DecisionTree implementation by Mustafa Karabulut - * feature [Clustering] - Fuzzy C Means implementation by Mustafa Karabulut - * other small fixes and code styles refactors +## [0.5.0] - 2017-11-14 +- general [php] Upgrade to PHP 7.1 (#150) +- general [coding standard] fix imports order and drop unused docs typehints +- feature [NeuralNetwork] Add PReLU activation function (#128) +- feature [NeuralNetwork] Add ThresholdedReLU activation function (#129) +- feature [Dataset] Support CSV with long lines (#119) +- feature [NeuralNetwork] Neural networks partial training and persistency (#91) +- feature Add french stopwords (#92) +- feature New methods: setBinPath, setVarPath in SupportVectorMachine (#73) +- feature Linear Discrimant Analysis (LDA) (#82) +- feature Linear algebra operations, Dimensionality reduction and some other minor changes (#81) +- feature Partial training base (#78) +- feature Add delimiter option for CsvDataset (#66) +- feature LogisticRegression classifier & Optimization methods (#63) +- feature Additional training for SVR (#59) +- optimization Comparison - replace eval (#130) +- optimization Use C-style casts (#124) +- optimization Speed up DataTransformer (#122) +- bug DBSCAN fix for associative keys and array_merge performance optimization (#139) +- bug Ensure user-provided SupportVectorMachine paths are valid (#126) +- bug [DecisionTree] Fix string cast #120 (#121) +- bug fix invalid typehint for subs method (#110) +- bug Fix samples transformation in Pipeline training (#94) +- bug Fix division by 0 error during normalization (#83) +- bug Fix wrong docs references (#79) -* 0.2.1 (2016-11-20) - * feature [Association] - Apriori algorithm implementation - * bug [Metric] - division by zero +## [0.4.0] - 2017-02-23 +- feature [Classification] - Ensemble Classifiers : Bagging and RandomForest by Mustafa Karabulut +- feature [Classification] - RandomForest::getFeatureImportances() method by Mustafa Karabulut +- feature [Classification] - Linear classifiers: Perceptron, Adaline, DecisionStump by Mustafa Karabulut +- feature [Classification] - AdaBoost algorithm by Mustafa Karabulut +- bug [Math] - Check if matrix is singular doing inverse by Povilas Susinskas +- optimization - Euclidean optimization by Mustafa Karabulut -* 0.2.0 (2016-08-14) - * feature [NeuralNetwork] - MultilayerPerceptron and Backpropagation training +## [0.3.0] - 2017-02-04 +- feature [Persistency] - ModelManager - save and restore trained models by David Monllaó +- feature [Classification] - DecisionTree implementation by Mustafa Karabulut +- feature [Clustering] - Fuzzy C Means implementation by Mustafa Karabulut +- other small fixes and code styles refactors -* 0.1.2 (2016-07-24) - * feature [Dataset] - FilesDataset - load dataset from files (folder names as targets) - * feature [Metric] - ClassificationReport - report about trained classifier - * bug [Feature Extraction] - fix problem with token count vectorizer array order - * tests [General] - add more tests for specific conditions +## [0.2.1] - 2016-11-20 +- feature [Association] - Apriori algorithm implementation +- bug [Metric] - division by zero -* 0.1.1 (2016-07-12) - * feature [Cross Validation] Stratified Random Split - equal distribution for targets in split - * feature [General] Documentation - add missing pages (Pipeline, ConfusionMatrix and TfIdfTransformer) and fix links +## [0.2.0] - 2016-08-14 +- feature [NeuralNetwork] - MultilayerPerceptron and Backpropagation training -* 0.1.0 (2016-07-08) - * first develop release - * base tools for Machine Learning: Algorithms, Cross Validation, Preprocessing, Feature Extraction - * bug [General] #7 - PHP-ML doesn't work on Mac +## [0.1.2] - 2016-07-24 +- feature [Dataset] - FilesDataset - load dataset from files (folder names as targets) +- feature [Metric] - ClassificationReport - report about trained classifier +- bug [Feature Extraction] - fix problem with token count vectorizer array order +- tests [General] - add more tests for specific conditions + +## [0.1.1] - 2016-07-12 +- feature [Cross Validation] Stratified Random Split - equal distribution for targets in split +- feature [General] Documentation - add missing pages (Pipeline, ConfusionMatrix and TfIdfTransformer) and fix links + +## [0.1.0] - 2016-07-08 +- first develop release +- base tools for Machine Learning: Algorithms, Cross Validation, Preprocessing, Feature Extraction +- bug [General] #7 - PHP-ML doesn't work on Mac From 1934d8af814855b0ec9d57d0c9a19ea913c4f690 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 12 Dec 2018 21:56:44 +0100 Subject: [PATCH 294/328] Update dependencies and fix code styles (#334) --- composer.lock | 500 +++++++++++------- src/Classification/Ensemble/Bagging.php | 2 +- src/Classification/Ensemble/RandomForest.php | 2 +- src/Clustering/FuzzyCMeans.php | 4 +- src/Clustering/KMeans/Space.php | 4 +- src/CrossValidation/StratifiedRandomSplit.php | 2 +- src/Math/Statistic/ANOVA.php | 4 +- src/Math/Statistic/Correlation.php | 4 +- src/Math/Statistic/StandardDeviation.php | 4 +- src/Pipeline.php | 6 +- 10 files changed, 338 insertions(+), 194 deletions(-) diff --git a/composer.lock b/composer.lock index e0a85c6f..e5eee3ca 100644 --- a/composer.lock +++ b/composer.lock @@ -1,7 +1,7 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], "content-hash": "9ec1ca6b843d05e0870bd777026d7a8b", @@ -126,16 +126,16 @@ }, { "name": "composer/xdebug-handler", - "version": "1.3.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "b8e9745fb9b06ea6664d8872c4505fb16df4611c" + "reference": "dc523135366eb68f22268d069ea7749486458562" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/b8e9745fb9b06ea6664d8872c4505fb16df4611c", - "reference": "b8e9745fb9b06ea6664d8872c4505fb16df4611c", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/dc523135366eb68f22268d069ea7749486458562", + "reference": "dc523135366eb68f22268d069ea7749486458562", "shasum": "" }, "require": { @@ -166,7 +166,7 @@ "Xdebug", "performance" ], - "time": "2018-08-31T19:07:57+00:00" + "time": "2018-11-29T10:59:02+00:00" }, { "name": "doctrine/annotations", @@ -488,16 +488,16 @@ }, { "name": "lstrojny/functional-php", - "version": "1.8.0", + "version": "1.9.0", "source": { "type": "git", "url": "https://github.com/lstrojny/functional-php.git", - "reference": "7d677bbc1dbf8338946cd3b31f0d5c2beb2b5a26" + "reference": "f5b3b4424dcddb406d3dcfcae0d1bc0060099a78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lstrojny/functional-php/zipball/7d677bbc1dbf8338946cd3b31f0d5c2beb2b5a26", - "reference": "7d677bbc1dbf8338946cd3b31f0d5c2beb2b5a26", + "url": "https://api.github.com/repos/lstrojny/functional-php/zipball/f5b3b4424dcddb406d3dcfcae0d1bc0060099a78", + "reference": "f5b3b4424dcddb406d3dcfcae0d1bc0060099a78", "shasum": "" }, "require": { @@ -623,7 +623,7 @@ "keywords": [ "functional" ], - "time": "2018-03-19T16:14:14+00:00" + "time": "2018-12-03T16:47:05+00:00" }, { "name": "myclabs/deep-copy", @@ -1558,16 +1558,16 @@ }, { "name": "phpstan/phpstan-shim", - "version": "0.10.5", + "version": "0.10.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-shim.git", - "reference": "a274185548d140a7f48cc1eed5b94f3a9068c674" + "reference": "c729ee281588bdb73ae50051503a6785aed46721" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-shim/zipball/a274185548d140a7f48cc1eed5b94f3a9068c674", - "reference": "a274185548d140a7f48cc1eed5b94f3a9068c674", + "url": "https://api.github.com/repos/phpstan/phpstan-shim/zipball/c729ee281588bdb73ae50051503a6785aed46721", + "reference": "c729ee281588bdb73ae50051503a6785aed46721", "shasum": "" }, "require": { @@ -1598,7 +1598,7 @@ "MIT" ], "description": "PHPStan Phar distribution", - "time": "2018-10-20T17:45:03+00:00" + "time": "2018-12-04T07:53:38+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -1900,16 +1900,16 @@ }, { "name": "phpunit/phpunit", - "version": "7.4.3", + "version": "7.5.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c151651fb6ed264038d486ea262e243af72e5e64" + "reference": "520723129e2b3fc1dc4c0953e43c9d40e1ecb352" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c151651fb6ed264038d486ea262e243af72e5e64", - "reference": "c151651fb6ed264038d486ea262e243af72e5e64", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/520723129e2b3fc1dc4c0953e43c9d40e1ecb352", + "reference": "520723129e2b3fc1dc4c0953e43c9d40e1ecb352", "shasum": "" }, "require": { @@ -1930,7 +1930,7 @@ "phpunit/php-timer": "^2.0", "sebastian/comparator": "^3.0", "sebastian/diff": "^3.0", - "sebastian/environment": "^3.1 || ^4.0", + "sebastian/environment": "^4.0", "sebastian/exporter": "^3.1", "sebastian/global-state": "^2.0", "sebastian/object-enumerator": "^3.0.3", @@ -1954,7 +1954,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.4-dev" + "dev-master": "7.5-dev" } }, "autoload": { @@ -1980,7 +1980,7 @@ "testing", "xunit" ], - "time": "2018-10-23T05:57:41+00:00" + "time": "2018-12-07T07:08:12+00:00" }, { "name": "psr/cache", @@ -2079,16 +2079,16 @@ }, { "name": "psr/log", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", "shasum": "" }, "require": { @@ -2122,7 +2122,7 @@ "psr", "psr-3" ], - "time": "2016-10-10T12:19:37+00:00" + "time": "2018-11-20T15:27:04+00:00" }, { "name": "psr/simple-cache", @@ -2339,28 +2339,28 @@ }, { "name": "sebastian/environment", - "version": "3.1.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" + "reference": "febd209a219cea7b56ad799b30ebbea34b71eb8f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", - "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/febd209a219cea7b56ad799b30ebbea34b71eb8f", + "reference": "febd209a219cea7b56ad799b30ebbea34b71eb8f", "shasum": "" }, "require": { - "php": "^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^6.1" + "phpunit/phpunit": "^7.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -2385,7 +2385,7 @@ "environment", "hhvm" ], - "time": "2017-07-01T08:51:00+00:00" + "time": "2018-11-25T09:31:21+00:00" }, { "name": "sebastian/exporter", @@ -2923,41 +2923,49 @@ }, { "name": "symfony/cache", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "05ce0ddc8bc1ffe592105398fc2c725cb3080a38" + "reference": "5c4b50d6ba4f1c8955c3454444c1e3cfddaaad41" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/05ce0ddc8bc1ffe592105398fc2c725cb3080a38", - "reference": "05ce0ddc8bc1ffe592105398fc2c725cb3080a38", + "url": "https://api.github.com/repos/symfony/cache/zipball/5c4b50d6ba4f1c8955c3454444c1e3cfddaaad41", + "reference": "5c4b50d6ba4f1c8955c3454444c1e3cfddaaad41", "shasum": "" }, "require": { "php": "^7.1.3", "psr/cache": "~1.0", "psr/log": "~1.0", - "psr/simple-cache": "^1.0" + "psr/simple-cache": "^1.0", + "symfony/contracts": "^1.0", + "symfony/var-exporter": "^4.2" }, "conflict": { + "doctrine/dbal": "<2.5", + "symfony/dependency-injection": "<3.4", "symfony/var-dumper": "<3.4" }, "provide": { "psr/cache-implementation": "1.0", - "psr/simple-cache-implementation": "1.0" + "psr/simple-cache-implementation": "1.0", + "symfony/cache-contracts-implementation": "1.0" }, "require-dev": { "cache/integration-tests": "dev-master", "doctrine/cache": "~1.6", - "doctrine/dbal": "~2.4", - "predis/predis": "~1.0" + "doctrine/dbal": "~2.5", + "predis/predis": "~1.1", + "symfony/config": "~4.2", + "symfony/dependency-injection": "~3.4|~4.1", + "symfony/var-dumper": "^4.1.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -2988,20 +2996,20 @@ "caching", "psr6" ], - "time": "2018-09-30T03:38:13+00:00" + "time": "2018-12-06T11:00:08+00:00" }, { "name": "symfony/config", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "991fec8bbe77367fc8b48ecbaa8a4bd6e905a238" + "reference": "005d9a083d03f588677d15391a716b1ac9b887c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/991fec8bbe77367fc8b48ecbaa8a4bd6e905a238", - "reference": "991fec8bbe77367fc8b48ecbaa8a4bd6e905a238", + "url": "https://api.github.com/repos/symfony/config/zipball/005d9a083d03f588677d15391a716b1ac9b887c0", + "reference": "005d9a083d03f588677d15391a716b1ac9b887c0", "shasum": "" }, "require": { @@ -3024,7 +3032,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -3051,24 +3059,25 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2018-10-31T09:09:42+00:00" + "time": "2018-11-30T22:21:14+00:00" }, { "name": "symfony/console", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "432122af37d8cd52fba1b294b11976e0d20df595" + "reference": "4dff24e5d01e713818805c1862d2e3f901ee7dd0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/432122af37d8cd52fba1b294b11976e0d20df595", - "reference": "432122af37d8cd52fba1b294b11976e0d20df595", + "url": "https://api.github.com/repos/symfony/console/zipball/4dff24e5d01e713818805c1862d2e3f901ee7dd0", + "reference": "4dff24e5d01e713818805c1862d2e3f901ee7dd0", "shasum": "" }, "require": { "php": "^7.1.3", + "symfony/contracts": "^1.0", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { @@ -3092,7 +3101,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -3119,20 +3128,88 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-10-31T09:30:44+00:00" + "time": "2018-11-27T07:40:44+00:00" + }, + { + "name": "symfony/contracts", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/contracts.git", + "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/contracts/zipball/1aa7ab2429c3d594dd70689604b5cf7421254cdf", + "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "require-dev": { + "psr/cache": "^1.0", + "psr/container": "^1.0" + }, + "suggest": { + "psr/cache": "When using the Cache contracts", + "psr/container": "When using the Service contracts", + "symfony/cache-contracts-implementation": "", + "symfony/service-contracts-implementation": "", + "symfony/translation-contracts-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\": "" + }, + "exclude-from-classmap": [ + "**/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A set of abstractions extracted out of the Symfony components", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2018-12-05T08:06:11+00:00" }, { "name": "symfony/debug", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "19090917b848a799cbae4800abf740fe4eb71c1d" + "reference": "e0a2b92ee0b5b934f973d90c2f58e18af109d276" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/19090917b848a799cbae4800abf740fe4eb71c1d", - "reference": "19090917b848a799cbae4800abf740fe4eb71c1d", + "url": "https://api.github.com/repos/symfony/debug/zipball/e0a2b92ee0b5b934f973d90c2f58e18af109d276", + "reference": "e0a2b92ee0b5b934f973d90c2f58e18af109d276", "shasum": "" }, "require": { @@ -3148,7 +3225,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -3175,37 +3252,39 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2018-10-31T09:09:42+00:00" + "time": "2018-11-28T18:24:18+00:00" }, { "name": "symfony/dependency-injection", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "e72ee2c23d952e4c368ee98610fa22b79b89b483" + "reference": "e4adc57a48d3fa7f394edfffa9e954086d7740e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e72ee2c23d952e4c368ee98610fa22b79b89b483", - "reference": "e72ee2c23d952e4c368ee98610fa22b79b89b483", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e4adc57a48d3fa7f394edfffa9e954086d7740e5", + "reference": "e4adc57a48d3fa7f394edfffa9e954086d7740e5", "shasum": "" }, "require": { "php": "^7.1.3", - "psr/container": "^1.0" + "psr/container": "^1.0", + "symfony/contracts": "^1.0" }, "conflict": { - "symfony/config": "<4.1.1", + "symfony/config": "<4.2", "symfony/finder": "<3.4", "symfony/proxy-manager-bridge": "<3.4", "symfony/yaml": "<3.4" }, "provide": { - "psr/container-implementation": "1.0" + "psr/container-implementation": "1.0", + "symfony/service-contracts-implementation": "1.0" }, "require-dev": { - "symfony/config": "~4.1", + "symfony/config": "~4.2", "symfony/expression-language": "~3.4|~4.0", "symfony/yaml": "~3.4|~4.0" }, @@ -3219,7 +3298,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -3246,24 +3325,25 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2018-10-31T10:54:16+00:00" + "time": "2018-12-02T15:59:36+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "552541dad078c85d9414b09c041ede488b456cd5" + "reference": "921f49c3158a276d27c0d770a5a347a3b718b328" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/552541dad078c85d9414b09c041ede488b456cd5", - "reference": "552541dad078c85d9414b09c041ede488b456cd5", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/921f49c3158a276d27c0d770a5a347a3b718b328", + "reference": "921f49c3158a276d27c0d770a5a347a3b718b328", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.1.3", + "symfony/contracts": "^1.0" }, "conflict": { "symfony/dependency-injection": "<3.4" @@ -3282,7 +3362,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -3309,20 +3389,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2018-10-10T13:52:42+00:00" + "time": "2018-12-01T08:52:38+00:00" }, { "name": "symfony/filesystem", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "fd7bd6535beb1f0a0a9e3ee960666d0598546981" + "reference": "2f4c8b999b3b7cadb2a69390b01af70886753710" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/fd7bd6535beb1f0a0a9e3ee960666d0598546981", - "reference": "fd7bd6535beb1f0a0a9e3ee960666d0598546981", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/2f4c8b999b3b7cadb2a69390b01af70886753710", + "reference": "2f4c8b999b3b7cadb2a69390b01af70886753710", "shasum": "" }, "require": { @@ -3332,7 +3412,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -3359,20 +3439,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-10-30T13:18:25+00:00" + "time": "2018-11-11T19:52:12+00:00" }, { "name": "symfony/finder", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "1f17195b44543017a9c9b2d437c670627e96ad06" + "reference": "e53d477d7b5c4982d0e1bfd2298dbee63d01441d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/1f17195b44543017a9c9b2d437c670627e96ad06", - "reference": "1f17195b44543017a9c9b2d437c670627e96ad06", + "url": "https://api.github.com/repos/symfony/finder/zipball/e53d477d7b5c4982d0e1bfd2298dbee63d01441d", + "reference": "e53d477d7b5c4982d0e1bfd2298dbee63d01441d", "shasum": "" }, "require": { @@ -3381,7 +3461,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -3408,20 +3488,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-10-03T08:47:56+00:00" + "time": "2018-11-11T19:52:12+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "82d494c1492b0dd24bbc5c2d963fb02eb44491af" + "reference": "1b31f3017fadd8cb05cf2c8aebdbf3b12a943851" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/82d494c1492b0dd24bbc5c2d963fb02eb44491af", - "reference": "82d494c1492b0dd24bbc5c2d963fb02eb44491af", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/1b31f3017fadd8cb05cf2c8aebdbf3b12a943851", + "reference": "1b31f3017fadd8cb05cf2c8aebdbf3b12a943851", "shasum": "" }, "require": { @@ -3435,7 +3515,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -3462,25 +3542,26 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2018-10-31T09:09:42+00:00" + "time": "2018-11-26T10:55:26+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "958be64ab13b65172ad646ef5ae20364c2305fae" + "reference": "b39ceffc0388232c309cbde3a7c3685f2ec0a624" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/958be64ab13b65172ad646ef5ae20364c2305fae", - "reference": "958be64ab13b65172ad646ef5ae20364c2305fae", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b39ceffc0388232c309cbde3a7c3685f2ec0a624", + "reference": "b39ceffc0388232c309cbde3a7c3685f2ec0a624", "shasum": "" }, "require": { "php": "^7.1.3", "psr/log": "~1.0", + "symfony/contracts": "^1.0.2", "symfony/debug": "~3.4|~4.0", "symfony/event-dispatcher": "~4.1", "symfony/http-foundation": "^4.1.1", @@ -3488,7 +3569,8 @@ }, "conflict": { "symfony/config": "<3.4", - "symfony/dependency-injection": "<4.1", + "symfony/dependency-injection": "<4.2", + "symfony/translation": "<4.2", "symfony/var-dumper": "<4.1.1", "twig/twig": "<1.34|<2.4,>=2" }, @@ -3501,7 +3583,7 @@ "symfony/config": "~3.4|~4.0", "symfony/console": "~3.4|~4.0", "symfony/css-selector": "~3.4|~4.0", - "symfony/dependency-injection": "^4.1", + "symfony/dependency-injection": "^4.2", "symfony/dom-crawler": "~3.4|~4.0", "symfony/expression-language": "~3.4|~4.0", "symfony/finder": "~3.4|~4.0", @@ -3509,7 +3591,7 @@ "symfony/routing": "~3.4|~4.0", "symfony/stopwatch": "~3.4|~4.0", "symfony/templating": "~3.4|~4.0", - "symfony/translation": "~3.4|~4.0", + "symfony/translation": "~4.2", "symfony/var-dumper": "^4.1.1" }, "suggest": { @@ -3522,7 +3604,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -3549,20 +3631,20 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2018-11-03T11:11:23+00:00" + "time": "2018-12-06T17:39:52+00:00" }, { "name": "symfony/options-resolver", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "40f0e40d37c1c8a762334618dea597d64bbb75ff" + "reference": "a9c38e8a3da2c03b3e71fdffa6efb0bda51390ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/40f0e40d37c1c8a762334618dea597d64bbb75ff", - "reference": "40f0e40d37c1c8a762334618dea597d64bbb75ff", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/a9c38e8a3da2c03b3e71fdffa6efb0bda51390ba", + "reference": "a9c38e8a3da2c03b3e71fdffa6efb0bda51390ba", "shasum": "" }, "require": { @@ -3571,7 +3653,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -3603,7 +3685,7 @@ "configuration", "options" ], - "time": "2018-09-18T12:45:12+00:00" + "time": "2018-11-11T19:52:12+00:00" }, { "name": "symfony/polyfill-ctype", @@ -3838,16 +3920,16 @@ }, { "name": "symfony/process", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "3e83acef94d979b1de946599ef86b3a352abcdc9" + "reference": "2b341009ccec76837a7f46f59641b431e4d4c2b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/3e83acef94d979b1de946599ef86b3a352abcdc9", - "reference": "3e83acef94d979b1de946599ef86b3a352abcdc9", + "url": "https://api.github.com/repos/symfony/process/zipball/2b341009ccec76837a7f46f59641b431e4d4c2b0", + "reference": "2b341009ccec76837a7f46f59641b431e4d4c2b0", "shasum": "" }, "require": { @@ -3856,7 +3938,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -3883,29 +3965,30 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-10-14T20:48:13+00:00" + "time": "2018-11-20T16:22:05+00:00" }, { "name": "symfony/stopwatch", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "5bfc064125b73ff81229e19381ce1c34d3416f4b" + "reference": "ec076716412274e51f8a7ea675d9515e5c311123" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5bfc064125b73ff81229e19381ce1c34d3416f4b", - "reference": "5bfc064125b73ff81229e19381ce1c34d3416f4b", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/ec076716412274e51f8a7ea675d9515e5c311123", + "reference": "ec076716412274e51f8a7ea675d9515e5c311123", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.1.3", + "symfony/contracts": "^1.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -3932,20 +4015,80 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2018-10-02T12:40:59+00:00" + "time": "2018-11-11T19:52:12+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v4.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "a39222e357362424b61dcde50e2f7b5a7d3306db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/a39222e357362424b61dcde50e2f7b5a7d3306db", + "reference": "a39222e357362424b61dcde50e2f7b5a7d3306db", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "require-dev": { + "symfony/var-dumper": "^4.1.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A blend of var_export() + serialize() to turn any serializable data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "serialize" + ], + "time": "2018-12-03T22:40:09+00:00" }, { "name": "symfony/yaml", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "367e689b2fdc19965be435337b50bc8adf2746c9" + "reference": "c41175c801e3edfda90f32e292619d10c27103d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/367e689b2fdc19965be435337b50bc8adf2746c9", - "reference": "367e689b2fdc19965be435337b50bc8adf2746c9", + "url": "https://api.github.com/repos/symfony/yaml/zipball/c41175c801e3edfda90f32e292619d10c27103d7", + "reference": "c41175c801e3edfda90f32e292619d10c27103d7", "shasum": "" }, "require": { @@ -3964,7 +4107,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -3991,27 +4134,27 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2018-10-02T16:36:10+00:00" + "time": "2018-11-11T19:52:12+00:00" }, { "name": "symplify/better-phpdoc-parser", - "version": "v5.2.0", + "version": "v5.2.15", "source": { "type": "git", "url": "https://github.com/Symplify/BetterPhpDocParser.git", - "reference": "9b3bf0de89bff4818b2178ff62bb63de19ba6119" + "reference": "cb8f3b2c2f5d9f514b9dde90623c897ccc6a3e8f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/BetterPhpDocParser/zipball/9b3bf0de89bff4818b2178ff62bb63de19ba6119", - "reference": "9b3bf0de89bff4818b2178ff62bb63de19ba6119", + "url": "https://api.github.com/repos/Symplify/BetterPhpDocParser/zipball/cb8f3b2c2f5d9f514b9dde90623c897ccc6a3e8f", + "reference": "cb8f3b2c2f5d9f514b9dde90623c897ccc6a3e8f", "shasum": "" }, "require": { "nette/utils": "^2.5", "php": "^7.1", "phpstan/phpdoc-parser": "^0.3", - "symplify/package-builder": "^5.2", + "symplify/package-builder": "^5.2.15", "thecodingmachine/safe": "^0.1.6" }, "require-dev": { @@ -4020,7 +4163,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.2-dev" + "dev-master": "5.3-dev" } }, "autoload": { @@ -4033,20 +4176,20 @@ "MIT" ], "description": "Slim wrapper around phpstan/phpdoc-parser with format preserving printer", - "time": "2018-10-31T09:23:48+00:00" + "time": "2018-12-07T13:16:16+00:00" }, { "name": "symplify/coding-standard", - "version": "v5.2.0", + "version": "v5.2.15", "source": { "type": "git", "url": "https://github.com/Symplify/CodingStandard.git", - "reference": "d057df9d605d664a37cdc00e7dbe5afa90f0dd17" + "reference": "028c532b3822da4abff31b1554e020c88df86383" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/CodingStandard/zipball/d057df9d605d664a37cdc00e7dbe5afa90f0dd17", - "reference": "d057df9d605d664a37cdc00e7dbe5afa90f0dd17", + "url": "https://api.github.com/repos/Symplify/CodingStandard/zipball/028c532b3822da4abff31b1554e020c88df86383", + "reference": "028c532b3822da4abff31b1554e020c88df86383", "shasum": "" }, "require": { @@ -4056,19 +4199,19 @@ "php": "^7.1", "slam/php-cs-fixer-extensions": "^1.17", "squizlabs/php_codesniffer": "^3.3", - "symplify/token-runner": "^5.2", + "symplify/token-runner": "^5.2.15", "thecodingmachine/safe": "^0.1.6" }, "require-dev": { "nette/application": "^2.4", "phpunit/phpunit": "^7.3", - "symplify/easy-coding-standard-tester": "^5.2", - "symplify/package-builder": "^5.2" + "symplify/easy-coding-standard-tester": "^5.2.15", + "symplify/package-builder": "^5.2.15" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.2-dev" + "dev-master": "5.3-dev" } }, "autoload": { @@ -4081,23 +4224,24 @@ "MIT" ], "description": "Set of Symplify rules for PHP_CodeSniffer and PHP CS Fixer.", - "time": "2018-10-31T15:24:17+00:00" + "time": "2018-12-07T13:16:16+00:00" }, { "name": "symplify/easy-coding-standard", - "version": "v5.2.0", + "version": "v5.2.15", "source": { "type": "git", "url": "https://github.com/Symplify/EasyCodingStandard.git", - "reference": "fc3e2f6749835628a125ee0d5d8db39294718ce3" + "reference": "055982678422769731a7b0572a9fc10aae9609cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/EasyCodingStandard/zipball/fc3e2f6749835628a125ee0d5d8db39294718ce3", - "reference": "fc3e2f6749835628a125ee0d5d8db39294718ce3", + "url": "https://api.github.com/repos/Symplify/EasyCodingStandard/zipball/055982678422769731a7b0572a9fc10aae9609cc", + "reference": "055982678422769731a7b0572a9fc10aae9609cc", "shasum": "" }, "require": { + "composer/xdebug-handler": "^1.3", "friendsofphp/php-cs-fixer": "^2.13", "jean85/pretty-package-versions": "^1.2", "nette/robot-loader": "^3.1.0", @@ -4113,14 +4257,14 @@ "symfony/finder": "^3.4|^4.1", "symfony/http-kernel": "^3.4|^4.1", "symfony/yaml": "^3.4|^4.1", - "symplify/coding-standard": "^5.2", - "symplify/package-builder": "^5.2", - "symplify/token-runner": "^5.2", + "symplify/coding-standard": "^5.2.15", + "symplify/package-builder": "^5.2.15", + "symplify/token-runner": "^5.2.15", "thecodingmachine/safe": "^0.1.6" }, "require-dev": { "phpunit/phpunit": "^7.3", - "symplify/easy-coding-standard-tester": "^5.2" + "symplify/easy-coding-standard-tester": "^5.2.15" }, "bin": [ "bin/ecs" @@ -4128,7 +4272,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.2-dev" + "dev-master": "5.3-dev" } }, "autoload": { @@ -4145,20 +4289,20 @@ "MIT" ], "description": "Use Coding Standard with 0-knowledge of PHP-CS-Fixer and PHP_CodeSniffer.", - "time": "2018-11-03T10:43:05+00:00" + "time": "2018-12-07T13:16:16+00:00" }, { "name": "symplify/package-builder", - "version": "v5.2.0", + "version": "v5.2.15", "source": { "type": "git", "url": "https://github.com/Symplify/PackageBuilder.git", - "reference": "0b88fb5038d015e00179286d55445695372245aa" + "reference": "978e9ac03eb4640282be4b56677e0dd8e665893d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/PackageBuilder/zipball/0b88fb5038d015e00179286d55445695372245aa", - "reference": "0b88fb5038d015e00179286d55445695372245aa", + "url": "https://api.github.com/repos/Symplify/PackageBuilder/zipball/978e9ac03eb4640282be4b56677e0dd8e665893d", + "reference": "978e9ac03eb4640282be4b56677e0dd8e665893d", "shasum": "" }, "require": { @@ -4180,7 +4324,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.2-dev" + "dev-master": "5.3-dev" } }, "autoload": { @@ -4193,20 +4337,20 @@ "MIT" ], "description": "Dependency Injection, Console and Kernel toolkit for Symplify packages.", - "time": "2018-11-03T13:34:54+00:00" + "time": "2018-12-02T21:24:21+00:00" }, { "name": "symplify/token-runner", - "version": "v5.2.0", + "version": "v5.2.15", "source": { "type": "git", "url": "https://github.com/Symplify/TokenRunner.git", - "reference": "88747d0c1b8021106d62fe1c8bc15284224d48a7" + "reference": "73878adbf28a7ce974f896af4f3890a295c97d17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/TokenRunner/zipball/88747d0c1b8021106d62fe1c8bc15284224d48a7", - "reference": "88747d0c1b8021106d62fe1c8bc15284224d48a7", + "url": "https://api.github.com/repos/Symplify/TokenRunner/zipball/73878adbf28a7ce974f896af4f3890a295c97d17", + "reference": "73878adbf28a7ce974f896af4f3890a295c97d17", "shasum": "" }, "require": { @@ -4215,8 +4359,8 @@ "nette/utils": "^2.5", "php": "^7.1", "squizlabs/php_codesniffer": "^3.3", - "symplify/better-phpdoc-parser": "^5.2", - "symplify/package-builder": "^5.2", + "symplify/better-phpdoc-parser": "^5.2.15", + "symplify/package-builder": "^5.2.15", "thecodingmachine/safe": "^0.1.6" }, "require-dev": { @@ -4225,7 +4369,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.2-dev" + "dev-master": "5.3-dev" } }, "autoload": { @@ -4238,20 +4382,20 @@ "MIT" ], "description": "Set of utils for PHP_CodeSniffer and PHP CS Fixer.", - "time": "2018-10-31T09:23:48+00:00" + "time": "2018-12-07T13:16:16+00:00" }, { "name": "thecodingmachine/safe", - "version": "v0.1.6", + "version": "v0.1.8", "source": { "type": "git", "url": "https://github.com/thecodingmachine/safe.git", - "reference": "31d2c13b9e67674614df33ecb3dc0f20f8733749" + "reference": "4547d4684086d463b00cbd1a7763395280355e7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/31d2c13b9e67674614df33ecb3dc0f20f8733749", - "reference": "31d2c13b9e67674614df33ecb3dc0f20f8733749", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/4547d4684086d463b00cbd1a7763395280355e7d", + "reference": "4547d4684086d463b00cbd1a7763395280355e7d", "shasum": "" }, "require": { @@ -4368,7 +4512,7 @@ "MIT" ], "description": "PHP core functions that throw exceptions instead of returning FALSE on error", - "time": "2018-10-30T16:49:26+00:00" + "time": "2018-11-13T09:01:03+00:00" }, { "name": "theseer/tokenizer", diff --git a/src/Classification/Ensemble/Bagging.php b/src/Classification/Ensemble/Bagging.php index 26cc7a6d..2c9010d0 100644 --- a/src/Classification/Ensemble/Bagging.php +++ b/src/Classification/Ensemble/Bagging.php @@ -157,7 +157,7 @@ protected function predictSample(array $sample) { $predictions = []; foreach ($this->classifiers as $classifier) { - /* @var $classifier Classifier */ + /** @var Classifier $classifier */ $predictions[] = $classifier->predict($sample); } diff --git a/src/Classification/Ensemble/RandomForest.php b/src/Classification/Ensemble/RandomForest.php index c0f0dd87..ac1304c6 100644 --- a/src/Classification/Ensemble/RandomForest.php +++ b/src/Classification/Ensemble/RandomForest.php @@ -86,7 +86,7 @@ public function getFeatureImportances(): array // Traverse each tree and sum importance of the columns $sum = []; foreach ($this->classifiers as $tree) { - /* @var $tree DecisionTree */ + /** @var DecisionTree $tree */ $importances = $tree->getFeatureImportances(); foreach ($importances as $column => $importance) { diff --git a/src/Clustering/FuzzyCMeans.php b/src/Clustering/FuzzyCMeans.php index db42fe94..3637ef65 100644 --- a/src/Clustering/FuzzyCMeans.php +++ b/src/Clustering/FuzzyCMeans.php @@ -18,7 +18,7 @@ class FuzzyCMeans implements Clusterer private $clustersNumber; /** - * @var array|Cluster[] + * @var Cluster[] */ private $clusters = []; @@ -28,7 +28,7 @@ class FuzzyCMeans implements Clusterer private $space; /** - * @var array|float[][] + * @var float[][] */ private $membership = []; diff --git a/src/Clustering/KMeans/Space.php b/src/Clustering/KMeans/Space.php index 566d691d..f9f57f5f 100644 --- a/src/Clustering/KMeans/Space.php +++ b/src/Clustering/KMeans/Space.php @@ -116,7 +116,7 @@ public function getRandomPoint(Point $min, Point $max): Point } /** - * @return array|Cluster[] + * @return Cluster[] */ public function cluster(int $clustersNumber, int $initMethod = KMeans::INIT_RANDOM): array { @@ -129,7 +129,7 @@ public function cluster(int $clustersNumber, int $initMethod = KMeans::INIT_RAND } /** - * @return array|Cluster[] + * @return Cluster[] */ protected function initializeClusters(int $clustersNumber, int $initMethod): array { diff --git a/src/CrossValidation/StratifiedRandomSplit.php b/src/CrossValidation/StratifiedRandomSplit.php index 4974d4ce..3b3acc47 100644 --- a/src/CrossValidation/StratifiedRandomSplit.php +++ b/src/CrossValidation/StratifiedRandomSplit.php @@ -19,7 +19,7 @@ protected function splitDataset(Dataset $dataset, float $testSize): void } /** - * @return Dataset[]|array + * @return Dataset[] */ private function splitByTarget(Dataset $dataset): array { diff --git a/src/Math/Statistic/ANOVA.php b/src/Math/Statistic/ANOVA.php index f7b01c7e..d233f84b 100644 --- a/src/Math/Statistic/ANOVA.php +++ b/src/Math/Statistic/ANOVA.php @@ -17,9 +17,9 @@ final class ANOVA * the same population mean. The test is applied to samples from two or * more groups, possibly with differing sizes. * - * @param array|array[] $samples - each row is class samples + * @param array[] $samples - each row is class samples * - * @return array|float[] + * @return float[] */ public static function oneWayF(array $samples): array { diff --git a/src/Math/Statistic/Correlation.php b/src/Math/Statistic/Correlation.php index 7039e39b..6878388f 100644 --- a/src/Math/Statistic/Correlation.php +++ b/src/Math/Statistic/Correlation.php @@ -9,8 +9,8 @@ class Correlation { /** - * @param array|int[]|float[] $x - * @param array|int[]|float[] $y + * @param int[]|float[] $x + * @param int[]|float[] $y * * @throws InvalidArgumentException */ diff --git a/src/Math/Statistic/StandardDeviation.php b/src/Math/Statistic/StandardDeviation.php index 170a9ee7..a9724d1c 100644 --- a/src/Math/Statistic/StandardDeviation.php +++ b/src/Math/Statistic/StandardDeviation.php @@ -9,7 +9,7 @@ class StandardDeviation { /** - * @param array|float[]|int[] $numbers + * @param float[]|int[] $numbers */ public static function population(array $numbers, bool $sample = true): float { @@ -39,7 +39,7 @@ public static function population(array $numbers, bool $sample = true): float * Sum of squares deviations * ∑⟮xᵢ - μ⟯² * - * @param array|float[]|int[] $numbers + * @param float[]|int[] $numbers */ public static function sumOfSquares(array $numbers): float { diff --git a/src/Pipeline.php b/src/Pipeline.php index d57da87f..41188f37 100644 --- a/src/Pipeline.php +++ b/src/Pipeline.php @@ -7,7 +7,7 @@ class Pipeline implements Estimator { /** - * @var array|Transformer[] + * @var Transformer[] */ private $transformers = []; @@ -17,7 +17,7 @@ class Pipeline implements Estimator private $estimator; /** - * @param array|Transformer[] $transformers + * @param Transformer[] $transformers */ public function __construct(array $transformers, Estimator $estimator) { @@ -39,7 +39,7 @@ public function setEstimator(Estimator $estimator): void } /** - * @return array|Transformer[] + * @return Transformer[] */ public function getTransformers(): array { From cc8e1b37531c1fcd60051aea2a6f600d762a2520 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 18 Dec 2018 07:53:32 +0100 Subject: [PATCH 295/328] Update phpunit to 7.5.1 and remove osx build from travis (#335) * Update phpunit to 7.5.1 * Remove osx build from travis --- .travis.yml | 7 ---- bin/prepare_osx_env.sh | 11 ------ composer.lock | 80 +++++++++++++++++++++--------------------- 3 files changed, 40 insertions(+), 58 deletions(-) delete mode 100644 bin/prepare_osx_env.sh diff --git a/.travis.yml b/.travis.yml index 77f956c7..68ef44bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,18 +15,11 @@ matrix: - os: linux php: '7.3' - - os: osx - osx_image: xcode7.3 - language: generic - env: - - _OSX=10.11 - cache: directories: - $HOME/.composer/cache before_install: - - if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then /usr/bin/env bash bin/prepare_osx_env.sh ; fi - if [[ $DISABLE_XDEBUG == "true" ]]; then phpenv config-rm xdebug.ini; fi install: diff --git a/bin/prepare_osx_env.sh b/bin/prepare_osx_env.sh deleted file mode 100644 index e185bd3c..00000000 --- a/bin/prepare_osx_env.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -echo "Here's the OSX environment:" -sw_vers -brew --version - -echo "Updating brew..." -brew update -brew install php@7.1 -brew upgrade php@7.1 -brew link php@7.1 --overwrite --force \ No newline at end of file diff --git a/composer.lock b/composer.lock index e5eee3ca..fd930b9e 100644 --- a/composer.lock +++ b/composer.lock @@ -1900,16 +1900,16 @@ }, { "name": "phpunit/phpunit", - "version": "7.5.0", + "version": "7.5.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "520723129e2b3fc1dc4c0953e43c9d40e1ecb352" + "reference": "c23d78776ad415d5506e0679723cb461d71f488f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/520723129e2b3fc1dc4c0953e43c9d40e1ecb352", - "reference": "520723129e2b3fc1dc4c0953e43c9d40e1ecb352", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c23d78776ad415d5506e0679723cb461d71f488f", + "reference": "c23d78776ad415d5506e0679723cb461d71f488f", "shasum": "" }, "require": { @@ -1980,7 +1980,7 @@ "testing", "xunit" ], - "time": "2018-12-07T07:08:12+00:00" + "time": "2018-12-12T07:20:32+00:00" }, { "name": "psr/cache", @@ -4138,23 +4138,23 @@ }, { "name": "symplify/better-phpdoc-parser", - "version": "v5.2.15", + "version": "v5.2.17", "source": { "type": "git", "url": "https://github.com/Symplify/BetterPhpDocParser.git", - "reference": "cb8f3b2c2f5d9f514b9dde90623c897ccc6a3e8f" + "reference": "cbdae4a58aa36016160a14ff9e4283fb8169448c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/BetterPhpDocParser/zipball/cb8f3b2c2f5d9f514b9dde90623c897ccc6a3e8f", - "reference": "cb8f3b2c2f5d9f514b9dde90623c897ccc6a3e8f", + "url": "https://api.github.com/repos/Symplify/BetterPhpDocParser/zipball/cbdae4a58aa36016160a14ff9e4283fb8169448c", + "reference": "cbdae4a58aa36016160a14ff9e4283fb8169448c", "shasum": "" }, "require": { "nette/utils": "^2.5", "php": "^7.1", "phpstan/phpdoc-parser": "^0.3", - "symplify/package-builder": "^5.2.15", + "symplify/package-builder": "^5.2.17", "thecodingmachine/safe": "^0.1.6" }, "require-dev": { @@ -4176,20 +4176,20 @@ "MIT" ], "description": "Slim wrapper around phpstan/phpdoc-parser with format preserving printer", - "time": "2018-12-07T13:16:16+00:00" + "time": "2018-12-12T11:50:57+00:00" }, { "name": "symplify/coding-standard", - "version": "v5.2.15", + "version": "v5.2.17", "source": { "type": "git", "url": "https://github.com/Symplify/CodingStandard.git", - "reference": "028c532b3822da4abff31b1554e020c88df86383" + "reference": "7e73f270bd3b41931f63b767956354f0236c45cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/CodingStandard/zipball/028c532b3822da4abff31b1554e020c88df86383", - "reference": "028c532b3822da4abff31b1554e020c88df86383", + "url": "https://api.github.com/repos/Symplify/CodingStandard/zipball/7e73f270bd3b41931f63b767956354f0236c45cc", + "reference": "7e73f270bd3b41931f63b767956354f0236c45cc", "shasum": "" }, "require": { @@ -4199,14 +4199,14 @@ "php": "^7.1", "slam/php-cs-fixer-extensions": "^1.17", "squizlabs/php_codesniffer": "^3.3", - "symplify/token-runner": "^5.2.15", + "symplify/token-runner": "^5.2.17", "thecodingmachine/safe": "^0.1.6" }, "require-dev": { "nette/application": "^2.4", "phpunit/phpunit": "^7.3", - "symplify/easy-coding-standard-tester": "^5.2.15", - "symplify/package-builder": "^5.2.15" + "symplify/easy-coding-standard-tester": "^5.2.17", + "symplify/package-builder": "^5.2.17" }, "type": "library", "extra": { @@ -4224,20 +4224,20 @@ "MIT" ], "description": "Set of Symplify rules for PHP_CodeSniffer and PHP CS Fixer.", - "time": "2018-12-07T13:16:16+00:00" + "time": "2018-12-12T11:50:57+00:00" }, { "name": "symplify/easy-coding-standard", - "version": "v5.2.15", + "version": "v5.2.17", "source": { "type": "git", "url": "https://github.com/Symplify/EasyCodingStandard.git", - "reference": "055982678422769731a7b0572a9fc10aae9609cc" + "reference": "e63f0d3d465104d730016e51aeac39cc73485c55" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/EasyCodingStandard/zipball/055982678422769731a7b0572a9fc10aae9609cc", - "reference": "055982678422769731a7b0572a9fc10aae9609cc", + "url": "https://api.github.com/repos/Symplify/EasyCodingStandard/zipball/e63f0d3d465104d730016e51aeac39cc73485c55", + "reference": "e63f0d3d465104d730016e51aeac39cc73485c55", "shasum": "" }, "require": { @@ -4257,14 +4257,14 @@ "symfony/finder": "^3.4|^4.1", "symfony/http-kernel": "^3.4|^4.1", "symfony/yaml": "^3.4|^4.1", - "symplify/coding-standard": "^5.2.15", - "symplify/package-builder": "^5.2.15", - "symplify/token-runner": "^5.2.15", + "symplify/coding-standard": "^5.2.17", + "symplify/package-builder": "^5.2.17", + "symplify/token-runner": "^5.2.17", "thecodingmachine/safe": "^0.1.6" }, "require-dev": { "phpunit/phpunit": "^7.3", - "symplify/easy-coding-standard-tester": "^5.2.15" + "symplify/easy-coding-standard-tester": "^5.2.17" }, "bin": [ "bin/ecs" @@ -4289,20 +4289,20 @@ "MIT" ], "description": "Use Coding Standard with 0-knowledge of PHP-CS-Fixer and PHP_CodeSniffer.", - "time": "2018-12-07T13:16:16+00:00" + "time": "2018-12-12T11:50:57+00:00" }, { "name": "symplify/package-builder", - "version": "v5.2.15", + "version": "v5.2.17", "source": { "type": "git", "url": "https://github.com/Symplify/PackageBuilder.git", - "reference": "978e9ac03eb4640282be4b56677e0dd8e665893d" + "reference": "4a5a771e719aeecedea2d1e631a8f6be976c7130" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/PackageBuilder/zipball/978e9ac03eb4640282be4b56677e0dd8e665893d", - "reference": "978e9ac03eb4640282be4b56677e0dd8e665893d", + "url": "https://api.github.com/repos/Symplify/PackageBuilder/zipball/4a5a771e719aeecedea2d1e631a8f6be976c7130", + "reference": "4a5a771e719aeecedea2d1e631a8f6be976c7130", "shasum": "" }, "require": { @@ -4337,20 +4337,20 @@ "MIT" ], "description": "Dependency Injection, Console and Kernel toolkit for Symplify packages.", - "time": "2018-12-02T21:24:21+00:00" + "time": "2018-12-12T11:50:51+00:00" }, { "name": "symplify/token-runner", - "version": "v5.2.15", + "version": "v5.2.17", "source": { "type": "git", "url": "https://github.com/Symplify/TokenRunner.git", - "reference": "73878adbf28a7ce974f896af4f3890a295c97d17" + "reference": "b6e2657ac326b4944d162d5c6020056bc69766b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/TokenRunner/zipball/73878adbf28a7ce974f896af4f3890a295c97d17", - "reference": "73878adbf28a7ce974f896af4f3890a295c97d17", + "url": "https://api.github.com/repos/Symplify/TokenRunner/zipball/b6e2657ac326b4944d162d5c6020056bc69766b8", + "reference": "b6e2657ac326b4944d162d5c6020056bc69766b8", "shasum": "" }, "require": { @@ -4359,8 +4359,8 @@ "nette/utils": "^2.5", "php": "^7.1", "squizlabs/php_codesniffer": "^3.3", - "symplify/better-phpdoc-parser": "^5.2.15", - "symplify/package-builder": "^5.2.15", + "symplify/better-phpdoc-parser": "^5.2.17", + "symplify/package-builder": "^5.2.17", "thecodingmachine/safe": "^0.1.6" }, "require-dev": { @@ -4382,7 +4382,7 @@ "MIT" ], "description": "Set of utils for PHP_CodeSniffer and PHP CS Fixer.", - "time": "2018-12-07T13:16:16+00:00" + "time": "2018-12-12T11:50:57+00:00" }, { "name": "thecodingmachine/safe", From 6844cf407acd99fd948b5eda593be9629148e982 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 23 Jan 2019 09:41:44 +0100 Subject: [PATCH 296/328] Fix typo in naive bayes docs --- docs/machine-learning/classification/naive-bayes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/machine-learning/classification/naive-bayes.md b/docs/machine-learning/classification/naive-bayes.md index 410fd458..af3b3577 100644 --- a/docs/machine-learning/classification/naive-bayes.md +++ b/docs/machine-learning/classification/naive-bayes.md @@ -24,6 +24,6 @@ To predict sample label use `predict` method. You can provide one sample or arra $classifier->predict([3, 1, 1]); // return 'a' -$classifier->predict([[3, 1, 1], [1, 4, 1]); +$classifier->predict([[3, 1, 1], [1, 4, 1]]); // return ['a', 'b'] ``` From 4b837fae8e55f4464856399fd9c825a6c9f838ec Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Wed, 6 Feb 2019 08:00:17 +0100 Subject: [PATCH 297/328] Improve distance performance and reduce duplication in distance classes. (#348) * Issue #347: Reduce duplicated code. * Issue #347: Replace array_* with regular loops for better perfomance. --- src/Math/Distance/Chebyshev.php | 23 ++++--------- src/Math/Distance/Distance.php | 61 +++++++++++++++++++++++++++++++++ src/Math/Distance/Euclidean.php | 28 ++++++--------- src/Math/Distance/Manhattan.php | 22 +++++------- src/Math/Distance/Minkowski.php | 38 ++++---------------- 5 files changed, 93 insertions(+), 79 deletions(-) create mode 100644 src/Math/Distance/Distance.php diff --git a/src/Math/Distance/Chebyshev.php b/src/Math/Distance/Chebyshev.php index 0ccd29a8..3c7dbc2b 100644 --- a/src/Math/Distance/Chebyshev.php +++ b/src/Math/Distance/Chebyshev.php @@ -4,27 +4,16 @@ namespace Phpml\Math\Distance; -use Phpml\Exception\InvalidArgumentException; -use Phpml\Math\Distance; - -class Chebyshev implements Distance +/** + * Class Chebyshev + */ +class Chebyshev extends Distance { /** - * @throws InvalidArgumentException + * {@inheritdoc} */ public function distance(array $a, array $b): float { - if (count($a) !== count($b)) { - throw new InvalidArgumentException('Size of given arrays does not match'); - } - - $differences = []; - $count = count($a); - - for ($i = 0; $i < $count; ++$i) { - $differences[] = abs($a[$i] - $b[$i]); - } - - return max($differences); + return max($this->deltas($a, $b)); } } diff --git a/src/Math/Distance/Distance.php b/src/Math/Distance/Distance.php new file mode 100644 index 00000000..ad9cdb99 --- /dev/null +++ b/src/Math/Distance/Distance.php @@ -0,0 +1,61 @@ +norm = $norm; + } + + /** + * @throws InvalidArgumentException + */ + public function distance(array $a, array $b): float + { + $distance = 0; + + foreach ($this->deltas($a, $b) as $delta) { + $distance += $delta ** $this->norm; + } + + return $distance ** (1 / $this->norm); + } + + /** + * @throws InvalidArgumentException + */ + protected function deltas(array $a, array $b): array + { + $count = count($a); + + if ($count !== count($b)) { + throw new InvalidArgumentException('Size of given arrays does not match'); + } + + $deltas = []; + + for ($i = 0; $i < $count; $i++) { + $deltas[] = abs($a[$i] - $b[$i]); + } + + return $deltas; + } +} diff --git a/src/Math/Distance/Euclidean.php b/src/Math/Distance/Euclidean.php index 4f437dcf..4b7abc40 100644 --- a/src/Math/Distance/Euclidean.php +++ b/src/Math/Distance/Euclidean.php @@ -4,31 +4,25 @@ namespace Phpml\Math\Distance; -use Phpml\Exception\InvalidArgumentException; -use Phpml\Math\Distance; - -class Euclidean implements Distance +/** + * Class Euclidean + * + * L^2 Metric. + */ +class Euclidean extends Distance { /** - * @throws InvalidArgumentException + * Euclidean constructor. */ - public function distance(array $a, array $b): float + public function __construct() { - if (count($a) !== count($b)) { - throw new InvalidArgumentException('Size of given arrays does not match'); - } - - $distance = 0; - - foreach ($a as $i => $val) { - $distance += ($val - $b[$i]) ** 2; - } - - return sqrt((float) $distance); + parent::__construct(2.0); } /** * Square of Euclidean distance + * + * @throws \Phpml\Exception\InvalidArgumentException */ public function sqDistance(array $a, array $b): float { diff --git a/src/Math/Distance/Manhattan.php b/src/Math/Distance/Manhattan.php index 459a5ec4..21ddee2e 100644 --- a/src/Math/Distance/Manhattan.php +++ b/src/Math/Distance/Manhattan.php @@ -4,22 +4,18 @@ namespace Phpml\Math\Distance; -use Phpml\Exception\InvalidArgumentException; -use Phpml\Math\Distance; - -class Manhattan implements Distance +/** + * Class Manhattan + * + * L^1 Metric. + */ +class Manhattan extends Distance { /** - * @throws InvalidArgumentException + * Manhattan constructor. */ - public function distance(array $a, array $b): float + public function __construct() { - if (count($a) !== count($b)) { - throw new InvalidArgumentException('Size of given arrays does not match'); - } - - return array_sum(array_map(function ($m, $n) { - return abs($m - $n); - }, $a, $b)); + parent::__construct(1.0); } } diff --git a/src/Math/Distance/Minkowski.php b/src/Math/Distance/Minkowski.php index 36edf9be..0ed5829b 100644 --- a/src/Math/Distance/Minkowski.php +++ b/src/Math/Distance/Minkowski.php @@ -4,37 +4,11 @@ namespace Phpml\Math\Distance; -use Phpml\Exception\InvalidArgumentException; -use Phpml\Math\Distance; - -class Minkowski implements Distance +/** + * Class Minkowski + * + * L^n Metric. + */ +class Minkowski extends Distance { - /** - * @var float - */ - private $lambda; - - public function __construct(float $lambda = 3.0) - { - $this->lambda = $lambda; - } - - /** - * @throws InvalidArgumentException - */ - public function distance(array $a, array $b): float - { - if (count($a) !== count($b)) { - throw new InvalidArgumentException('Size of given arrays does not match'); - } - - $distance = 0; - $count = count($a); - - for ($i = 0; $i < $count; ++$i) { - $distance += pow(abs($a[$i] - $b[$i]), $this->lambda); - } - - return (float) pow($distance, 1 / $this->lambda); - } } From 40f1ca06aa40ffd409c16ff4aacb7f8e2ffb483a Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Fri, 8 Feb 2019 22:24:02 +0100 Subject: [PATCH 298/328] Issue #351: Replace pow() and sqrt() with double stars notation. (#352) --- src/Classification/DecisionTree.php | 2 +- src/Classification/Ensemble/RandomForest.php | 2 +- src/Classification/NaiveBayes.php | 2 +- src/Clustering/KMeans/Point.php | 2 +- .../LinearAlgebra/EigenvalueDecomposition.php | 25 ++++++++++--------- src/Math/Matrix.php | 2 +- src/Math/Statistic/Correlation.php | 6 ++--- src/Math/Statistic/Gaussian.php | 2 +- src/Math/Statistic/StandardDeviation.php | 2 +- .../ActivationFunction/Gaussian.php | 2 +- .../ActivationFunction/HyperbolicTangent.php | 2 +- src/Preprocessing/Normalizer.php | 2 +- 12 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/Classification/DecisionTree.php b/src/Classification/DecisionTree.php index d8010f02..04cde562 100644 --- a/src/Classification/DecisionTree.php +++ b/src/Classification/DecisionTree.php @@ -137,7 +137,7 @@ public function getGiniIndex($baseValue, array $colValues, array $targets): floa $sum = array_sum(array_column($countMatrix, $i)); if ($sum > 0) { foreach ($this->labels as $label) { - $part += pow($countMatrix[$label][$i] / (float) $sum, 2); + $part += ($countMatrix[$label][$i] / (float) $sum) ** 2; } } diff --git a/src/Classification/Ensemble/RandomForest.php b/src/Classification/Ensemble/RandomForest.php index ac1304c6..b75d7ae3 100644 --- a/src/Classification/Ensemble/RandomForest.php +++ b/src/Classification/Ensemble/RandomForest.php @@ -131,7 +131,7 @@ protected function initSingleClassifier(Classifier $classifier): Classifier if (is_float($this->featureSubsetRatio)) { $featureCount = (int) ($this->featureSubsetRatio * $this->featureCount); } elseif ($this->featureSubsetRatio === 'sqrt') { - $featureCount = (int) sqrt($this->featureCount) + 1; + $featureCount = (int) ($this->featureCount ** .5) + 1; } else { $featureCount = (int) log($this->featureCount, 2) + 1; } diff --git a/src/Classification/NaiveBayes.php b/src/Classification/NaiveBayes.php index 45075a42..079b6f7f 100644 --- a/src/Classification/NaiveBayes.php +++ b/src/Classification/NaiveBayes.php @@ -162,7 +162,7 @@ private function sampleProbability(array $sample, int $feature, string $label): // scikit-learn did. // (See : https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/naive_bayes.py) $pdf = -0.5 * log(2.0 * M_PI * $std * $std); - $pdf -= 0.5 * pow($value - $mean, 2) / ($std * $std); + $pdf -= 0.5 * (($value - $mean) ** 2) / ($std * $std); return $pdf; } diff --git a/src/Clustering/KMeans/Point.php b/src/Clustering/KMeans/Point.php index f6ad3f57..a3f195d8 100644 --- a/src/Clustering/KMeans/Point.php +++ b/src/Clustering/KMeans/Point.php @@ -49,7 +49,7 @@ public function getDistanceWith(self $point, bool $precise = true) $distance += $difference * $difference; } - return $precise ? sqrt((float) $distance) : $distance; + return $precise ? $distance ** .5 : $distance; } /** diff --git a/src/Math/LinearAlgebra/EigenvalueDecomposition.php b/src/Math/LinearAlgebra/EigenvalueDecomposition.php index e0f16397..cc688640 100644 --- a/src/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -128,12 +128,13 @@ public function getEigenvectors(): array $vectors = new Matrix($vectors); $vectors = array_map(function ($vect) { $sum = 0; - for ($i = 0; $i < count($vect); ++$i) { + $count = count($vect); + for ($i = 0; $i < $count; ++$i) { $sum += $vect[$i] ** 2; } - $sum = sqrt($sum); - for ($i = 0; $i < count($vect); ++$i) { + $sum **= .5; + for ($i = 0; $i < $count; ++$i) { $vect[$i] /= $sum; } @@ -208,11 +209,11 @@ private function tred2(): void // Generate Householder vector. for ($k = 0; $k < $i; ++$k) { $this->d[$k] /= $scale; - $h += pow($this->d[$k], 2); + $h += $this->d[$k] ** 2; } $f = $this->d[$i_]; - $g = sqrt($h); + $g = $h ** .5; if ($f > 0) { $g = -$g; } @@ -320,7 +321,7 @@ private function tql2(): void $this->e[$this->n - 1] = 0.0; $f = 0.0; $tst1 = 0.0; - $eps = pow(2.0, -52.0); + $eps = 2.0 ** -52.0; for ($l = 0; $l < $this->n; ++$l) { // Find small subdiagonal element @@ -443,7 +444,7 @@ private function orthes(): void $h += $this->ort[$i] * $this->ort[$i]; } - $g = sqrt($h); + $g = $h ** .5; if ($this->ort[$m] > 0) { $g *= -1; } @@ -548,7 +549,7 @@ private function hqr2(): void $n = $nn - 1; $low = 0; $high = $nn - 1; - $eps = pow(2.0, -52.0); + $eps = 2.0 ** -52.0; $exshift = 0.0; $p = $q = $r = $s = $z = 0; // Store roots isolated by balanc and compute matrix norm @@ -596,7 +597,7 @@ private function hqr2(): void $w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n]; $p = ($this->H[$n - 1][$n - 1] - $this->H[$n][$n]) / 2.0; $q = $p * $p + $w; - $z = sqrt(abs($q)); + $z = abs($q) ** .5; $this->H[$n][$n] += $exshift; $this->H[$n - 1][$n - 1] += $exshift; $x = $this->H[$n][$n]; @@ -620,7 +621,7 @@ private function hqr2(): void $s = abs($x) + abs($z); $p = $x / $s; $q = $z / $s; - $r = sqrt($p * $p + $q * $q); + $r = ($p * $p + $q * $q) ** .5; $p /= $r; $q /= $r; // Row modification @@ -682,7 +683,7 @@ private function hqr2(): void $s = ($y - $x) / 2.0; $s *= $s + $w; if ($s > 0) { - $s = sqrt($s); + $s **= .5; if ($y < $x) { $s = -$s; } @@ -750,7 +751,7 @@ private function hqr2(): void break; } - $s = sqrt($p * $p + $q * $q + $r * $r); + $s = ($p * $p + $q * $q + $r * $r) ** .5; if ($p < 0) { $s = -$s; } diff --git a/src/Math/Matrix.php b/src/Math/Matrix.php index a511f558..db6be42b 100644 --- a/src/Math/Matrix.php +++ b/src/Math/Matrix.php @@ -271,7 +271,7 @@ public function frobeniusNorm(): float } } - return sqrt($squareSum); + return $squareSum ** .5; } /** diff --git a/src/Math/Statistic/Correlation.php b/src/Math/Statistic/Correlation.php index 6878388f..c730c473 100644 --- a/src/Math/Statistic/Correlation.php +++ b/src/Math/Statistic/Correlation.php @@ -32,10 +32,10 @@ public static function pearson(array $x, array $y): float $a = $x[$i] - $meanX; $b = $y[$i] - $meanY; $axb += ($a * $b); - $a2 += pow($a, 2); - $b2 += pow($b, 2); + $a2 += $a ** 2; + $b2 += $b ** 2; } - return $axb / sqrt((float) ($a2 * $b2)); + return $axb / ($a2 * $b2) ** .5; } } diff --git a/src/Math/Statistic/Gaussian.php b/src/Math/Statistic/Gaussian.php index ff8470ca..649063d8 100644 --- a/src/Math/Statistic/Gaussian.php +++ b/src/Math/Statistic/Gaussian.php @@ -34,7 +34,7 @@ public function pdf(float $value) $std2 = $this->std ** 2; $mean = $this->mean; - return exp(-(($value - $mean) ** 2) / (2 * $std2)) / sqrt(2 * $std2 * M_PI); + return exp(-(($value - $mean) ** 2) / (2 * $std2)) / ((2 * $std2 * M_PI) ** .5); } /** diff --git a/src/Math/Statistic/StandardDeviation.php b/src/Math/Statistic/StandardDeviation.php index a9724d1c..50effab7 100644 --- a/src/Math/Statistic/StandardDeviation.php +++ b/src/Math/Statistic/StandardDeviation.php @@ -32,7 +32,7 @@ public static function population(array $numbers, bool $sample = true): float --$n; } - return sqrt($carry / $n); + return ($carry / $n) ** .5; } /** diff --git a/src/NeuralNetwork/ActivationFunction/Gaussian.php b/src/NeuralNetwork/ActivationFunction/Gaussian.php index 8871b583..29cfef2d 100644 --- a/src/NeuralNetwork/ActivationFunction/Gaussian.php +++ b/src/NeuralNetwork/ActivationFunction/Gaussian.php @@ -13,7 +13,7 @@ class Gaussian implements ActivationFunction */ public function compute($value): float { - return exp(-pow($value, 2)); + return exp(- $value ** 2); } /** diff --git a/src/NeuralNetwork/ActivationFunction/HyperbolicTangent.php b/src/NeuralNetwork/ActivationFunction/HyperbolicTangent.php index 7aa96148..c230aff9 100644 --- a/src/NeuralNetwork/ActivationFunction/HyperbolicTangent.php +++ b/src/NeuralNetwork/ActivationFunction/HyperbolicTangent.php @@ -32,6 +32,6 @@ public function compute($value): float */ public function differentiate($value, $computedvalue): float { - return 1 - pow($computedvalue, 2); + return 1 - $computedvalue ** 2; } } diff --git a/src/Preprocessing/Normalizer.php b/src/Preprocessing/Normalizer.php index fb14adaf..9888e0e5 100644 --- a/src/Preprocessing/Normalizer.php +++ b/src/Preprocessing/Normalizer.php @@ -106,7 +106,7 @@ private function normalizeL2(array &$sample): void $norm2 += $feature * $feature; } - $norm2 = sqrt((float) $norm2); + $norm2 **= .5; if ($norm2 == 0) { $sample = array_fill(0, count($sample), 1); From b3fe9dae1e6fb277d29c04ef4f485ad2def680a2 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Sun, 10 Feb 2019 08:08:35 +0100 Subject: [PATCH 299/328] Issue #355: Add a .editorconfig file. (#356) --- .editorconfig | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..173228f8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +end_of_line = lf +charset = utf-8 +max_line_length = 80 +indent_style = space +indent_size = 4 +insert_final_newline = true From 02dab418300f8f4847db4d6aaeef51d155cda04f Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Fri, 15 Feb 2019 17:31:10 +0100 Subject: [PATCH 300/328] Provide a new NGramTokenizer with minGram and maxGram support (#350) * Issue #349: Provide a new NGramTokenizer. * Issue #349: Add tests. * Fixes from code review. * Implement NGramTokenizer with min and max gram support * Add missing tests for ngram * Add info about NGramTokenizer to docs and readme * Add performance test for tokenization --- README.md | 3 + .../token-count-vectorizer.md | 18 ++++ src/Tokenization/NGramTokenizer.php | 59 +++++++++++ .../Tokenization/NGramTokenizerBench.php | 33 ++++++ tests/Tokenization/NGramTokenizerTest.php | 100 ++++++++++++++++++ tests/Tokenization/TokenizerTest.php | 24 +++++ .../Tokenization/WhitespaceTokenizerTest.php | 21 ++-- tests/Tokenization/WordTokenizerTest.php | 15 +-- 8 files changed, 246 insertions(+), 27 deletions(-) create mode 100644 src/Tokenization/NGramTokenizer.php create mode 100644 tests/Performance/Tokenization/NGramTokenizerBench.php create mode 100644 tests/Tokenization/NGramTokenizerTest.php create mode 100644 tests/Tokenization/TokenizerTest.php diff --git a/README.md b/README.md index f518fd0d..4df5730e 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,9 @@ Public datasets are available in a separate repository [php-ai/php-ml-datasets]( * [Imputation missing values](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/imputation-missing-values/) * Feature Extraction * [Token Count Vectorizer](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-extraction/token-count-vectorizer/) + * NGramTokenizer + * WhitespaceTokenizer + * WordTokenizer * [Tf-idf Transformer](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-extraction/tf-idf-transformer/) * Dimensionality Reduction * PCA (Principal Component Analysis) diff --git a/docs/machine-learning/feature-extraction/token-count-vectorizer.md b/docs/machine-learning/feature-extraction/token-count-vectorizer.md index c4ede683..8e2e9fd0 100644 --- a/docs/machine-learning/feature-extraction/token-count-vectorizer.md +++ b/docs/machine-learning/feature-extraction/token-count-vectorizer.md @@ -53,3 +53,21 @@ $vectorizer->getVocabulary(); * WhitespaceTokenizer - select tokens by whitespace. * WordTokenizer - select tokens of 2 or more alphanumeric characters (punctuation is completely ignored and always treated as a token separator). +* NGramTokenizer - continuous sequence of characters of the specified length. They are useful for querying languages that don’t use spaces or that have long compound words, like German. + +**NGramTokenizer** + +The NGramTokenizer tokenizer accepts the following parameters: + +`$minGram` - minimum length of characters in a gram. Defaults to 1. +`$maxGram` - maximum length of characters in a gram. Defaults to 2. + +```php +use Phpml\Tokenization\NGramTokenizer; + +$tokenizer = new NGramTokenizer(1, 2); + +$tokenizer->tokenize('Quick Fox'); + +// returns ['Q', 'u', 'i', 'c', 'k', 'Qu', 'ui', 'ic', 'ck', 'F', 'o', 'x', 'Fo', 'ox'] +``` diff --git a/src/Tokenization/NGramTokenizer.php b/src/Tokenization/NGramTokenizer.php new file mode 100644 index 00000000..59e6f258 --- /dev/null +++ b/src/Tokenization/NGramTokenizer.php @@ -0,0 +1,59 @@ + $maxGram) { + throw new InvalidArgumentException(sprintf('Invalid (%s, %s) minGram and maxGram value combination', $minGram, $maxGram)); + } + + $this->minGram = $minGram; + $this->maxGram = $maxGram; + } + + /** + * {@inheritdoc} + */ + public function tokenize(string $text): array + { + $words = []; + preg_match_all('/\w\w+/u', $text, $words); + + $nGrams = []; + foreach ($words[0] as $word) { + $this->generateNGrams($word, $nGrams); + } + + return $nGrams; + } + + private function generateNGrams(string $word, array &$nGrams): void + { + $length = mb_strlen($word); + + for ($j = 1; $j <= $this->maxGram; $j++) { + for ($k = 0; $k < $length - $j + 1; $k++) { + if ($j >= $this->minGram) { + $nGrams[] = mb_substr($word, $k, $j); + } + } + } + } +} diff --git a/tests/Performance/Tokenization/NGramTokenizerBench.php b/tests/Performance/Tokenization/NGramTokenizerBench.php new file mode 100644 index 00000000..f99128d2 --- /dev/null +++ b/tests/Performance/Tokenization/NGramTokenizerBench.php @@ -0,0 +1,33 @@ +tokenize( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent placerat blandit cursus. Suspendisse sed + turpis sit amet enim viverra sodales a euismod est. Ut vitae tincidunt est. Proin venenatis placerat nunc + sed ornare. Etiam feugiat, nisl nec sollicitudin sodales, nulla massa sollicitudin ipsum, vitae cursus ante + velit vitae arcu. Vestibulum feugiat ultricies hendrerit. Morbi sed varius metus. Nam feugiat maximus + turpis, a sollicitudin ligula porttitor eu.Fusce hendrerit tellus et dignissim sagittis. Nulla consectetur + condimentum tortor, non bibendum erat lacinia eget. Integer vitae maximus tortor. Vestibulum ante ipsum + primis in faucibus orci luctus et ultrices posuere cubilia Curae; Pellentesque suscipit sem ipsum, in + tincidunt risus pellentesque vel. Nullam hendrerit consequat leo, in suscipit lectus euismod non. Cras arcu + lacus, lacinia semper mauris vel, pharetra dignissim velit. Nam lacinia turpis a nibh bibendum, et + placerat tellus accumsan. Sed tincidunt cursus nisi in laoreet. Suspendisse amet.' + ); + } +} diff --git a/tests/Tokenization/NGramTokenizerTest.php b/tests/Tokenization/NGramTokenizerTest.php new file mode 100644 index 00000000..2df95314 --- /dev/null +++ b/tests/Tokenization/NGramTokenizerTest.php @@ -0,0 +1,100 @@ +tokenize($text)); + } + + public function testMinGramGreaterThanMaxGramNotAllowed(): void + { + self::expectException(InvalidArgumentException::class); + + new NGramTokenizer(5, 2); + } + + public function testMinGramValueTooSmall(): void + { + self::expectException(InvalidArgumentException::class); + + new NGramTokenizer(0, 2); + } + + public function testMaxGramValueTooSmall(): void + { + self::expectException(InvalidArgumentException::class); + + new NGramTokenizer(1, 0); + } + + public function textDataProvider(): array + { + return [ + [ + 1, 2, + 'Quick Fox', + ['Q', 'u', 'i', 'c', 'k', 'Qu', 'ui', 'ic', 'ck', 'F', 'o', 'x', 'Fo', 'ox'], + ], + [ + 3, 3, + 'Quick Foxes', + ['Qui', 'uic', 'ick', 'Fox', 'oxe', 'xes'], + ], + [ + 1, 2, + '快狐跑过 边缘跑', + ['快', '狐', '跑', '过', '快狐', '狐跑', '跑过', '边', '缘', '跑', '边缘', '缘跑'], + ], + [ + 3, 3, + '快狐跑过狐 边缘跑狐狐', + ['快狐跑', '狐跑过', '跑过狐', '边缘跑', '缘跑狐', '跑狐狐'], + ], + [ + 2, 4, + $this->getSimpleText(), + [ + 'Lo', 'or', 're', 'em', 'Lor', 'ore', 'rem', 'Lore', 'orem', 'ip', 'ps', 'su', 'um', 'ips', 'psu', 'sum', 'ipsu', + 'psum', 'do', 'ol', 'lo', 'or', 'dol', 'olo', 'lor', 'dolo', 'olor', 'si', 'it', 'sit', 'am', 'me', 'et', 'ame', + 'met', 'amet', 'co', 'on', 'ns', 'se', 'ec', 'ct', 'te', 'et', 'tu', 'ur', 'con', 'ons', 'nse', 'sec', 'ect', 'cte', + 'tet', 'etu', 'tur', 'cons', 'onse', 'nsec', 'sect', 'ecte', 'ctet', 'tetu', 'etur', 'ad', 'di', 'ip', 'pi', 'is', + 'sc', 'ci', 'in', 'ng', 'adi', 'dip', 'ipi', 'pis', 'isc', 'sci', 'cin', 'ing', 'adip', 'dipi', 'ipis', 'pisc', + 'isci', 'scin', 'cing', 'el', 'li', 'it', 'eli', 'lit', 'elit', 'Cr', 'ra', 'as', 'Cra', 'ras', 'Cras', 'co', 'on', + 'ns', 'se', 'ec', 'ct', 'te', 'et', 'tu', 'ur', 'con', 'ons', 'nse', 'sec', 'ect', 'cte', 'tet', 'etu', 'tur', + 'cons', 'onse', 'nsec', 'sect', 'ecte', 'ctet', 'tetu', 'etur', 'du', 'ui', 'dui', 'et', 'lo', 'ob', 'bo', 'or', + 'rt', 'ti', 'is', 'lob', 'obo', 'bor', 'ort', 'rti', 'tis', 'lobo', 'obor', 'bort', 'orti', 'rtis', 'au', 'uc', + 'ct', 'to', 'or', 'auc', 'uct', 'cto', 'tor', 'auct', 'ucto', 'ctor', 'Nu', 'ul', 'll', 'la', 'Nul', 'ull', 'lla', + 'Null', 'ulla', 'vi', 'it', 'ta', 'ae', 'vit', 'ita', 'tae', 'vita', 'itae', 'co', 'on', 'ng', 'gu', 'ue', 'con', + 'ong', 'ngu', 'gue', 'cong', 'ongu', 'ngue', 'lo', 'or', 're', 'em', 'lor', 'ore', 'rem', 'lore', 'orem', + ], + ], + [ + 2, 4, + $this->getUtf8Text(), + [ + '鋍鞎', '鞮鞢', '鞢騉', '鞮鞢騉', '袟袘', '袘觕', '袟袘觕', '炟砏', '謺貙', '貙蹖', '謺貙蹖', '偢偣', '偣唲', + '偢偣唲', '箷箯', '箯緷', '箷箯緷', '鑴鱱', '鱱爧', '鑴鱱爧', '覮轀', '剆坲', '煘煓', '煓瑐', '煘煓瑐', '鬐鶤', + '鶤鶐', '鬐鶤鶐', '飹勫', '勫嫢', '飹勫嫢', '枲柊', '柊氠', '枲柊氠', '鍎鞚', '鞚韕', '鍎鞚韕', '焲犈', '殍涾', + '涾烰', '殍涾烰', '齞齝', '齝囃', '齞齝囃', '蹅輶', '孻憵', '擙樲', '樲橚', '擙樲橚', '藒襓', '襓謥', '藒襓謥', + '岯岪', '岪弨', '岯岪弨', '廞徲', '孻憵', '憵懥', '孻憵懥', '趡趛', '趛踠', '趡趛踠', + ], + ], + ]; + } +} diff --git a/tests/Tokenization/TokenizerTest.php b/tests/Tokenization/TokenizerTest.php new file mode 100644 index 00000000..5d0833cd --- /dev/null +++ b/tests/Tokenization/TokenizerTest.php @@ -0,0 +1,24 @@ +tokenize($text)); + self::assertEquals($tokens, $tokenizer->tokenize($this->getSimpleText())); } public function testTokenizationOnUtf8(): void { $tokenizer = new WhitespaceTokenizer(); - $text = '鋍鞎 鳼 鞮鞢騉 袟袘觕, 炟砏 蒮 謺貙蹖 偢偣唲 蒛 箷箯緷 鑴鱱爧 覮轀, - 剆坲 煘煓瑐 鬐鶤鶐 飹勫嫢 銪 餀 枲柊氠 鍎鞚韕 焲犈, - 殍涾烰 齞齝囃 蹅輶 鄜, 孻憵 擙樲橚 藒襓謥 岯岪弨 蒮 廞徲 孻憵懥 趡趛踠 槏'; - $tokens = ['鋍鞎', '鳼', '鞮鞢騉', '袟袘觕,', '炟砏', '蒮', '謺貙蹖', '偢偣唲', '蒛', '箷箯緷', '鑴鱱爧', '覮轀,', '剆坲', '煘煓瑐', '鬐鶤鶐', '飹勫嫢', '銪', '餀', '枲柊氠', '鍎鞚韕', '焲犈,', '殍涾烰', '齞齝囃', '蹅輶', '鄜,', '孻憵', '擙樲橚', '藒襓謥', '岯岪弨', '蒮', '廞徲', '孻憵懥', '趡趛踠', '槏', ]; - self::assertEquals($tokens, $tokenizer->tokenize($text)); + self::assertEquals($tokens, $tokenizer->tokenize($this->getUtf8Text())); } } diff --git a/tests/Tokenization/WordTokenizerTest.php b/tests/Tokenization/WordTokenizerTest.php index 39448b78..9c55dd60 100644 --- a/tests/Tokenization/WordTokenizerTest.php +++ b/tests/Tokenization/WordTokenizerTest.php @@ -5,37 +5,28 @@ namespace Phpml\Tests\Tokenization; use Phpml\Tokenization\WordTokenizer; -use PHPUnit\Framework\TestCase; -class WordTokenizerTest extends TestCase +class WordTokenizerTest extends TokenizerTest { public function testTokenizationOnAscii(): void { $tokenizer = new WordTokenizer(); - $text = 'Lorem ipsum-dolor sit amet, consectetur/adipiscing elit. - Cras consectetur, dui et lobortis;auctor. - Nulla vitae ,.,/ congue lorem.'; - $tokens = ['Lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur', 'adipiscing', 'elit', 'Cras', 'consectetur', 'dui', 'et', 'lobortis', 'auctor', 'Nulla', 'vitae', 'congue', 'lorem', ]; - self::assertEquals($tokens, $tokenizer->tokenize($text)); + self::assertEquals($tokens, $tokenizer->tokenize($this->getSimpleText())); } public function testTokenizationOnUtf8(): void { $tokenizer = new WordTokenizer(); - $text = '鋍鞎 鳼 鞮鞢騉 袟袘觕, 炟砏 蒮 謺貙蹖 偢偣唲 蒛 箷箯緷 鑴鱱爧 覮轀, - 剆坲 煘煓瑐 鬐鶤鶐 飹勫嫢 銪 餀 枲柊氠 鍎鞚韕 焲犈, - 殍涾烰 齞齝囃 蹅輶 鄜, 孻憵 擙樲橚 藒襓謥 岯岪弨 蒮 廞徲 孻憵懥 趡趛踠 槏'; - $tokens = ['鋍鞎', '鞮鞢騉', '袟袘觕', '炟砏', '謺貙蹖', '偢偣唲', '箷箯緷', '鑴鱱爧', '覮轀', '剆坲', '煘煓瑐', '鬐鶤鶐', '飹勫嫢', '枲柊氠', '鍎鞚韕', '焲犈', '殍涾烰', '齞齝囃', '蹅輶', '孻憵', '擙樲橚', '藒襓謥', '岯岪弨', '廞徲', '孻憵懥', '趡趛踠', ]; - self::assertEquals($tokens, $tokenizer->tokenize($text)); + self::assertEquals($tokens, $tokenizer->tokenize($this->getUtf8Text())); } } From 5e02b893e904761cd7e2415b11778cd421494c07 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 20 Mar 2019 23:22:45 +0100 Subject: [PATCH 301/328] Fix FilesDataset arrays and TokenCountVectorizer numeric token (#363) --- CHANGELOG.md | 13 +++++++++++++ src/Dataset/FilesDataset.php | 2 +- src/FeatureExtraction/TokenCountVectorizer.php | 2 +- tests/Dataset/FilesDatasetTest.php | 4 ++-- .../FeatureExtraction/TokenCountVectorizerTest.php | 7 ++++++- 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e00fb13..63507f0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.8.0] - 2019-03-20 +### Added +- [Tokenization] Added NGramTokenizer (#350) +- editorconfig file (#355) +### Fixed +- [Dataset] FilesDataset read samples without additional array (#363) +- [Tokenization] fixed error with numeric token values (#363) +### Changed +- [Math] improved performance with pow and sqrt replacement (#350) +- [Math] reduce duplicated code in distance metrics (#348) +- update phpunit to 7.5.1 (#335) +- code style fixes (#334) + ## [0.7.0] - 2018-11-07 ### Added - [Clustering] added KMeans associative clustering (#262) diff --git a/src/Dataset/FilesDataset.php b/src/Dataset/FilesDataset.php index a1597535..daa7192c 100644 --- a/src/Dataset/FilesDataset.php +++ b/src/Dataset/FilesDataset.php @@ -29,7 +29,7 @@ private function scanDir(string $dir): void $target = basename($dir); foreach (array_filter(glob($dir.DIRECTORY_SEPARATOR.'*'), 'is_file') as $file) { - $this->samples[] = [file_get_contents($file)]; + $this->samples[] = file_get_contents($file); $this->targets[] = $target; } } diff --git a/src/FeatureExtraction/TokenCountVectorizer.php b/src/FeatureExtraction/TokenCountVectorizer.php index a1e38f44..afd5f339 100644 --- a/src/FeatureExtraction/TokenCountVectorizer.php +++ b/src/FeatureExtraction/TokenCountVectorizer.php @@ -157,7 +157,7 @@ private function getBeyondMinimumIndexes(int $samplesCount): array $indexes = []; foreach ($this->frequencies as $token => $frequency) { if (($frequency / $samplesCount) < $this->minDF) { - $indexes[] = $this->getTokenIndex($token); + $indexes[] = $this->getTokenIndex((string) $token); } } diff --git a/tests/Dataset/FilesDatasetTest.php b/tests/Dataset/FilesDatasetTest.php index ec30f91b..a7ecd97a 100644 --- a/tests/Dataset/FilesDatasetTest.php +++ b/tests/Dataset/FilesDatasetTest.php @@ -29,13 +29,13 @@ public function testLoadFilesDatasetWithBBCData(): void self::assertEquals($targets, array_values(array_unique($dataset->getTargets()))); $firstSample = file_get_contents($rootPath.'/business/001.txt'); - self::assertEquals($firstSample, $dataset->getSamples()[0][0]); + self::assertEquals($firstSample, $dataset->getSamples()[0]); $firstTarget = 'business'; self::assertEquals($firstTarget, $dataset->getTargets()[0]); $lastSample = file_get_contents($rootPath.'/tech/010.txt'); - self::assertEquals($lastSample, $dataset->getSamples()[49][0]); + self::assertEquals($lastSample, $dataset->getSamples()[49]); $lastTarget = 'tech'; self::assertEquals($lastTarget, $dataset->getTargets()[49]); diff --git a/tests/FeatureExtraction/TokenCountVectorizerTest.php b/tests/FeatureExtraction/TokenCountVectorizerTest.php index dff9436a..13479156 100644 --- a/tests/FeatureExtraction/TokenCountVectorizerTest.php +++ b/tests/FeatureExtraction/TokenCountVectorizerTest.php @@ -84,7 +84,7 @@ public function testTransformationWithMinimumDocumentTokenCountFrequency(): void { // word at least in half samples $samples = [ - 'Lorem ipsum dolor sit amet', + 'Lorem ipsum dolor sit amet 1550', 'Lorem ipsum sit amet', 'ipsum sit amet', 'ipsum sit amet', @@ -96,6 +96,7 @@ public function testTransformationWithMinimumDocumentTokenCountFrequency(): void 2 => 'dolor', 3 => 'sit', 4 => 'amet', + 5 => 1550, ]; $tokensCounts = [ @@ -105,6 +106,7 @@ public function testTransformationWithMinimumDocumentTokenCountFrequency(): void 2 => 0, 3 => 1, 4 => 1, + 5 => 0, ], [ 0 => 1, @@ -112,6 +114,7 @@ public function testTransformationWithMinimumDocumentTokenCountFrequency(): void 2 => 0, 3 => 1, 4 => 1, + 5 => 0, ], [ 0 => 0, @@ -119,6 +122,7 @@ public function testTransformationWithMinimumDocumentTokenCountFrequency(): void 2 => 0, 3 => 1, 4 => 1, + 5 => 0, ], [ 0 => 0, @@ -126,6 +130,7 @@ public function testTransformationWithMinimumDocumentTokenCountFrequency(): void 2 => 0, 3 => 1, 4 => 1, + 5 => 0, ], ]; From d3888efa7a7a3090a09cc30830f0750c22fbb80b Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Mon, 25 Mar 2019 14:55:14 +0100 Subject: [PATCH 302/328] Update phpstan & easy coding standard (#366) --- composer.json | 6 +- composer.lock | 1036 ++++++++++------- ecs.yml | 12 +- phpstan.neon | 5 +- src/Classification/Ensemble/RandomForest.php | 14 +- .../Linear/LogisticRegression.php | 2 +- src/Clustering/FuzzyCMeans.php | 5 +- src/Dataset/SvmDataset.php | 2 +- .../UnivariateLinearRegression.php | 2 +- src/Math/Kernel/RBF.php | 9 +- .../LinearAlgebra/EigenvalueDecomposition.php | 5 +- .../Network/MultilayerPerceptron.php | 10 +- .../Optimizer/ConjugateGradientTest.php | 6 +- tests/Helper/Optimizer/GDTest.php | 4 +- tests/Helper/Optimizer/StochasticGDTest.php | 4 +- tests/Math/Kernel/RBFTest.php | 9 + 16 files changed, 644 insertions(+), 487 deletions(-) diff --git a/composer.json b/composer.json index 1604bbfd..8b3b1586 100644 --- a/composer.json +++ b/composer.json @@ -24,9 +24,9 @@ }, "require-dev": { "phpbench/phpbench": "^0.14.0", - "phpstan/phpstan-phpunit": "^0.10", - "phpstan/phpstan-shim": "^0.10", - "phpstan/phpstan-strict-rules": "^0.10", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpstan/phpstan-strict-rules": "^0.11", "phpunit/phpunit": "^7.0.0", "symplify/coding-standard": "^5.1", "symplify/easy-coding-standard": "^5.1" diff --git a/composer.lock b/composer.lock index fd930b9e..62a34e0c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9ec1ca6b843d05e0870bd777026d7a8b", + "content-hash": "dee9be6daf48915171d2778b17d941fa", "packages": [], "packages-dev": [ { @@ -64,16 +64,16 @@ }, { "name": "composer/semver", - "version": "1.4.2", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573" + "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573", - "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573", + "url": "https://api.github.com/repos/composer/semver/zipball/46d9139568ccb8d9e7cdd4539cab7347568a5e2e", + "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e", "shasum": "" }, "require": { @@ -122,20 +122,20 @@ "validation", "versioning" ], - "time": "2016-08-30T16:08:34+00:00" + "time": "2019-03-19T17:25:45+00:00" }, { "name": "composer/xdebug-handler", - "version": "1.3.1", + "version": "1.3.2", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "dc523135366eb68f22268d069ea7749486458562" + "reference": "d17708133b6c276d6e42ef887a877866b909d892" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/dc523135366eb68f22268d069ea7749486458562", - "reference": "dc523135366eb68f22268d069ea7749486458562", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/d17708133b6c276d6e42ef887a877866b909d892", + "reference": "d17708133b6c276d6e42ef887a877866b909d892", "shasum": "" }, "require": { @@ -166,7 +166,7 @@ "Xdebug", "performance" ], - "time": "2018-11-29T10:59:02+00:00" + "time": "2019-01-28T20:25:53+00:00" }, { "name": "doctrine/annotations", @@ -236,29 +236,98 @@ ], "time": "2017-12-06T07:11:42+00:00" }, + { + "name": "doctrine/inflector", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "5527a48b7313d15261292c149e55e26eae771b0a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/5527a48b7313d15261292c149e55e26eae771b0a", + "reference": "5527a48b7313d15261292c149e55e26eae771b0a", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Common String Manipulations with regard to casing and singular/plural rules.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "inflection", + "pluralize", + "singularize", + "string" + ], + "time": "2018-01-09T20:05:19+00:00" + }, { "name": "doctrine/instantiator", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" + "reference": "a2c590166b2133a4633738648b6b064edae0814a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", + "reference": "a2c590166b2133a4633738648b6b064edae0814a", "shasum": "" }, "require": { "php": "^7.1" }, "require-dev": { - "athletic/athletic": "~0.1.8", + "doctrine/coding-standard": "^6.0", "ext-pdo": "*", "ext-phar": "*", - "phpunit/phpunit": "^6.2.3", - "squizlabs/php_codesniffer": "^3.0.2" + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { @@ -283,12 +352,12 @@ } ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ "constructor", "instantiate" ], - "time": "2017-07-22T11:58:36+00:00" + "time": "2019-03-17T17:37:11+00:00" }, { "name": "doctrine/lexer", @@ -346,16 +415,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v2.13.1", + "version": "v2.14.2", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "54814c62d5beef3ba55297b9b3186ed8b8a1b161" + "reference": "ff401e58261ffc5934a58f795b3f95b355e276cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/54814c62d5beef3ba55297b9b3186ed8b8a1b161", - "reference": "54814c62d5beef3ba55297b9b3186ed8b8a1b161", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/ff401e58261ffc5934a58f795b3f95b355e276cb", + "reference": "ff401e58261ffc5934a58f795b3f95b355e276cb", "shasum": "" }, "require": { @@ -364,7 +433,7 @@ "doctrine/annotations": "^1.2", "ext-json": "*", "ext-tokenizer": "*", - "php": "^5.6 || >=7.0 <7.3", + "php": "^5.6 || ^7.0", "php-cs-fixer/diff": "^1.3", "symfony/console": "^3.4.17 || ^4.1.6", "symfony/event-dispatcher": "^3.0 || ^4.0", @@ -376,13 +445,10 @@ "symfony/process": "^3.0 || ^4.0", "symfony/stopwatch": "^3.0 || ^4.0" }, - "conflict": { - "hhvm": "*" - }, "require-dev": { "johnkary/phpunit-speedtrap": "^1.1 || ^2.0 || ^3.0", "justinrainbow/json-schema": "^5.0", - "keradus/cli-executor": "^1.1", + "keradus/cli-executor": "^1.2", "mikey179/vfsstream": "^1.6", "php-coveralls/php-coveralls": "^2.1", "php-cs-fixer/accessible-object": "^1.0", @@ -433,7 +499,112 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2018-10-21T00:32:10+00:00" + "time": "2019-02-17T17:44:13+00:00" + }, + { + "name": "illuminate/contracts", + "version": "v5.8.4", + "source": { + "type": "git", + "url": "https://github.com/illuminate/contracts.git", + "reference": "3e3a9a654adbf798e05491a5dbf90112df1effde" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/3e3a9a654adbf798e05491a5dbf90112df1effde", + "reference": "3e3a9a654adbf798e05491a5dbf90112df1effde", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "psr/container": "^1.0", + "psr/simple-cache": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.8-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Contracts\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Contracts package.", + "homepage": "https://laravel.com", + "time": "2019-02-18T18:37:54+00:00" + }, + { + "name": "illuminate/support", + "version": "v5.8.4", + "source": { + "type": "git", + "url": "https://github.com/illuminate/support.git", + "reference": "07062f5750872a31e086ff37a7c50ac18b8c417c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/support/zipball/07062f5750872a31e086ff37a7c50ac18b8c417c", + "reference": "07062f5750872a31e086ff37a7c50ac18b8c417c", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^1.1", + "ext-json": "*", + "ext-mbstring": "*", + "illuminate/contracts": "5.8.*", + "nesbot/carbon": "^1.26.3 || ^2.0", + "php": "^7.1.3" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "suggest": { + "illuminate/filesystem": "Required to use the composer class (5.8.*).", + "moontoast/math": "Required to use ordered UUIDs (^1.1).", + "ramsey/uuid": "Required to use Str::uuid() (^3.7).", + "symfony/process": "Required to use the composer class (^4.2).", + "symfony/var-dumper": "Required to use the dd function (^4.2).", + "vlucas/phpdotenv": "Required to use the env helper (^3.3)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.8-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + }, + "files": [ + "helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Support package.", + "homepage": "https://laravel.com", + "time": "2019-03-12T13:17:00+00:00" }, { "name": "jean85/pretty-package-versions", @@ -673,35 +844,95 @@ ], "time": "2018-06-11T23:09:50+00:00" }, + { + "name": "nesbot/carbon", + "version": "2.16.0", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "dd16fedc022180ea4292a03aabe95e9895677911" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/dd16fedc022180ea4292a03aabe95e9895677911", + "reference": "dd16fedc022180ea4292a03aabe95e9895677911", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.1.8 || ^8.0", + "symfony/translation": "^3.4 || ^4.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.14 || ^3.0", + "kylekatarnls/multi-tester": "^0.1", + "phpmd/phpmd": "^2.6", + "phpstan/phpstan": "^0.10.8", + "phpunit/phpunit": "^7.5 || ^8.0", + "squizlabs/php_codesniffer": "^3.4" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "http://nesbot.com" + } + ], + "description": "A simple API extension for DateTime.", + "homepage": "http://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "time": "2019-03-12T09:31:40+00:00" + }, { "name": "nette/finder", - "version": "v2.4.2", + "version": "v2.5.0", "source": { "type": "git", "url": "https://github.com/nette/finder.git", - "reference": "ee951a656cb8ac622e5dd33474a01fd2470505a0" + "reference": "6be1b83ea68ac558aff189d640abe242e0306fe2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/finder/zipball/ee951a656cb8ac622e5dd33474a01fd2470505a0", - "reference": "ee951a656cb8ac622e5dd33474a01fd2470505a0", + "url": "https://api.github.com/repos/nette/finder/zipball/6be1b83ea68ac558aff189d640abe242e0306fe2", + "reference": "6be1b83ea68ac558aff189d640abe242e0306fe2", "shasum": "" }, "require": { - "nette/utils": "~2.4", - "php": ">=5.6.0" + "nette/utils": "^2.4 || ~3.0.0", + "php": ">=7.1" }, "conflict": { "nette/nette": "<2.2" }, "require-dev": { - "nette/tester": "~2.0", + "nette/tester": "^2.0", "tracy/tracy": "^2.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.4-dev" + "dev-master": "2.5-dev" } }, "autoload": { @@ -733,20 +964,20 @@ "iterator", "nette" ], - "time": "2018-06-28T11:49:23+00:00" + "time": "2019-02-28T18:13:25+00:00" }, { "name": "nette/robot-loader", - "version": "v3.1.0", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/nette/robot-loader.git", - "reference": "fc76c70e740b10f091e502b2e393d0be912f38d4" + "reference": "3e8d75d6d976e191bdf46752ca40a286671219d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/robot-loader/zipball/fc76c70e740b10f091e502b2e393d0be912f38d4", - "reference": "fc76c70e740b10f091e502b2e393d0be912f38d4", + "url": "https://api.github.com/repos/nette/robot-loader/zipball/3e8d75d6d976e191bdf46752ca40a286671219d2", + "reference": "3e8d75d6d976e191bdf46752ca40a286671219d2", "shasum": "" }, "require": { @@ -798,7 +1029,7 @@ "nette", "trait" ], - "time": "2018-08-13T14:19:06+00:00" + "time": "2019-03-01T20:23:02+00:00" }, { "name": "nette/utils", @@ -884,16 +1115,16 @@ }, { "name": "ocramius/package-versions", - "version": "1.3.0", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/Ocramius/PackageVersions.git", - "reference": "4489d5002c49d55576fa0ba786f42dbb009be46f" + "reference": "a4d4b60d0e60da2487bd21a2c6ac089f85570dbb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/4489d5002c49d55576fa0ba786f42dbb009be46f", - "reference": "4489d5002c49d55576fa0ba786f42dbb009be46f", + "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/a4d4b60d0e60da2487bd21a2c6ac089f85570dbb", + "reference": "a4d4b60d0e60da2487bd21a2c6ac089f85570dbb", "shasum": "" }, "require": { @@ -902,6 +1133,7 @@ }, "require-dev": { "composer/composer": "^1.6.3", + "doctrine/coding-standard": "^5.0.1", "ext-zip": "*", "infection/infection": "^0.7.1", "phpunit/phpunit": "^7.0.0" @@ -929,7 +1161,7 @@ } ], "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", - "time": "2018-02-05T13:05:30+00:00" + "time": "2019-02-21T12:16:21+00:00" }, { "name": "paragonie/random_compat", @@ -1508,22 +1740,23 @@ }, { "name": "phpstan/phpstan-phpunit", - "version": "0.10", + "version": "0.11", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "6feecc7faae187daa6be44140cd0f1ba210e6aa0" + "reference": "70c22d44b96a21a4952fc13021a5a63cc83f5c81" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/6feecc7faae187daa6be44140cd0f1ba210e6aa0", - "reference": "6feecc7faae187daa6be44140cd0f1ba210e6aa0", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/70c22d44b96a21a4952fc13021a5a63cc83f5c81", + "reference": "70c22d44b96a21a4952fc13021a5a63cc83f5c81", "shasum": "" }, "require": { "nikic/php-parser": "^4.0", "php": "~7.1", - "phpstan/phpstan": "^0.10" + "phpstan/phpdoc-parser": "^0.3", + "phpstan/phpstan": "^0.11" }, "conflict": { "phpunit/phpunit": "<7.0" @@ -1533,7 +1766,7 @@ "dealerdirect/phpcodesniffer-composer-installer": "^0.4.4", "jakub-onderka/php-parallel-lint": "^1.0", "phing/phing": "^2.16.0", - "phpstan/phpstan-strict-rules": "^0.10", + "phpstan/phpstan-strict-rules": "^0.11", "phpunit/phpunit": "^7.0", "satooshi/php-coveralls": "^1.0", "slevomat/coding-standard": "^4.5.2" @@ -1541,7 +1774,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "0.10-dev" + "dev-master": "0.11-dev" } }, "autoload": { @@ -1554,20 +1787,20 @@ "MIT" ], "description": "PHPUnit extensions and rules for PHPStan", - "time": "2018-06-22T18:12:17+00:00" + "time": "2018-12-22T14:05:04+00:00" }, { "name": "phpstan/phpstan-shim", - "version": "0.10.6", + "version": "0.11.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-shim.git", - "reference": "c729ee281588bdb73ae50051503a6785aed46721" + "reference": "70e1a346907142449ac085745f158aa715b4e0b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-shim/zipball/c729ee281588bdb73ae50051503a6785aed46721", - "reference": "c729ee281588bdb73ae50051503a6785aed46721", + "url": "https://api.github.com/repos/phpstan/phpstan-shim/zipball/70e1a346907142449ac085745f158aa715b4e0b8", + "reference": "70e1a346907142449ac085745f158aa715b4e0b8", "shasum": "" }, "require": { @@ -1585,7 +1818,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "0.10-dev" + "dev-master": "0.11-dev" } }, "autoload": { @@ -1598,40 +1831,40 @@ "MIT" ], "description": "PHPStan Phar distribution", - "time": "2018-12-04T07:53:38+00:00" + "time": "2019-03-14T15:24:47+00:00" }, { "name": "phpstan/phpstan-strict-rules", - "version": "0.10.1", + "version": "0.11", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "18c0b6e8899606b127c680402ab473a7b67166db" + "reference": "747a742b26a35ef4e4ebef5ec4490ad74eebcbc0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/18c0b6e8899606b127c680402ab473a7b67166db", - "reference": "18c0b6e8899606b127c680402ab473a7b67166db", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/747a742b26a35ef4e4ebef5ec4490ad74eebcbc0", + "reference": "747a742b26a35ef4e4ebef5ec4490ad74eebcbc0", "shasum": "" }, "require": { "nikic/php-parser": "^4.0", "php": "~7.1", - "phpstan/phpstan": "^0.10" + "phpstan/phpstan": "^0.11" }, "require-dev": { "consistence/coding-standard": "^3.0.1", "dealerdirect/phpcodesniffer-composer-installer": "^0.4.4", "jakub-onderka/php-parallel-lint": "^1.0", "phing/phing": "^2.16.0", - "phpstan/phpstan-phpunit": "^0.10", + "phpstan/phpstan-phpunit": "^0.11", "phpunit/phpunit": "^7.0", "slevomat/coding-standard": "^4.5.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "0.10-dev" + "dev-master": "0.11-dev" } }, "autoload": { @@ -1644,7 +1877,7 @@ "MIT" ], "description": "Extra strict and opinionated rules for PHPStan", - "time": "2018-07-06T20:36:44+00:00" + "time": "2019-01-14T09:56:55+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1802,16 +2035,16 @@ }, { "name": "phpunit/php-timer", - "version": "2.0.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f" + "reference": "8b389aebe1b8b0578430bda0c7c95a829608e059" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b8454ea6958c3dee38453d3bd571e023108c91f", - "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b389aebe1b8b0578430bda0c7c95a829608e059", + "reference": "8b389aebe1b8b0578430bda0c7c95a829608e059", "shasum": "" }, "require": { @@ -1823,7 +2056,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.1-dev" } }, "autoload": { @@ -1847,7 +2080,7 @@ "keywords": [ "timer" ], - "time": "2018-02-01T13:07:23+00:00" + "time": "2019-02-20T10:12:59+00:00" }, { "name": "phpunit/php-token-stream", @@ -1900,16 +2133,16 @@ }, { "name": "phpunit/phpunit", - "version": "7.5.1", + "version": "7.5.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c23d78776ad415d5506e0679723cb461d71f488f" + "reference": "eb343b86753d26de07ecba7868fa983104361948" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c23d78776ad415d5506e0679723cb461d71f488f", - "reference": "c23d78776ad415d5506e0679723cb461d71f488f", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/eb343b86753d26de07ecba7868fa983104361948", + "reference": "eb343b86753d26de07ecba7868fa983104361948", "shasum": "" }, "require": { @@ -1927,7 +2160,7 @@ "phpunit/php-code-coverage": "^6.0.7", "phpunit/php-file-iterator": "^2.0.1", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.0", + "phpunit/php-timer": "^2.1", "sebastian/comparator": "^3.0", "sebastian/diff": "^3.0", "sebastian/environment": "^4.0", @@ -1980,7 +2213,7 @@ "testing", "xunit" ], - "time": "2018-12-12T07:20:32+00:00" + "time": "2019-03-16T07:31:17+00:00" }, { "name": "psr/cache", @@ -2283,23 +2516,23 @@ }, { "name": "sebastian/diff", - "version": "3.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "366541b989927187c4ca70490a35615d3fef2dce" + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/366541b989927187c4ca70490a35615d3fef2dce", - "reference": "366541b989927187c4ca70490a35615d3fef2dce", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", "shasum": "" }, "require": { "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^7.0", + "phpunit/phpunit": "^7.5 || ^8.0", "symfony/process": "^2 || ^3.3 || ^4" }, "type": "library", @@ -2335,32 +2568,35 @@ "unidiff", "unified diff" ], - "time": "2018-06-10T07:54:39+00:00" + "time": "2019-02-04T06:01:07+00:00" }, { "name": "sebastian/environment", - "version": "4.0.1", + "version": "4.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "febd209a219cea7b56ad799b30ebbea34b71eb8f" + "reference": "6fda8ce1974b62b14935adc02a9ed38252eca656" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/febd209a219cea7b56ad799b30ebbea34b71eb8f", - "reference": "febd209a219cea7b56ad799b30ebbea34b71eb8f", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6fda8ce1974b62b14935adc02a9ed38252eca656", + "reference": "6fda8ce1974b62b14935adc02a9ed38252eca656", "shasum": "" }, "require": { "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^7.4" + "phpunit/phpunit": "^7.5" + }, + "suggest": { + "ext-posix": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -2385,7 +2621,7 @@ "environment", "hhvm" ], - "time": "2018-11-25T09:31:21+00:00" + "time": "2019-02-01T05:27:49+00:00" }, { "name": "sebastian/exporter", @@ -2786,26 +3022,26 @@ }, { "name": "slam/php-cs-fixer-extensions", - "version": "v1.17.0", + "version": "v1.18.0", "source": { "type": "git", "url": "https://github.com/Slamdunk/php-cs-fixer-extensions.git", - "reference": "f2d819ff70cc098f68cd2eefd3279c2f54e0ccad" + "reference": "da18f089d1c559915d3c25d5e8783c7b7d272d1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Slamdunk/php-cs-fixer-extensions/zipball/f2d819ff70cc098f68cd2eefd3279c2f54e0ccad", - "reference": "f2d819ff70cc098f68cd2eefd3279c2f54e0ccad", + "url": "https://api.github.com/repos/Slamdunk/php-cs-fixer-extensions/zipball/da18f089d1c559915d3c25d5e8783c7b7d272d1d", + "reference": "da18f089d1c559915d3c25d5e8783c7b7d272d1d", "shasum": "" }, "require": { - "friendsofphp/php-cs-fixer": "^2.13", + "friendsofphp/php-cs-fixer": "^2.14", "php": "^7.1" }, "require-dev": { "phpstan/phpstan": "^0.10", "phpstan/phpstan-phpunit": "^0.10", - "phpunit/phpunit": "^7.3", + "phpunit/phpunit": "^7.5", "roave/security-advisories": "dev-master", "slam/php-debug-r": "^1.4", "slam/phpstan-extensions": "^2.0", @@ -2829,33 +3065,34 @@ } ], "description": "Slam extension of friendsofphp/php-cs-fixer", - "time": "2018-08-30T15:03:51+00:00" + "time": "2019-01-07T15:02:12+00:00" }, { "name": "slevomat/coding-standard", - "version": "4.8.6", + "version": "5.0.4", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "af0c0c99e84106525484ef25f15144b9831522bb" + "reference": "287ac3347c47918c0bf5e10335e36197ea10894c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/af0c0c99e84106525484ef25f15144b9831522bb", - "reference": "af0c0c99e84106525484ef25f15144b9831522bb", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/287ac3347c47918c0bf5e10335e36197ea10894c", + "reference": "287ac3347c47918c0bf5e10335e36197ea10894c", "shasum": "" }, "require": { "php": "^7.1", - "squizlabs/php_codesniffer": "^3.3.1" + "phpstan/phpdoc-parser": "^0.3.1", + "squizlabs/php_codesniffer": "^3.4.1" }, "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", "phing/phing": "2.16.1", - "phpstan/phpstan": "0.9.2", - "phpstan/phpstan-phpunit": "0.9.4", - "phpstan/phpstan-strict-rules": "0.9", - "phpunit/phpunit": "7.3.5" + "phpstan/phpstan": "0.11.4", + "phpstan/phpstan-phpunit": "0.11", + "phpstan/phpstan-strict-rules": "0.11", + "phpunit/phpunit": "8.0.5" }, "type": "phpcodesniffer-standard", "autoload": { @@ -2868,20 +3105,20 @@ "MIT" ], "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "time": "2018-11-03T21:28:29+00:00" + "time": "2019-03-22T19:10:53+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.3.2", + "version": "3.4.1", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e" + "reference": "5b4333b4010625d29580eb4a41f1e53251be6baa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/6ad28354c04b364c3c71a34e4a18b629cc3b231e", - "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5b4333b4010625d29580eb4a41f1e53251be6baa", + "reference": "5b4333b4010625d29580eb4a41f1e53251be6baa", "shasum": "" }, "require": { @@ -2914,25 +3151,25 @@ } ], "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "http://www.squizlabs.com/php-codesniffer", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", "keywords": [ "phpcs", "standards" ], - "time": "2018-09-23T23:08:17+00:00" + "time": "2019-03-19T03:22:27+00:00" }, { "name": "symfony/cache", - "version": "v4.2.1", + "version": "v4.2.4", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "5c4b50d6ba4f1c8955c3454444c1e3cfddaaad41" + "reference": "b5c650406953f2f44a37c4f3ac66152fafc22c66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/5c4b50d6ba4f1c8955c3454444c1e3cfddaaad41", - "reference": "5c4b50d6ba4f1c8955c3454444c1e3cfddaaad41", + "url": "https://api.github.com/repos/symfony/cache/zipball/b5c650406953f2f44a37c4f3ac66152fafc22c66", + "reference": "b5c650406953f2f44a37c4f3ac66152fafc22c66", "shasum": "" }, "require": { @@ -2996,20 +3233,20 @@ "caching", "psr6" ], - "time": "2018-12-06T11:00:08+00:00" + "time": "2019-02-23T15:17:42+00:00" }, { "name": "symfony/config", - "version": "v4.2.1", + "version": "v4.2.4", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "005d9a083d03f588677d15391a716b1ac9b887c0" + "reference": "7f70d79c7a24a94f8e98abb988049403a53d7b31" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/005d9a083d03f588677d15391a716b1ac9b887c0", - "reference": "005d9a083d03f588677d15391a716b1ac9b887c0", + "url": "https://api.github.com/repos/symfony/config/zipball/7f70d79c7a24a94f8e98abb988049403a53d7b31", + "reference": "7f70d79c7a24a94f8e98abb988049403a53d7b31", "shasum": "" }, "require": { @@ -3059,20 +3296,20 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2018-11-30T22:21:14+00:00" + "time": "2019-02-23T15:17:42+00:00" }, { "name": "symfony/console", - "version": "v4.2.1", + "version": "v4.2.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "4dff24e5d01e713818805c1862d2e3f901ee7dd0" + "reference": "9dc2299a016497f9ee620be94524e6c0af0280a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/4dff24e5d01e713818805c1862d2e3f901ee7dd0", - "reference": "4dff24e5d01e713818805c1862d2e3f901ee7dd0", + "url": "https://api.github.com/repos/symfony/console/zipball/9dc2299a016497f9ee620be94524e6c0af0280a9", + "reference": "9dc2299a016497f9ee620be94524e6c0af0280a9", "shasum": "" }, "require": { @@ -3084,6 +3321,9 @@ "symfony/dependency-injection": "<3.4", "symfony/process": "<3.3" }, + "provide": { + "psr/log-implementation": "1.0" + }, "require-dev": { "psr/log": "~1.0", "symfony/config": "~3.4|~4.0", @@ -3093,7 +3333,7 @@ "symfony/process": "~3.4|~4.0" }, "suggest": { - "psr/log-implementation": "For using the console logger", + "psr/log": "For using the console logger", "symfony/event-dispatcher": "", "symfony/lock": "", "symfony/process": "" @@ -3128,7 +3368,7 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-11-27T07:40:44+00:00" + "time": "2019-02-23T15:17:42+00:00" }, { "name": "symfony/contracts", @@ -3200,16 +3440,16 @@ }, { "name": "symfony/debug", - "version": "v4.2.1", + "version": "v4.2.4", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "e0a2b92ee0b5b934f973d90c2f58e18af109d276" + "reference": "de73f48977b8eaf7ce22814d66e43a1662cc864f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/e0a2b92ee0b5b934f973d90c2f58e18af109d276", - "reference": "e0a2b92ee0b5b934f973d90c2f58e18af109d276", + "url": "https://api.github.com/repos/symfony/debug/zipball/de73f48977b8eaf7ce22814d66e43a1662cc864f", + "reference": "de73f48977b8eaf7ce22814d66e43a1662cc864f", "shasum": "" }, "require": { @@ -3252,20 +3492,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2018-11-28T18:24:18+00:00" + "time": "2019-03-03T18:11:24+00:00" }, { "name": "symfony/dependency-injection", - "version": "v4.2.1", + "version": "v4.2.4", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "e4adc57a48d3fa7f394edfffa9e954086d7740e5" + "reference": "cdadb3765df7c89ac93628743913b92bb91f1704" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e4adc57a48d3fa7f394edfffa9e954086d7740e5", - "reference": "e4adc57a48d3fa7f394edfffa9e954086d7740e5", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/cdadb3765df7c89ac93628743913b92bb91f1704", + "reference": "cdadb3765df7c89ac93628743913b92bb91f1704", "shasum": "" }, "require": { @@ -3325,20 +3565,20 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2018-12-02T15:59:36+00:00" + "time": "2019-02-23T15:17:42+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.2.1", + "version": "v4.2.4", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "921f49c3158a276d27c0d770a5a347a3b718b328" + "reference": "3354d2e6af986dd71f68b4e5cf4a933ab58697fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/921f49c3158a276d27c0d770a5a347a3b718b328", - "reference": "921f49c3158a276d27c0d770a5a347a3b718b328", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/3354d2e6af986dd71f68b4e5cf4a933ab58697fb", + "reference": "3354d2e6af986dd71f68b4e5cf4a933ab58697fb", "shasum": "" }, "require": { @@ -3389,20 +3629,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2018-12-01T08:52:38+00:00" + "time": "2019-02-23T15:17:42+00:00" }, { "name": "symfony/filesystem", - "version": "v4.2.1", + "version": "v4.2.4", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "2f4c8b999b3b7cadb2a69390b01af70886753710" + "reference": "e16b9e471703b2c60b95f14d31c1239f68f11601" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/2f4c8b999b3b7cadb2a69390b01af70886753710", - "reference": "2f4c8b999b3b7cadb2a69390b01af70886753710", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/e16b9e471703b2c60b95f14d31c1239f68f11601", + "reference": "e16b9e471703b2c60b95f14d31c1239f68f11601", "shasum": "" }, "require": { @@ -3439,20 +3679,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-11-11T19:52:12+00:00" + "time": "2019-02-07T11:40:08+00:00" }, { "name": "symfony/finder", - "version": "v4.2.1", + "version": "v4.2.4", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "e53d477d7b5c4982d0e1bfd2298dbee63d01441d" + "reference": "267b7002c1b70ea80db0833c3afe05f0fbde580a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/e53d477d7b5c4982d0e1bfd2298dbee63d01441d", - "reference": "e53d477d7b5c4982d0e1bfd2298dbee63d01441d", + "url": "https://api.github.com/repos/symfony/finder/zipball/267b7002c1b70ea80db0833c3afe05f0fbde580a", + "reference": "267b7002c1b70ea80db0833c3afe05f0fbde580a", "shasum": "" }, "require": { @@ -3488,20 +3728,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-11-11T19:52:12+00:00" + "time": "2019-02-23T15:42:05+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.2.1", + "version": "v4.2.4", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "1b31f3017fadd8cb05cf2c8aebdbf3b12a943851" + "reference": "850a667d6254ccf6c61d853407b16f21c4579c77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/1b31f3017fadd8cb05cf2c8aebdbf3b12a943851", - "reference": "1b31f3017fadd8cb05cf2c8aebdbf3b12a943851", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/850a667d6254ccf6c61d853407b16f21c4579c77", + "reference": "850a667d6254ccf6c61d853407b16f21c4579c77", "shasum": "" }, "require": { @@ -3542,20 +3782,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2018-11-26T10:55:26+00:00" + "time": "2019-02-26T08:03:39+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.2.1", + "version": "v4.2.4", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "b39ceffc0388232c309cbde3a7c3685f2ec0a624" + "reference": "895ceccaa8149f9343e6134e607c21da42d73b7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b39ceffc0388232c309cbde3a7c3685f2ec0a624", - "reference": "b39ceffc0388232c309cbde3a7c3685f2ec0a624", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/895ceccaa8149f9343e6134e607c21da42d73b7a", + "reference": "895ceccaa8149f9343e6134e607c21da42d73b7a", "shasum": "" }, "require": { @@ -3631,20 +3871,20 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2018-12-06T17:39:52+00:00" + "time": "2019-03-03T19:38:09+00:00" }, { "name": "symfony/options-resolver", - "version": "v4.2.1", + "version": "v4.2.4", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "a9c38e8a3da2c03b3e71fdffa6efb0bda51390ba" + "reference": "3896e5a7d06fd15fa4947694c8dcdd371ff147d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/a9c38e8a3da2c03b3e71fdffa6efb0bda51390ba", - "reference": "a9c38e8a3da2c03b3e71fdffa6efb0bda51390ba", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/3896e5a7d06fd15fa4947694c8dcdd371ff147d1", + "reference": "3896e5a7d06fd15fa4947694c8dcdd371ff147d1", "shasum": "" }, "require": { @@ -3685,20 +3925,20 @@ "configuration", "options" ], - "time": "2018-11-11T19:52:12+00:00" + "time": "2019-02-23T15:17:42+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" + "reference": "82ebae02209c21113908c229e9883c419720738a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", + "reference": "82ebae02209c21113908c229e9883c419720738a", "shasum": "" }, "require": { @@ -3710,7 +3950,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -3743,20 +3983,20 @@ "polyfill", "portable" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494" + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", "shasum": "" }, "require": { @@ -3768,7 +4008,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -3802,20 +4042,20 @@ "portable", "shim" ], - "time": "2018-09-21T13:07:52+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/polyfill-php70", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "6b88000cdd431cd2e940caa2cb569201f3f84224" + "reference": "bc4858fb611bda58719124ca079baff854149c89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/6b88000cdd431cd2e940caa2cb569201f3f84224", - "reference": "6b88000cdd431cd2e940caa2cb569201f3f84224", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/bc4858fb611bda58719124ca079baff854149c89", + "reference": "bc4858fb611bda58719124ca079baff854149c89", "shasum": "" }, "require": { @@ -3825,7 +4065,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -3861,20 +4101,20 @@ "portable", "shim" ], - "time": "2018-09-21T06:26:08+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "9050816e2ca34a8e916c3a0ae8b9c2fccf68b631" + "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9050816e2ca34a8e916c3a0ae8b9c2fccf68b631", - "reference": "9050816e2ca34a8e916c3a0ae8b9c2fccf68b631", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/ab50dcf166d5f577978419edd37aa2bb8eabce0c", + "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c", "shasum": "" }, "require": { @@ -3883,7 +4123,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -3916,20 +4156,20 @@ "portable", "shim" ], - "time": "2018-09-21T13:07:52+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/process", - "version": "v4.2.1", + "version": "v4.2.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "2b341009ccec76837a7f46f59641b431e4d4c2b0" + "reference": "6c05edb11fbeff9e2b324b4270ecb17911a8b7ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/2b341009ccec76837a7f46f59641b431e4d4c2b0", - "reference": "2b341009ccec76837a7f46f59641b431e4d4c2b0", + "url": "https://api.github.com/repos/symfony/process/zipball/6c05edb11fbeff9e2b324b4270ecb17911a8b7ad", + "reference": "6c05edb11fbeff9e2b324b4270ecb17911a8b7ad", "shasum": "" }, "require": { @@ -3965,20 +4205,20 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-11-20T16:22:05+00:00" + "time": "2019-01-24T22:05:03+00:00" }, { "name": "symfony/stopwatch", - "version": "v4.2.1", + "version": "v4.2.4", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "ec076716412274e51f8a7ea675d9515e5c311123" + "reference": "b1a5f646d56a3290230dbc8edf2a0d62cda23f67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/ec076716412274e51f8a7ea675d9515e5c311123", - "reference": "ec076716412274e51f8a7ea675d9515e5c311123", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/b1a5f646d56a3290230dbc8edf2a0d62cda23f67", + "reference": "b1a5f646d56a3290230dbc8edf2a0d62cda23f67", "shasum": "" }, "require": { @@ -4015,20 +4255,93 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2018-11-11T19:52:12+00:00" + "time": "2019-01-16T20:31:39+00:00" + }, + { + "name": "symfony/translation", + "version": "v4.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "748464177a77011f8f4cdd076773862ce4915f8f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/748464177a77011f8f4cdd076773862ce4915f8f", + "reference": "748464177a77011f8f4cdd076773862ce4915f8f", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/contracts": "^1.0.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/config": "<3.4", + "symfony/dependency-injection": "<3.4", + "symfony/yaml": "<3.4" + }, + "provide": { + "symfony/translation-contracts-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/console": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/finder": "~2.8|~3.0|~4.0", + "symfony/intl": "~3.4|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "psr/log-implementation": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Translation Component", + "homepage": "https://symfony.com", + "time": "2019-02-27T03:31:50+00:00" }, { "name": "symfony/var-exporter", - "version": "v4.2.1", + "version": "v4.2.4", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "a39222e357362424b61dcde50e2f7b5a7d3306db" + "reference": "d8bf4424c232b55f4c1816037d3077a89258557e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/a39222e357362424b61dcde50e2f7b5a7d3306db", - "reference": "a39222e357362424b61dcde50e2f7b5a7d3306db", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/d8bf4424c232b55f4c1816037d3077a89258557e", + "reference": "d8bf4424c232b55f4c1816037d3077a89258557e", "shasum": "" }, "require": { @@ -4075,20 +4388,20 @@ "instantiate", "serialize" ], - "time": "2018-12-03T22:40:09+00:00" + "time": "2019-01-16T20:35:37+00:00" }, { "name": "symfony/yaml", - "version": "v4.2.1", + "version": "v4.2.4", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "c41175c801e3edfda90f32e292619d10c27103d7" + "reference": "761fa560a937fd7686e5274ff89dcfa87a5047df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/c41175c801e3edfda90f32e292619d10c27103d7", - "reference": "c41175c801e3edfda90f32e292619d10c27103d7", + "url": "https://api.github.com/repos/symfony/yaml/zipball/761fa560a937fd7686e5274ff89dcfa87a5047df", + "reference": "761fa560a937fd7686e5274ff89dcfa87a5047df", "shasum": "" }, "require": { @@ -4134,41 +4447,41 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2018-11-11T19:52:12+00:00" + "time": "2019-02-23T15:17:42+00:00" }, { "name": "symplify/better-phpdoc-parser", - "version": "v5.2.17", + "version": "v5.4.16", "source": { "type": "git", "url": "https://github.com/Symplify/BetterPhpDocParser.git", - "reference": "cbdae4a58aa36016160a14ff9e4283fb8169448c" + "reference": "a730f69c4b19c741f13b4d05116da7bb64e3db26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/BetterPhpDocParser/zipball/cbdae4a58aa36016160a14ff9e4283fb8169448c", - "reference": "cbdae4a58aa36016160a14ff9e4283fb8169448c", + "url": "https://api.github.com/repos/Symplify/BetterPhpDocParser/zipball/a730f69c4b19c741f13b4d05116da7bb64e3db26", + "reference": "a730f69c4b19c741f13b4d05116da7bb64e3db26", "shasum": "" }, "require": { "nette/utils": "^2.5", "php": "^7.1", - "phpstan/phpdoc-parser": "^0.3", - "symplify/package-builder": "^5.2.17", - "thecodingmachine/safe": "^0.1.6" + "phpstan/phpdoc-parser": "^0.3.1", + "symplify/package-builder": "^5.4.16" }, "require-dev": { - "phpunit/phpunit": "^7.3" + "phpunit/phpunit": "^7.5|^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.3-dev" + "dev-master": "5.5-dev" } }, "autoload": { "psr-4": { - "Symplify\\BetterPhpDocParser\\": "src" + "Symplify\\BetterPhpDocParser\\": "src", + "Symplify\\BetterPhpDocParser\\Attributes\\": "packages/Attributes/src" } }, "notification-url": "https://packagist.org/downloads/", @@ -4176,47 +4489,48 @@ "MIT" ], "description": "Slim wrapper around phpstan/phpdoc-parser with format preserving printer", - "time": "2018-12-12T11:50:57+00:00" + "time": "2019-03-05T23:15:04+00:00" }, { "name": "symplify/coding-standard", - "version": "v5.2.17", + "version": "v5.4.16", "source": { "type": "git", "url": "https://github.com/Symplify/CodingStandard.git", - "reference": "7e73f270bd3b41931f63b767956354f0236c45cc" + "reference": "72a3b03f21be6c978a90ad567a29bd9261df0dfa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/CodingStandard/zipball/7e73f270bd3b41931f63b767956354f0236c45cc", - "reference": "7e73f270bd3b41931f63b767956354f0236c45cc", + "url": "https://api.github.com/repos/Symplify/CodingStandard/zipball/72a3b03f21be6c978a90ad567a29bd9261df0dfa", + "reference": "72a3b03f21be6c978a90ad567a29bd9261df0dfa", "shasum": "" }, "require": { - "friendsofphp/php-cs-fixer": "^2.13", + "friendsofphp/php-cs-fixer": "^2.14", "nette/finder": "^2.4", "nette/utils": "^2.5", "php": "^7.1", "slam/php-cs-fixer-extensions": "^1.17", - "squizlabs/php_codesniffer": "^3.3", - "symplify/token-runner": "^5.2.17", - "thecodingmachine/safe": "^0.1.6" + "squizlabs/php_codesniffer": "^3.4", + "symplify/better-phpdoc-parser": "^5.4.16", + "symplify/package-builder": "^5.4.16" }, "require-dev": { "nette/application": "^2.4", - "phpunit/phpunit": "^7.3", - "symplify/easy-coding-standard-tester": "^5.2.17", - "symplify/package-builder": "^5.2.17" + "phpunit/phpunit": "^7.5|^8.0", + "symplify/easy-coding-standard-tester": "^5.4.16", + "symplify/package-builder": "^5.4.16" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.3-dev" + "dev-master": "5.5-dev" } }, "autoload": { "psr-4": { - "Symplify\\CodingStandard\\": "src" + "Symplify\\CodingStandard\\": "src", + "Symplify\\CodingStandard\\TokenRunner\\": "packages/TokenRunner/src" } }, "notification-url": "https://packagist.org/downloads/", @@ -4224,32 +4538,32 @@ "MIT" ], "description": "Set of Symplify rules for PHP_CodeSniffer and PHP CS Fixer.", - "time": "2018-12-12T11:50:57+00:00" + "time": "2019-03-05T23:15:04+00:00" }, { "name": "symplify/easy-coding-standard", - "version": "v5.2.17", + "version": "v5.4.16", "source": { "type": "git", "url": "https://github.com/Symplify/EasyCodingStandard.git", - "reference": "e63f0d3d465104d730016e51aeac39cc73485c55" + "reference": "66ed360e0b81881336c7339989dce3b0c14509e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/EasyCodingStandard/zipball/e63f0d3d465104d730016e51aeac39cc73485c55", - "reference": "e63f0d3d465104d730016e51aeac39cc73485c55", + "url": "https://api.github.com/repos/Symplify/EasyCodingStandard/zipball/66ed360e0b81881336c7339989dce3b0c14509e9", + "reference": "66ed360e0b81881336c7339989dce3b0c14509e9", "shasum": "" }, "require": { "composer/xdebug-handler": "^1.3", - "friendsofphp/php-cs-fixer": "^2.13", + "friendsofphp/php-cs-fixer": "^2.14", "jean85/pretty-package-versions": "^1.2", "nette/robot-loader": "^3.1.0", "nette/utils": "^2.5", "ocramius/package-versions": "^1.3", "php": "^7.1", - "slevomat/coding-standard": "^4.7", - "squizlabs/php_codesniffer": "^3.3", + "slevomat/coding-standard": "^5.0.1", + "squizlabs/php_codesniffer": "^3.4", "symfony/cache": "^3.4|^4.1", "symfony/config": "^3.4|^4.1", "symfony/console": "^3.4|^4.1", @@ -4257,14 +4571,12 @@ "symfony/finder": "^3.4|^4.1", "symfony/http-kernel": "^3.4|^4.1", "symfony/yaml": "^3.4|^4.1", - "symplify/coding-standard": "^5.2.17", - "symplify/package-builder": "^5.2.17", - "symplify/token-runner": "^5.2.17", - "thecodingmachine/safe": "^0.1.6" + "symplify/coding-standard": "^5.4.16", + "symplify/package-builder": "^5.4.16" }, "require-dev": { - "phpunit/phpunit": "^7.3", - "symplify/easy-coding-standard-tester": "^5.2.17" + "phpunit/phpunit": "^7.5|^8.0", + "symplify/easy-coding-standard-tester": "^5.4.16" }, "bin": [ "bin/ecs" @@ -4272,7 +4584,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.3-dev" + "dev-master": "5.5-dev" } }, "autoload": { @@ -4289,23 +4601,24 @@ "MIT" ], "description": "Use Coding Standard with 0-knowledge of PHP-CS-Fixer and PHP_CodeSniffer.", - "time": "2018-12-12T11:50:57+00:00" + "time": "2019-03-05T23:15:04+00:00" }, { "name": "symplify/package-builder", - "version": "v5.2.17", + "version": "v5.4.16", "source": { "type": "git", "url": "https://github.com/Symplify/PackageBuilder.git", - "reference": "4a5a771e719aeecedea2d1e631a8f6be976c7130" + "reference": "20e04ad9cd15a53527807a62c8b244d8a114f779" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/PackageBuilder/zipball/4a5a771e719aeecedea2d1e631a8f6be976c7130", - "reference": "4a5a771e719aeecedea2d1e631a8f6be976c7130", + "url": "https://api.github.com/repos/Symplify/PackageBuilder/zipball/20e04ad9cd15a53527807a62c8b244d8a114f779", + "reference": "20e04ad9cd15a53527807a62c8b244d8a114f779", "shasum": "" }, "require": { + "illuminate/support": "^5.7", "nette/finder": "^2.4", "nette/utils": "^2.5", "php": "^7.1", @@ -4315,16 +4628,15 @@ "symfony/dependency-injection": "^3.4|^4.1", "symfony/finder": "^3.4|^4.1", "symfony/http-kernel": "^3.4|^4.1", - "symfony/yaml": "^3.4|^4.1", - "thecodingmachine/safe": "^0.1.6" + "symfony/yaml": "^3.4|^4.1" }, "require-dev": { - "phpunit/phpunit": "^7.3" + "phpunit/phpunit": "^7.5|^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.3-dev" + "dev-master": "5.5-dev" } }, "autoload": { @@ -4337,182 +4649,7 @@ "MIT" ], "description": "Dependency Injection, Console and Kernel toolkit for Symplify packages.", - "time": "2018-12-12T11:50:51+00:00" - }, - { - "name": "symplify/token-runner", - "version": "v5.2.17", - "source": { - "type": "git", - "url": "https://github.com/Symplify/TokenRunner.git", - "reference": "b6e2657ac326b4944d162d5c6020056bc69766b8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Symplify/TokenRunner/zipball/b6e2657ac326b4944d162d5c6020056bc69766b8", - "reference": "b6e2657ac326b4944d162d5c6020056bc69766b8", - "shasum": "" - }, - "require": { - "friendsofphp/php-cs-fixer": "^2.13", - "nette/finder": "^2.4", - "nette/utils": "^2.5", - "php": "^7.1", - "squizlabs/php_codesniffer": "^3.3", - "symplify/better-phpdoc-parser": "^5.2.17", - "symplify/package-builder": "^5.2.17", - "thecodingmachine/safe": "^0.1.6" - }, - "require-dev": { - "phpunit/phpunit": "^7.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.3-dev" - } - }, - "autoload": { - "psr-4": { - "Symplify\\TokenRunner\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Set of utils for PHP_CodeSniffer and PHP CS Fixer.", - "time": "2018-12-12T11:50:57+00:00" - }, - { - "name": "thecodingmachine/safe", - "version": "v0.1.8", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/safe.git", - "reference": "4547d4684086d463b00cbd1a7763395280355e7d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/4547d4684086d463b00cbd1a7763395280355e7d", - "reference": "4547d4684086d463b00cbd1a7763395280355e7d", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "phpstan/phpstan": "^0.10.3", - "squizlabs/php_codesniffer": "^3.2", - "thecodingmachine/phpstan-strict-rules": "^0.10.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.1-dev" - } - }, - "autoload": { - "psr-4": { - "Safe\\": [ - "lib/", - "generated/" - ] - }, - "files": [ - "generated/apache.php", - "generated/apc.php", - "generated/apcu.php", - "generated/array.php", - "generated/bzip2.php", - "generated/classobj.php", - "generated/com.php", - "generated/cubrid.php", - "generated/curl.php", - "generated/datetime.php", - "generated/dir.php", - "generated/eio.php", - "generated/errorfunc.php", - "generated/exec.php", - "generated/fileinfo.php", - "generated/filesystem.php", - "generated/filter.php", - "generated/fpm.php", - "generated/ftp.php", - "generated/funchand.php", - "generated/gmp.php", - "generated/gnupg.php", - "generated/hash.php", - "generated/ibase.php", - "generated/ibmDb2.php", - "generated/iconv.php", - "generated/image.php", - "generated/imap.php", - "generated/info.php", - "generated/ingres-ii.php", - "generated/inotify.php", - "generated/json.php", - "generated/ldap.php", - "generated/libevent.php", - "generated/libxml.php", - "generated/lzf.php", - "generated/mailparse.php", - "generated/mbstring.php", - "generated/misc.php", - "generated/msql.php", - "generated/mssql.php", - "generated/mysql.php", - "generated/mysqlndMs.php", - "generated/mysqlndQc.php", - "generated/network.php", - "generated/oci8.php", - "generated/opcache.php", - "generated/openssl.php", - "generated/outcontrol.php", - "generated/password.php", - "generated/pcntl.php", - "generated/pcre.php", - "generated/pdf.php", - "generated/pgsql.php", - "generated/posix.php", - "generated/ps.php", - "generated/pspell.php", - "generated/readline.php", - "generated/rrd.php", - "generated/sem.php", - "generated/session.php", - "generated/shmop.php", - "generated/simplexml.php", - "generated/sockets.php", - "generated/sodium.php", - "generated/solr.php", - "generated/spl.php", - "generated/sqlsrv.php", - "generated/ssh2.php", - "generated/stats.php", - "generated/stream.php", - "generated/strings.php", - "generated/swoole.php", - "generated/uodbc.php", - "generated/uopz.php", - "generated/url.php", - "generated/var.php", - "generated/xdiff.php", - "generated/xml.php", - "generated/xmlrpc.php", - "generated/yaml.php", - "generated/yaz.php", - "generated/zip.php", - "generated/zlib.php", - "lib/special_cases.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHP core functions that throw exceptions instead of returning FALSE on error", - "time": "2018-11-13T09:01:03+00:00" + "time": "2019-03-03T15:32:34+00:00" }, { "name": "theseer/tokenizer", @@ -4556,20 +4693,21 @@ }, { "name": "webmozart/assert", - "version": "1.3.0", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a" + "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a", + "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9", + "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": "^5.3.3 || ^7.0", + "symfony/polyfill-ctype": "^1.8" }, "require-dev": { "phpunit/phpunit": "^4.6", @@ -4602,7 +4740,7 @@ "check", "validate" ], - "time": "2018-01-29T19:49:41+00:00" + "time": "2018-12-25T11:19:39+00:00" } ], "aliases": [], diff --git a/ecs.yml b/ecs.yml index 405ef369..b19571e5 100644 --- a/ecs.yml +++ b/ecs.yml @@ -30,14 +30,14 @@ services: Symplify\CodingStandard\Fixer\ArrayNotation\StandaloneLineInMultilineArrayFixer: ~ parameters: - exclude_checkers: + skip: # from strict.neon - - 'PhpCsFixer\Fixer\PhpUnit\PhpUnitStrictFixer' - - 'PhpCsFixer\Fixer\Strict\StrictComparisonFixer' + PhpCsFixer\Fixer\PhpUnit\PhpUnitStrictFixer: ~ + PhpCsFixer\Fixer\Strict\StrictComparisonFixer: ~ + # personal prefference - - 'PhpCsFixer\Fixer\Operator\NotOperatorWithSuccessorSpaceFixer' + PhpCsFixer\Fixer\Operator\NotOperatorWithSuccessorSpaceFixer: ~ - skip: PhpCsFixer\Fixer\Alias\RandomApiMigrationFixer: # random_int() breaks code - 'src/CrossValidation/RandomSplit.php' @@ -65,4 +65,4 @@ parameters: SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingTraversablePropertyTypeHintSpecification: ~ # assignment in "while ($var = ...)" are ok - PHP_CodeSniffer\Standards\Generic\Sniffs\CodeAnalysis\AssignmentInConditionSniff.FoundInWhileCondition: \ No newline at end of file + PHP_CodeSniffer\Standards\Generic\Sniffs\CodeAnalysis\AssignmentInConditionSniff.FoundInWhileCondition: diff --git a/phpstan.neon b/phpstan.neon index 0ee43c49..c2abfaf9 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -7,9 +7,8 @@ parameters: ignoreErrors: - '#Property Phpml\\Clustering\\KMeans\\Cluster\:\:\$points \(iterable\\&SplObjectStorage\) does not accept SplObjectStorage#' - '#Phpml\\Dataset\\(.*)Dataset::__construct\(\) does not call parent constructor from Phpml\\Dataset\\ArrayDataset#' - - # wide range cases - - '#Parameter \#1 \$coordinates of class Phpml\\Clustering\\KMeans\\Point constructor expects array, array\|Phpml\\Clustering\\KMeans\\Point given#' + - '#Variable property access on .+#' + - '#Variable method call on .+#' # probably known value - '#Method Phpml\\Classification\\DecisionTree::getBestSplit\(\) should return Phpml\\Classification\\DecisionTree\\DecisionTreeLeaf but returns Phpml\\Classification\\DecisionTree\\DecisionTreeLeaf\|null#' diff --git a/src/Classification/Ensemble/RandomForest.php b/src/Classification/Ensemble/RandomForest.php index b75d7ae3..71ea8d1e 100644 --- a/src/Classification/Ensemble/RandomForest.php +++ b/src/Classification/Ensemble/RandomForest.php @@ -41,7 +41,7 @@ public function __construct(int $numClassifier = 50) * Default value for the ratio is 'log' which results in log(numFeatures, 2) + 1 * features to be taken into consideration while selecting subspace of features * - * @param string|float $ratio + * @param mixed $ratio */ public function setFeatureSubsetRatio($ratio): self { @@ -73,7 +73,9 @@ public function setClassifer(string $classifier, array $classifierOptions = []) throw new InvalidArgumentException('RandomForest can only use DecisionTree as base classifier'); } - return parent::setClassifer($classifier, $classifierOptions); + parent::setClassifer($classifier, $classifierOptions); + + return $this; } /** @@ -122,12 +124,16 @@ public function setColumnNames(array $names) } /** - * @param DecisionTree $classifier - * * @return DecisionTree */ protected function initSingleClassifier(Classifier $classifier): Classifier { + if (!$classifier instanceof DecisionTree) { + throw new InvalidArgumentException( + sprintf('Classifier %s expected, got %s', DecisionTree::class, get_class($classifier)) + ); + } + if (is_float($this->featureSubsetRatio)) { $featureCount = (int) ($this->featureSubsetRatio * $this->featureCount); } elseif ($this->featureSubsetRatio === 'sqrt') { diff --git a/src/Classification/Linear/LogisticRegression.php b/src/Classification/Linear/LogisticRegression.php index 8ab69394..889ea981 100644 --- a/src/Classification/Linear/LogisticRegression.php +++ b/src/Classification/Linear/LogisticRegression.php @@ -226,7 +226,7 @@ protected function getCostFunction(): Closure $y = $y < 0 ? 0 : 1; - $error = ($y - $hX) ** 2; + $error = (($y - $hX) ** 2); $gradient = -($y - $hX) * $hX * (1 - $hX); return [$error, $gradient, $penalty]; diff --git a/src/Clustering/FuzzyCMeans.php b/src/Clustering/FuzzyCMeans.php index 3637ef65..abc53f17 100644 --- a/src/Clustering/FuzzyCMeans.php +++ b/src/Clustering/FuzzyCMeans.php @@ -77,9 +77,6 @@ public function getMembershipMatrix(): array return $this->membership; } - /** - * @param Point[]|int[][] $samples - */ public function cluster(array $samples): array { // Initialize variables, clusters and membership matrix @@ -210,7 +207,7 @@ protected function getDistanceCalc(int $row, int $col): float $this->samples[$col] ); - $val = ($dist1 / $dist2) ** 2.0 / ($this->fuzziness - 1); + $val = (($dist1 / $dist2) ** 2.0) / ($this->fuzziness - 1); $sum += $val; } diff --git a/src/Dataset/SvmDataset.php b/src/Dataset/SvmDataset.php index 334ec6c2..4ac951c6 100644 --- a/src/Dataset/SvmDataset.php +++ b/src/Dataset/SvmDataset.php @@ -24,7 +24,7 @@ private static function readProblem(string $filePath): array $targets = []; $maxIndex = 0; while (false !== $line = fgets($handle)) { - [$sample, $target, $maxIndex] = self::processLine((string) $line, $maxIndex); + [$sample, $target, $maxIndex] = self::processLine($line, $maxIndex); $samples[] = $sample; $targets[] = $target; } diff --git a/src/FeatureSelection/ScoringFunction/UnivariateLinearRegression.php b/src/FeatureSelection/ScoringFunction/UnivariateLinearRegression.php index 18d0ba9b..13bdc991 100644 --- a/src/FeatureSelection/ScoringFunction/UnivariateLinearRegression.php +++ b/src/FeatureSelection/ScoringFunction/UnivariateLinearRegression.php @@ -46,7 +46,7 @@ public function score(array $samples, array $targets): array foreach (array_keys($samples[0]) as $index) { $featureColumn = array_column($samples, $index); $correlations[$index] = - (Matrix::dot($targets, $featureColumn)[0] / (new Matrix($featureColumn, false))->transpose()->frobeniusNorm()) + Matrix::dot($targets, $featureColumn)[0] / (new Matrix($featureColumn, false))->transpose()->frobeniusNorm() / (new Matrix($targets, false))->frobeniusNorm(); } diff --git a/src/Math/Kernel/RBF.php b/src/Math/Kernel/RBF.php index 4f9cfaf6..bf22d6f3 100644 --- a/src/Math/Kernel/RBF.php +++ b/src/Math/Kernel/RBF.php @@ -4,6 +4,7 @@ namespace Phpml\Math\Kernel; +use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Kernel; use Phpml\Math\Product; @@ -19,12 +20,12 @@ public function __construct(float $gamma) $this->gamma = $gamma; } - /** - * @param array $a - * @param array $b - */ public function compute($a, $b): float { + if (!is_array($a) || !is_array($b)) { + throw new InvalidArgumentException(sprintf('Arguments of %s must be arrays', __METHOD__)); + } + $score = 2 * Product::scalar($a, $b); $squares = Product::scalar($a, $a) + Product::scalar($b, $b); diff --git a/src/Math/LinearAlgebra/EigenvalueDecomposition.php b/src/Math/LinearAlgebra/EigenvalueDecomposition.php index cc688640..94f0a9e8 100644 --- a/src/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -502,7 +502,8 @@ private function orthes(): void } // Double division avoids possible underflow - $g = ($g / $this->ort[$m]) / $this->H[$m][$m - 1]; + $g /= $this->ort[$m]; + $g /= $this->H[$m][$m - 1]; for ($i = $m; $i <= $high; ++$i) { $this->V[$i][$j] += $g * $this->ort[$i]; } @@ -734,7 +735,7 @@ private function hqr2(): void // Double QR step involving rows l:n and columns m:n for ($k = $m; $k <= $n - 1; ++$k) { - $notlast = ($k != $n - 1); + $notlast = $k != $n - 1; if ($k != $m) { $p = $this->H[$k][$k - 1]; $q = $this->H[$k + 1][$k - 1]; diff --git a/src/NeuralNetwork/Network/MultilayerPerceptron.php b/src/NeuralNetwork/Network/MultilayerPerceptron.php index 7fe08e14..e9e6b516 100644 --- a/src/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/NeuralNetwork/Network/MultilayerPerceptron.php @@ -59,8 +59,14 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, /** * @throws InvalidArgumentException */ - public function __construct(int $inputLayerFeatures, array $hiddenLayers, array $classes, int $iterations = 10000, ?ActivationFunction $activationFunction = null, float $learningRate = 1) - { + public function __construct( + int $inputLayerFeatures, + array $hiddenLayers, + array $classes, + int $iterations = 10000, + ?ActivationFunction $activationFunction = null, + float $learningRate = 1. + ) { if (count($hiddenLayers) === 0) { throw new InvalidArgumentException('Provide at least 1 hidden layer'); } diff --git a/tests/Helper/Optimizer/ConjugateGradientTest.php b/tests/Helper/Optimizer/ConjugateGradientTest.php index 2080019a..230900f4 100644 --- a/tests/Helper/Optimizer/ConjugateGradientTest.php +++ b/tests/Helper/Optimizer/ConjugateGradientTest.php @@ -23,7 +23,7 @@ public function testRunOptimization(): void $callback = function ($theta, $sample, $target) { $y = $theta[0] + $theta[1] * $sample[0]; - $cost = ($y - $target) ** 2 / 2; + $cost = (($y - $target) ** 2) / 2; $grad = $y - $target; return [$cost, $grad]; @@ -49,7 +49,7 @@ public function testRunOptimizationWithCustomInitialTheta(): void $callback = function ($theta, $sample, $target) { $y = $theta[0] + $theta[1] * $sample[0]; - $cost = ($y - $target) ** 2 / 2; + $cost = (($y - $target) ** 2) / 2; $grad = $y - $target; return [$cost, $grad]; @@ -78,7 +78,7 @@ public function testRunOptimization2Dim(): void $callback = function ($theta, $sample, $target) { $y = $theta[0] + $theta[1] * $sample[0] + $theta[2] * $sample[1]; - $cost = ($y - $target) ** 2 / 2; + $cost = (($y - $target) ** 2) / 2; $grad = $y - $target; return [$cost, $grad]; diff --git a/tests/Helper/Optimizer/GDTest.php b/tests/Helper/Optimizer/GDTest.php index ecbd5152..548a74be 100644 --- a/tests/Helper/Optimizer/GDTest.php +++ b/tests/Helper/Optimizer/GDTest.php @@ -22,7 +22,7 @@ public function testRunOptimization(): void $callback = function ($theta, $sample, $target) { $y = $theta[0] + $theta[1] * $sample[0]; - $cost = ($y - $target) ** 2 / 2; + $cost = (($y - $target) ** 2) / 2; $grad = $y - $target; return [$cost, $grad]; @@ -49,7 +49,7 @@ public function testRunOptimization2Dim(): void $callback = function ($theta, $sample, $target) { $y = $theta[0] + $theta[1] * $sample[0] + $theta[2] * $sample[1]; - $cost = ($y - $target) ** 2 / 2; + $cost = (($y - $target) ** 2) / 2; $grad = $y - $target; return [$cost, $grad]; diff --git a/tests/Helper/Optimizer/StochasticGDTest.php b/tests/Helper/Optimizer/StochasticGDTest.php index ba3430bf..075cee15 100644 --- a/tests/Helper/Optimizer/StochasticGDTest.php +++ b/tests/Helper/Optimizer/StochasticGDTest.php @@ -22,7 +22,7 @@ public function testRunOptimization(): void $callback = function ($theta, $sample, $target) { $y = $theta[0] + $theta[1] * $sample[0]; - $cost = ($y - $target) ** 2 / 2; + $cost = (($y - $target) ** 2) / 2; $grad = $y - $target; return [$cost, $grad]; @@ -49,7 +49,7 @@ public function testRunOptimization2Dim(): void $callback = function ($theta, $sample, $target) { $y = $theta[0] + $theta[1] * $sample[0] + $theta[2] * $sample[1]; - $cost = ($y - $target) ** 2 / 2; + $cost = (($y - $target) ** 2) / 2; $grad = $y - $target; return [$cost, $grad]; diff --git a/tests/Math/Kernel/RBFTest.php b/tests/Math/Kernel/RBFTest.php index bf24aab5..15fbd2a0 100644 --- a/tests/Math/Kernel/RBFTest.php +++ b/tests/Math/Kernel/RBFTest.php @@ -4,6 +4,7 @@ namespace Phpml\Tests\Math\Kernel; +use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Kernel\RBF; use PHPUnit\Framework\TestCase; @@ -23,4 +24,12 @@ public function testComputeRBFKernelFunction(): void self::assertEquals(0.00451, $rbf->compute([1, 2, 3], [4, 5, 6]), '', $delta = 0.0001); self::assertEquals(0, $rbf->compute([4, 5], [1, 100])); } + + public function testThrowExceptionWhenComputeArgumentIsNotAnArray(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Arguments of Phpml\\Math\\Kernel\\RBF::compute must be arrays'); + + (new RBF(0.1))->compute([0], 1.0); + } } From dbbce0e06630d52db0e53d8e00fcf38ea5019267 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 2 Apr 2019 11:07:00 +0200 Subject: [PATCH 303/328] Implement LabelEncoder (#369) --- CHANGELOG.md | 4 ++ README.md | 1 + docs/index.md | 1 + src/Preprocessing/LabelEncoder.php | 47 ++++++++++++++++ tests/Preprocessing/LabelEncoderTest.php | 68 ++++++++++++++++++++++++ 5 files changed, 121 insertions(+) create mode 100644 src/Preprocessing/LabelEncoder.php create mode 100644 tests/Preprocessing/LabelEncoderTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 63507f0e..662a086a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] +### Added +- [Preprocessing] Implement LabelEncoder + ## [0.8.0] - 2019-03-20 ### Added - [Tokenization] Added NGramTokenizer (#350) diff --git a/README.md b/README.md index 4df5730e..999b48b6 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,7 @@ Public datasets are available in a separate repository [php-ai/php-ml-datasets]( * Preprocessing * [Normalization](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/normalization/) * [Imputation missing values](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/imputation-missing-values/) + * LabelEncoder * Feature Extraction * [Token Count Vectorizer](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-extraction/token-count-vectorizer/) * NGramTokenizer diff --git a/docs/index.md b/docs/index.md index 3c6ede22..25ad6b06 100644 --- a/docs/index.md +++ b/docs/index.md @@ -85,6 +85,7 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * Preprocessing * [Normalization](machine-learning/preprocessing/normalization.md) * [Imputation missing values](machine-learning/preprocessing/imputation-missing-values.md) + * LabelEncoder * Feature Extraction * [Token Count Vectorizer](machine-learning/feature-extraction/token-count-vectorizer.md) * [Tf-idf Transformer](machine-learning/feature-extraction/tf-idf-transformer.md) diff --git a/src/Preprocessing/LabelEncoder.php b/src/Preprocessing/LabelEncoder.php new file mode 100644 index 00000000..9b5df2c1 --- /dev/null +++ b/src/Preprocessing/LabelEncoder.php @@ -0,0 +1,47 @@ +classes = []; + + foreach ($samples as $sample) { + if (!isset($this->classes[(string) $sample])) { + $this->classes[(string) $sample] = count($this->classes); + } + } + } + + public function transform(array &$samples): void + { + foreach ($samples as &$sample) { + $sample = $this->classes[(string) $sample]; + } + } + + public function inverseTransform(array &$samples): void + { + $classes = array_flip($this->classes); + foreach ($samples as &$sample) { + $sample = $classes[$sample]; + } + } + + /** + * @return string[] + */ + public function classes(): array + { + return array_keys($this->classes); + } +} diff --git a/tests/Preprocessing/LabelEncoderTest.php b/tests/Preprocessing/LabelEncoderTest.php new file mode 100644 index 00000000..71dc87ed --- /dev/null +++ b/tests/Preprocessing/LabelEncoderTest.php @@ -0,0 +1,68 @@ +fit($samples); + $le->transform($samples); + + self::assertEquals($transformed, $samples); + } + + public function labelEncoderDataProvider(): array + { + return [ + [['one', 'one', 'two', 'three'], [0, 0, 1, 2]], + [['one', 1, 'two', 'three'], [0, 1, 2, 3]], + [['one', null, 'two', 'three'], [0, 1, 2, 3]], + [['one', 'one', 'one', 'one'], [0, 0, 0, 0]], + [['one', 'one', 'one', 'one', null, null, 1, 1, 2, 'two'], [0, 0, 0, 0, 1, 1, 2, 2, 3, 4]], + ]; + } + + public function testResetClassesAfterNextFit(): void + { + $samples = ['Shanghai', 'Beijing', 'Karachi']; + + $le = new LabelEncoder(); + $le->fit($samples); + + self::assertEquals(['Shanghai', 'Beijing', 'Karachi'], $le->classes()); + + $samples = ['Istanbul', 'Dhaka', 'Tokyo']; + + $le->fit($samples); + + self::assertEquals(['Istanbul', 'Dhaka', 'Tokyo'], $le->classes()); + } + + public function testFitAndTransformFullCycle(): void + { + $samples = ['Shanghai', 'Beijing', 'Karachi', 'Beijing', 'Beijing', 'Karachi']; + $encoded = [0, 1, 2, 1, 1, 2]; + + $le = new LabelEncoder(); + $le->fit($samples); + + self::assertEquals(['Shanghai', 'Beijing', 'Karachi'], $le->classes()); + + $transformed = $samples; + $le->transform($transformed); + self::assertEquals($encoded, $transformed); + + $le->inverseTransform($transformed); + self::assertEquals($samples, $transformed); + } +} From cefb4fc7a7e57f63da3361f574fc9d3a0c47643c Mon Sep 17 00:00:00 2001 From: KenorFR Date: Fri, 5 Apr 2019 21:23:09 +0200 Subject: [PATCH 304/328] Ngram word (#370) * Add NGramWordTokenizer * Update doc Add test Check coding standards --- .../token-count-vectorizer.md | 17 +++ src/Tokenization/NGramWordTokenizer.php | 64 ++++++++++ tests/Tokenization/NGramWordTokenizerTest.php | 112 ++++++++++++++++++ 3 files changed, 193 insertions(+) create mode 100644 src/Tokenization/NGramWordTokenizer.php create mode 100644 tests/Tokenization/NGramWordTokenizerTest.php diff --git a/docs/machine-learning/feature-extraction/token-count-vectorizer.md b/docs/machine-learning/feature-extraction/token-count-vectorizer.md index 8e2e9fd0..4dc52604 100644 --- a/docs/machine-learning/feature-extraction/token-count-vectorizer.md +++ b/docs/machine-learning/feature-extraction/token-count-vectorizer.md @@ -71,3 +71,20 @@ $tokenizer->tokenize('Quick Fox'); // returns ['Q', 'u', 'i', 'c', 'k', 'Qu', 'ui', 'ic', 'ck', 'F', 'o', 'x', 'Fo', 'ox'] ``` + +**NGramWordTokenizer** + +The NGramWordTokenizer tokenizer accepts the following parameters: + +`$minGram` - minimum length of characters in a gram. Defaults to 1. +`$maxGram` - maximum length of characters in a gram. Defaults to 2. + +```php +use Phpml\Tokenization\NGramWordTokenizer; + +$tokenizer = new NGramWordTokenizer(1, 2); + +$tokenizer->tokenize('very quick fox'); + +// returns ['very', 'quick', 'fox', 'very quick', 'quick fox'] +``` diff --git a/src/Tokenization/NGramWordTokenizer.php b/src/Tokenization/NGramWordTokenizer.php new file mode 100644 index 00000000..20ee28cb --- /dev/null +++ b/src/Tokenization/NGramWordTokenizer.php @@ -0,0 +1,64 @@ + $maxGram) { + throw new InvalidArgumentException(sprintf('Invalid (%s, %s) minGram and maxGram value combination', $minGram, $maxGram)); + } + + $this->minGram = $minGram; + $this->maxGram = $maxGram; + } + + /** + * {@inheritdoc} + */ + public function tokenize(string $text): array + { + preg_match_all('/\w\w+/u', $text, $words); + + $words = $words[0]; + + $nGrams = []; + for ($j = $this->minGram; $j <= $this->maxGram; $j++) { + $nGrams = array_merge($nGrams, $this->getNgrams($words, $j)); + } + + return $nGrams; + } + + private function getNgrams(array $match, int $n = 2): array + { + $ngrams = []; + $len = count($match); + for ($i = 0; $i < $len; $i++) { + if ($i > ($n - 2)) { + $ng = ''; + for ($j = $n - 1; $j >= 0; $j--) { + $ng .= ' '.$match[$i - $j]; + } + $ngrams[] = trim($ng); + } + } + + return $ngrams; + } +} diff --git a/tests/Tokenization/NGramWordTokenizerTest.php b/tests/Tokenization/NGramWordTokenizerTest.php new file mode 100644 index 00000000..f9986d50 --- /dev/null +++ b/tests/Tokenization/NGramWordTokenizerTest.php @@ -0,0 +1,112 @@ +tokenize($text)); + } + + public function testMinGramGreaterThanMaxGramNotAllowed(): void + { + self::expectException(InvalidArgumentException::class); + + new NGramWordTokenizer(5, 2); + } + + public function testMinGramValueTooSmall(): void + { + self::expectException(InvalidArgumentException::class); + + new NGramWordTokenizer(0, 2); + } + + public function testMaxGramValueTooSmall(): void + { + self::expectException(InvalidArgumentException::class); + + new NGramWordTokenizer(1, 0); + } + + public function textDataProvider(): array + { + return [ + [ + 1, 1, + 'one two three four', + ['one', 'two', 'three', 'four'], + ], + [ + 1, 2, + 'one two three four', + ['one', 'two', 'three', 'four', 'one two', 'two three', 'three four'], + ], + [ + 1, 3, + 'one two three four', + ['one', 'two', 'three', 'four', 'one two', 'two three', 'three four', 'one two three', 'two three four'], + ], + [ + 2, 3, + 'one two three four', + ['one two', 'two three', 'three four', 'one two three', 'two three four'], + ], + [ + 1, 2, + '快狐跑过 边缘跑', + ['快狐跑过', '边缘跑', '快狐跑过 边缘跑'], + ], + [ + 2, 4, + $this->getSimpleText(), + [ + 'Lorem ipsum', 'ipsum dolor', 'dolor sit', 'sit amet', 'amet consectetur', 'consectetur adipiscing', + 'adipiscing elit', 'elit Cras', 'Cras consectetur', 'consectetur dui', 'dui et', 'et lobortis', + 'lobortis auctor', 'auctor Nulla', 'Nulla vitae', 'vitae congue', 'congue lorem', 'Lorem ipsum dolor', + 'ipsum dolor sit', 'dolor sit amet', 'sit amet consectetur', 'amet consectetur adipiscing', + 'consectetur adipiscing elit', 'adipiscing elit Cras', 'elit Cras consectetur', 'Cras consectetur dui', + 'consectetur dui et', 'dui et lobortis', 'et lobortis auctor', 'lobortis auctor Nulla', 'auctor Nulla vitae', + 'Nulla vitae congue', 'vitae congue lorem', 'Lorem ipsum dolor sit', 'ipsum dolor sit amet', + 'dolor sit amet consectetur', 'sit amet consectetur adipiscing', 'amet consectetur adipiscing elit', + 'consectetur adipiscing elit Cras', 'adipiscing elit Cras consectetur', 'elit Cras consectetur dui', + 'Cras consectetur dui et', 'consectetur dui et lobortis', 'dui et lobortis auctor', 'et lobortis auctor Nulla', + 'lobortis auctor Nulla vitae', 'auctor Nulla vitae congue', 'Nulla vitae congue lorem', + ], + ], + [ + 2, 4, + $this->getUtf8Text(), + [ + '鋍鞎 鞮鞢騉', '鞮鞢騉 袟袘觕', '袟袘觕 炟砏', '炟砏 謺貙蹖', '謺貙蹖 偢偣唲', '偢偣唲 箷箯緷', '箷箯緷 鑴鱱爧', '鑴鱱爧 覮轀', + '覮轀 剆坲', '剆坲 煘煓瑐', '煘煓瑐 鬐鶤鶐', '鬐鶤鶐 飹勫嫢', '飹勫嫢 枲柊氠', '枲柊氠 鍎鞚韕', '鍎鞚韕 焲犈', '焲犈 殍涾烰', + '殍涾烰 齞齝囃', '齞齝囃 蹅輶', '蹅輶 孻憵', '孻憵 擙樲橚', '擙樲橚 藒襓謥', '藒襓謥 岯岪弨', '岯岪弨 廞徲', '廞徲 孻憵懥', + '孻憵懥 趡趛踠', '鋍鞎 鞮鞢騉 袟袘觕', '鞮鞢騉 袟袘觕 炟砏', '袟袘觕 炟砏 謺貙蹖', '炟砏 謺貙蹖 偢偣唲', '謺貙蹖 偢偣唲 箷箯緷', + '偢偣唲 箷箯緷 鑴鱱爧', '箷箯緷 鑴鱱爧 覮轀', '鑴鱱爧 覮轀 剆坲', '覮轀 剆坲 煘煓瑐', '剆坲 煘煓瑐 鬐鶤鶐', '煘煓瑐 鬐鶤鶐 飹勫嫢', + '鬐鶤鶐 飹勫嫢 枲柊氠', '飹勫嫢 枲柊氠 鍎鞚韕', '枲柊氠 鍎鞚韕 焲犈', '鍎鞚韕 焲犈 殍涾烰', '焲犈 殍涾烰 齞齝囃', '殍涾烰 齞齝囃 蹅輶', + '齞齝囃 蹅輶 孻憵', '蹅輶 孻憵 擙樲橚', '孻憵 擙樲橚 藒襓謥', '擙樲橚 藒襓謥 岯岪弨', '藒襓謥 岯岪弨 廞徲', '岯岪弨 廞徲 孻憵懥', + '廞徲 孻憵懥 趡趛踠', '鋍鞎 鞮鞢騉 袟袘觕 炟砏', '鞮鞢騉 袟袘觕 炟砏 謺貙蹖', '袟袘觕 炟砏 謺貙蹖 偢偣唲', '炟砏 謺貙蹖 偢偣唲 箷箯緷', + '謺貙蹖 偢偣唲 箷箯緷 鑴鱱爧', '偢偣唲 箷箯緷 鑴鱱爧 覮轀', '箷箯緷 鑴鱱爧 覮轀 剆坲', '鑴鱱爧 覮轀 剆坲 煘煓瑐', + '覮轀 剆坲 煘煓瑐 鬐鶤鶐', '剆坲 煘煓瑐 鬐鶤鶐 飹勫嫢', '煘煓瑐 鬐鶤鶐 飹勫嫢 枲柊氠', '鬐鶤鶐 飹勫嫢 枲柊氠 鍎鞚韕', + '飹勫嫢 枲柊氠 鍎鞚韕 焲犈', '枲柊氠 鍎鞚韕 焲犈 殍涾烰', '鍎鞚韕 焲犈 殍涾烰 齞齝囃', '焲犈 殍涾烰 齞齝囃 蹅輶', + '殍涾烰 齞齝囃 蹅輶 孻憵', '齞齝囃 蹅輶 孻憵 擙樲橚', '蹅輶 孻憵 擙樲橚 藒襓謥', '孻憵 擙樲橚 藒襓謥 岯岪弨', '擙樲橚 藒襓謥 岯岪弨 廞徲', + '藒襓謥 岯岪弨 廞徲 孻憵懥', '岯岪弨 廞徲 孻憵懥 趡趛踠', + ], + ], + ]; + } +} From db82afa263271d4545c013d44b2ae2f4ed81fbc6 Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Wed, 10 Apr 2019 20:42:59 +0200 Subject: [PATCH 305/328] Update to phpunit 8 and bump min php to 7.2 (#367) * Update to phpunit 8 * Require at least PHP 7.2 --- .gitignore | 3 +- .travis.yml | 8 +-- README.md | 4 +- composer.json | 4 +- composer.lock | 71 ++++++++++--------- docs/index.md | 4 +- .../Linear/LogisticRegressionTest.php | 10 +-- tests/DimensionReduction/KernelPCATest.php | 6 +- tests/DimensionReduction/LDATest.php | 2 +- tests/DimensionReduction/PCATest.php | 4 +- .../TfIdfTransformerTest.php | 2 +- .../ScoringFunction/ANOVAFValueTest.php | 3 +- .../UnivariateLinearRegressionTest.php | 4 +- .../Optimizer/ConjugateGradientTest.php | 6 +- tests/Helper/Optimizer/GDTest.php | 4 +- tests/Helper/Optimizer/StochasticGDTest.php | 4 +- tests/Math/Distance/MinkowskiTest.php | 6 +- tests/Math/Kernel/RBFTest.php | 6 +- .../EigenvalueDecompositionTest.php | 20 +++--- tests/Math/MatrixTest.php | 6 +- tests/Math/Statistic/ANOVATest.php | 4 +- tests/Math/Statistic/CorrelationTest.php | 6 +- tests/Math/Statistic/CovarianceTest.php | 12 ++-- tests/Math/Statistic/GaussianTest.php | 4 +- tests/Math/Statistic/MeanTest.php | 6 +- .../Math/Statistic/StandardDeviationTest.php | 8 +-- tests/Math/Statistic/VarianceTest.php | 2 +- tests/Metric/AccuracyTest.php | 2 +- tests/Metric/ClassificationReportTest.php | 42 +++++------ .../ActivationFunction/GaussianTest.php | 4 +- .../HyperboliTangentTest.php | 4 +- .../ActivationFunction/PReLUTest.php | 2 +- .../ActivationFunction/SigmoidTest.php | 4 +- .../Network/LayeredNetworkTest.php | 6 +- .../Network/MultilayerPerceptronTest.php | 4 +- .../NeuralNetwork/Node/Neuron/SynapseTest.php | 4 +- tests/NeuralNetwork/Node/NeuronTest.php | 12 ++-- tests/PipelineTest.php | 2 +- tests/Preprocessing/ImputerTest.php | 10 +-- tests/Preprocessing/NormalizerTest.php | 10 +-- tests/Regression/LeastSquaresTest.php | 22 +++--- tests/Regression/SVRTest.php | 4 +- .../SupportVectorMachineTest.php | 4 +- 43 files changed, 176 insertions(+), 179 deletions(-) diff --git a/.gitignore b/.gitignore index 5fb4f2b0..3adb3efd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /vendor/ -.php_cs.cache /build /tests/Performance/Data/*.csv +.php_cs.cache +.phpunit.result.cache diff --git a/.travis.yml b/.travis.yml index 68ef44bc..92c643b3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,14 +4,10 @@ matrix: fast_finish: true include: - - os: linux - php: '7.1' - env: DISABLE_XDEBUG="true" STATIC_ANALYSIS="true" - - os: linux php: '7.2' - env: PHPUNIT_FLAGS="--coverage-clover build/logs/clover.xml" - + env: PHPUNIT_FLAGS="--coverage-clover build/logs/clover.xml" DISABLE_XDEBUG="true" STATIC_ANALYSIS="true" + - os: linux php: '7.3' diff --git a/README.md b/README.md index 999b48b6..3449ee0d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # PHP-ML - Machine Learning library for PHP -[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.1-8892BF.svg)](https://php.net/) +[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.2-8892BF.svg)](https://php.net/) [![Latest Stable Version](https://img.shields.io/packagist/v/php-ai/php-ml.svg)](https://packagist.org/packages/php-ai/php-ml) [![Build Status](https://travis-ci.org/php-ai/php-ml.svg?branch=master)](https://travis-ci.org/php-ai/php-ml) [![Documentation Status](https://readthedocs.org/projects/php-ml/badge/?version=master)](http://php-ml.readthedocs.org/) @@ -15,7 +15,7 @@ Fresh approach to Machine Learning in PHP. Algorithms, Cross Validation, Neural Network, Preprocessing, Feature Extraction and much more in one library. -PHP-ML requires PHP >= 7.1. +PHP-ML requires PHP >= 7.2. Simple example of classification: ```php diff --git a/composer.json b/composer.json index 8b3b1586..52e783c3 100644 --- a/composer.json +++ b/composer.json @@ -20,14 +20,14 @@ } ], "require": { - "php": "^7.1" + "php": "^7.2" }, "require-dev": { "phpbench/phpbench": "^0.14.0", "phpstan/phpstan-phpunit": "^0.11", "phpstan/phpstan-shim": "^0.11", "phpstan/phpstan-strict-rules": "^0.11", - "phpunit/phpunit": "^7.0.0", + "phpunit/phpunit": "^8.0", "symplify/coding-standard": "^5.1", "symplify/easy-coding-standard": "^5.1" }, diff --git a/composer.lock b/composer.lock index 62a34e0c..e270c978 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "dee9be6daf48915171d2778b17d941fa", + "content-hash": "b329ea9fc7b690ad2d498e85a445d214", "packages": [], "packages-dev": [ { @@ -1881,40 +1881,40 @@ }, { "name": "phpunit/php-code-coverage", - "version": "6.1.4", + "version": "7.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d" + "reference": "0317a769a81845c390e19684d9ba25d7f6aa4707" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", - "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/0317a769a81845c390e19684d9ba25d7f6aa4707", + "reference": "0317a769a81845c390e19684d9ba25d7f6aa4707", "shasum": "" }, "require": { "ext-dom": "*", "ext-xmlwriter": "*", - "php": "^7.1", - "phpunit/php-file-iterator": "^2.0", + "php": "^7.2", + "phpunit/php-file-iterator": "^2.0.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^3.0", + "phpunit/php-token-stream": "^3.0.1", "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^3.1 || ^4.0", + "sebastian/environment": "^4.1", "sebastian/version": "^2.0.1", "theseer/tokenizer": "^1.1" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^8.0" }, "suggest": { - "ext-xdebug": "^2.6.0" + "ext-xdebug": "^2.6.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "6.1-dev" + "dev-master": "7.0-dev" } }, "autoload": { @@ -1940,7 +1940,7 @@ "testing", "xunit" ], - "time": "2018-10-31T16:06:48+00:00" + "time": "2019-02-26T07:38:26+00:00" }, { "name": "phpunit/php-file-iterator", @@ -2133,16 +2133,16 @@ }, { "name": "phpunit/phpunit", - "version": "7.5.7", + "version": "8.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "eb343b86753d26de07ecba7868fa983104361948" + "reference": "19cbed2120839772c4a00e8b28456b0c77d1a7b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/eb343b86753d26de07ecba7868fa983104361948", - "reference": "eb343b86753d26de07ecba7868fa983104361948", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/19cbed2120839772c4a00e8b28456b0c77d1a7b4", + "reference": "19cbed2120839772c4a00e8b28456b0c77d1a7b4", "shasum": "" }, "require": { @@ -2152,27 +2152,25 @@ "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", + "ext-xmlwriter": "*", "myclabs/deep-copy": "^1.7", "phar-io/manifest": "^1.0.2", "phar-io/version": "^2.0", - "php": "^7.1", + "php": "^7.2", "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^6.0.7", + "phpunit/php-code-coverage": "^7.0", "phpunit/php-file-iterator": "^2.0.1", "phpunit/php-text-template": "^1.2.1", "phpunit/php-timer": "^2.1", "sebastian/comparator": "^3.0", "sebastian/diff": "^3.0", - "sebastian/environment": "^4.0", + "sebastian/environment": "^4.1", "sebastian/exporter": "^3.1", - "sebastian/global-state": "^2.0", + "sebastian/global-state": "^3.0", "sebastian/object-enumerator": "^3.0.3", "sebastian/resource-operations": "^2.0", "sebastian/version": "^2.0.1" }, - "conflict": { - "phpunit/phpunit-mock-objects": "*" - }, "require-dev": { "ext-pdo": "*" }, @@ -2187,7 +2185,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.5-dev" + "dev-master": "8.0-dev" } }, "autoload": { @@ -2213,7 +2211,7 @@ "testing", "xunit" ], - "time": "2019-03-16T07:31:17+00:00" + "time": "2019-03-16T07:33:46+00:00" }, { "name": "psr/cache", @@ -2692,23 +2690,26 @@ }, { "name": "sebastian/global-state", - "version": "2.0.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", + "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", "shasum": "" }, "require": { - "php": "^7.0" + "php": "^7.2", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "ext-dom": "*", + "phpunit/phpunit": "^8.0" }, "suggest": { "ext-uopz": "*" @@ -2716,7 +2717,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2739,7 +2740,7 @@ "keywords": [ "global state" ], - "time": "2017-04-27T15:39:26+00:00" + "time": "2019-02-01T05:30:01+00:00" }, { "name": "sebastian/object-enumerator", @@ -4749,7 +4750,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^7.1" + "php": "^7.2" }, "platform-dev": [] } diff --git a/docs/index.md b/docs/index.md index 25ad6b06..8ce2751b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,6 @@ # PHP-ML - Machine Learning library for PHP -[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.1-8892BF.svg)](https://php.net/) +[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.2-8892BF.svg)](https://php.net/) [![Latest Stable Version](https://img.shields.io/packagist/v/php-ai/php-ml.svg)](https://packagist.org/packages/php-ai/php-ml) [![Build Status](https://travis-ci.org/php-ai/php-ml.svg?branch=master)](https://travis-ci.org/php-ai/php-ml) [![Documentation Status](https://readthedocs.org/projects/php-ml/badge/?version=master)](http://php-ml.readthedocs.org/) @@ -15,7 +15,7 @@ Fresh approach to Machine Learning in PHP. Algorithms, Cross Validation, Neural Network, Preprocessing, Feature Extraction and much more in one library. -PHP-ML requires PHP >= 7.1. +PHP-ML requires PHP >= 7.2. Simple example of classification: ```php diff --git a/tests/Classification/Linear/LogisticRegressionTest.php b/tests/Classification/Linear/LogisticRegressionTest.php index 9573531e..812870cb 100644 --- a/tests/Classification/Linear/LogisticRegressionTest.php +++ b/tests/Classification/Linear/LogisticRegressionTest.php @@ -171,12 +171,12 @@ public function testPredictProbabilitySingleSample(): void $zero = $method->invoke($predictor, [0.1, 0.1], 0); $one = $method->invoke($predictor, [0.1, 0.1], 1); - self::assertEquals(1, $zero + $one, '', 1e-6); + self::assertEqualsWithDelta(1, $zero + $one, 1e-6); self::assertTrue($zero > $one); $zero = $method->invoke($predictor, [0.9, 0.9], 0); $one = $method->invoke($predictor, [0.9, 0.9], 1); - self::assertEquals(1, $zero + $one, '', 1e-6); + self::assertEqualsWithDelta(1, $zero + $one, 1e-6); self::assertTrue($zero < $one); } @@ -213,9 +213,9 @@ public function testPredictProbabilityMultiClassSample(): void $two = $method->invoke($predictor, [3.0, 9.5], 2); $not_two = $method->invoke($predictor, [3.0, 9.5], 'not_2'); - self::assertEquals(1, $zero + $not_zero, '', 1e-6); - self::assertEquals(1, $one + $not_one, '', 1e-6); - self::assertEquals(1, $two + $not_two, '', 1e-6); + self::assertEqualsWithDelta(1, $zero + $not_zero, 1e-6); + self::assertEqualsWithDelta(1, $one + $not_one, 1e-6); + self::assertEqualsWithDelta(1, $two + $not_two, 1e-6); self::assertTrue($zero < $two); self::assertTrue($one < $two); } diff --git a/tests/DimensionReduction/KernelPCATest.php b/tests/DimensionReduction/KernelPCATest.php index cd04260b..6e2ec2bb 100644 --- a/tests/DimensionReduction/KernelPCATest.php +++ b/tests/DimensionReduction/KernelPCATest.php @@ -33,14 +33,14 @@ public function testKernelPCA(): void [-0.13128352866095], [-0.20865959471756], [-0.17531601535848], [0.4240660966961], [0.36351946685163], [-0.14334173054136], [0.22454914091011], [0.15035027480881], ]; - $kpca = new KernelPCA(KernelPCA::KERNEL_RBF, null, 1, 15); + $kpca = new KernelPCA(KernelPCA::KERNEL_RBF, null, 1, 15.); $reducedData = $kpca->fit($data); // Due to the fact that the sign of values can be flipped // during the calculation of eigenValues, we have to compare // absolute value of the values array_map(function ($val1, $val2) use ($epsilon): void { - self::assertEquals(abs($val1), abs($val2), '', $epsilon); + self::assertEqualsWithDelta(abs($val1), abs($val2), $epsilon); }, $transformed, $reducedData); // Fitted KernelPCA object can also transform an arbitrary sample of the @@ -48,7 +48,7 @@ public function testKernelPCA(): void $newData = [1.25, 2.25]; $newTransformed = [0.18956227539216]; $newTransformed2 = $kpca->transform($newData); - self::assertEquals(abs($newTransformed[0]), abs($newTransformed2[0]), '', $epsilon); + self::assertEqualsWithDelta(abs($newTransformed[0]), abs($newTransformed2[0]), $epsilon); } public function testKernelPCAThrowWhenKernelInvalid(): void diff --git a/tests/DimensionReduction/LDATest.php b/tests/DimensionReduction/LDATest.php index 101d2f96..b64a7f34 100644 --- a/tests/DimensionReduction/LDATest.php +++ b/tests/DimensionReduction/LDATest.php @@ -51,7 +51,7 @@ public function testLDA(): void // absolute value of the values $row1 = array_map('abs', $row1); $row2 = array_map('abs', $row2); - self::assertEquals($row1, $row2, '', $epsilon); + self::assertEqualsWithDelta($row1, $row2, $epsilon); }; array_map($check, $control, $transformed2); diff --git a/tests/DimensionReduction/PCATest.php b/tests/DimensionReduction/PCATest.php index ebb5b013..5fbbc94b 100644 --- a/tests/DimensionReduction/PCATest.php +++ b/tests/DimensionReduction/PCATest.php @@ -42,7 +42,7 @@ public function testPCA(): void // during the calculation of eigenValues, we have to compare // absolute value of the values array_map(function ($val1, $val2) use ($epsilon): void { - self::assertEquals(abs($val1), abs($val2), '', $epsilon); + self::assertEqualsWithDelta(abs($val1), abs($val2), $epsilon); }, $transformed, $reducedData); // Test fitted PCA object to transform an arbitrary sample of the @@ -52,7 +52,7 @@ public function testPCA(): void $newRow2 = $pca->transform($row); array_map(function ($val1, $val2) use ($epsilon): void { - self::assertEquals(abs($val1), abs($val2), '', $epsilon); + self::assertEqualsWithDelta(abs($val1), abs($val2), $epsilon); }, $newRow, $newRow2); } } diff --git a/tests/FeatureExtraction/TfIdfTransformerTest.php b/tests/FeatureExtraction/TfIdfTransformerTest.php index b0cf9f63..acb01034 100644 --- a/tests/FeatureExtraction/TfIdfTransformerTest.php +++ b/tests/FeatureExtraction/TfIdfTransformerTest.php @@ -54,6 +54,6 @@ public function testTfIdfTransformation(): void $transformer = new TfIdfTransformer($samples); $transformer->transform($samples); - self::assertEquals($tfIdfSamples, $samples, '', 0.001); + self::assertEqualsWithDelta($tfIdfSamples, $samples, 0.001); } } diff --git a/tests/FeatureSelection/ScoringFunction/ANOVAFValueTest.php b/tests/FeatureSelection/ScoringFunction/ANOVAFValueTest.php index 7a601db2..8954e323 100644 --- a/tests/FeatureSelection/ScoringFunction/ANOVAFValueTest.php +++ b/tests/FeatureSelection/ScoringFunction/ANOVAFValueTest.php @@ -15,10 +15,9 @@ public function testScoreForANOVAFValue(): void $dataset = new IrisDataset(); $function = new ANOVAFValue(); - self::assertEquals( + self::assertEqualsWithDelta( [119.2645, 47.3644, 1179.0343, 959.3244], $function->score($dataset->getSamples(), $dataset->getTargets()), - '', 0.0001 ); } diff --git a/tests/FeatureSelection/ScoringFunction/UnivariateLinearRegressionTest.php b/tests/FeatureSelection/ScoringFunction/UnivariateLinearRegressionTest.php index 0047e5fe..48d72d32 100644 --- a/tests/FeatureSelection/ScoringFunction/UnivariateLinearRegressionTest.php +++ b/tests/FeatureSelection/ScoringFunction/UnivariateLinearRegressionTest.php @@ -15,7 +15,7 @@ public function testRegressionScore(): void $targets = [2000, 2750, 15500, 960, 4400, 8800, 7100, 2550, 1025, 5900, 4600, 4400]; $function = new UnivariateLinearRegression(); - self::assertEquals([6.97286, 6.48558], $function->score($samples, $targets), '', 0.0001); + self::assertEqualsWithDelta([6.97286, 6.48558], $function->score($samples, $targets), 0.0001); } public function testRegressionScoreWithoutCenter(): void @@ -24,6 +24,6 @@ public function testRegressionScoreWithoutCenter(): void $targets = [2000, 2750, 15500, 960, 4400, 8800, 7100, 2550, 1025, 5900, 4600, 4400]; $function = new UnivariateLinearRegression(false); - self::assertEquals([1.74450, 18.08347], $function->score($samples, $targets), '', 0.0001); + self::assertEqualsWithDelta([1.74450, 18.08347], $function->score($samples, $targets), 0.0001); } } diff --git a/tests/Helper/Optimizer/ConjugateGradientTest.php b/tests/Helper/Optimizer/ConjugateGradientTest.php index 230900f4..86a2991b 100644 --- a/tests/Helper/Optimizer/ConjugateGradientTest.php +++ b/tests/Helper/Optimizer/ConjugateGradientTest.php @@ -33,7 +33,7 @@ public function testRunOptimization(): void $theta = $optimizer->runOptimization($samples, $targets, $callback); - self::assertEquals([-1, 2], $theta, '', 0.1); + self::assertEqualsWithDelta([-1, 2], $theta, 0.1); } public function testRunOptimizationWithCustomInitialTheta(): void @@ -61,7 +61,7 @@ public function testRunOptimizationWithCustomInitialTheta(): void $theta = $optimizer->runOptimization($samples, $targets, $callback); - self::assertEquals([-1.087708, 2.212034], $theta, '', 0.000001); + self::assertEqualsWithDelta([-1.087708, 2.212034], $theta, 0.000001); } public function testRunOptimization2Dim(): void @@ -89,7 +89,7 @@ public function testRunOptimization2Dim(): void $theta = $optimizer->runOptimization($samples, $targets, $callback); - self::assertEquals([-1, 2, -3], $theta, '', 0.1); + self::assertEqualsWithDelta([-1, 2, -3], $theta, 0.1); } public function testThrowExceptionOnInvalidTheta(): void diff --git a/tests/Helper/Optimizer/GDTest.php b/tests/Helper/Optimizer/GDTest.php index 548a74be..96409889 100644 --- a/tests/Helper/Optimizer/GDTest.php +++ b/tests/Helper/Optimizer/GDTest.php @@ -32,7 +32,7 @@ public function testRunOptimization(): void $theta = $optimizer->runOptimization($samples, $targets, $callback); - self::assertEquals([-1, 2], $theta, '', 0.1); + self::assertEqualsWithDelta([-1, 2], $theta, 0.1); } public function testRunOptimization2Dim(): void @@ -60,6 +60,6 @@ public function testRunOptimization2Dim(): void $theta = $optimizer->runOptimization($samples, $targets, $callback); - self::assertEquals([-1, 2, -3], $theta, '', 0.1); + self::assertEqualsWithDelta([-1, 2, -3], $theta, 0.1); } } diff --git a/tests/Helper/Optimizer/StochasticGDTest.php b/tests/Helper/Optimizer/StochasticGDTest.php index 075cee15..07927af4 100644 --- a/tests/Helper/Optimizer/StochasticGDTest.php +++ b/tests/Helper/Optimizer/StochasticGDTest.php @@ -32,7 +32,7 @@ public function testRunOptimization(): void $theta = $optimizer->runOptimization($samples, $targets, $callback); - self::assertEquals([-1, 2], $theta, '', 0.1); + self::assertEqualsWithDelta([-1, 2], $theta, 0.1); } public function testRunOptimization2Dim(): void @@ -60,6 +60,6 @@ public function testRunOptimization2Dim(): void $theta = $optimizer->runOptimization($samples, $targets, $callback); - self::assertEquals([-1, 2, -3], $theta, '', 0.1); + self::assertEqualsWithDelta([-1, 2, -3], $theta, 0.1); } } diff --git a/tests/Math/Distance/MinkowskiTest.php b/tests/Math/Distance/MinkowskiTest.php index 770bf153..fbff7d9c 100644 --- a/tests/Math/Distance/MinkowskiTest.php +++ b/tests/Math/Distance/MinkowskiTest.php @@ -47,7 +47,7 @@ public function testCalculateDistanceForTwoDimensions(): void $expectedDistance = 2.080; $actualDistance = $this->distanceMetric->distance($a, $b); - self::assertEquals($expectedDistance, $actualDistance, '', $delta = 0.001); + self::assertEqualsWithDelta($expectedDistance, $actualDistance, $delta = 0.001); } public function testCalculateDistanceForThreeDimensions(): void @@ -58,7 +58,7 @@ public function testCalculateDistanceForThreeDimensions(): void $expectedDistance = 5.819; $actualDistance = $this->distanceMetric->distance($a, $b); - self::assertEquals($expectedDistance, $actualDistance, '', $delta = 0.001); + self::assertEqualsWithDelta($expectedDistance, $actualDistance, $delta = 0.001); } public function testCalculateDistanceForThreeDimensionsWithDifferentLambda(): void @@ -71,6 +71,6 @@ public function testCalculateDistanceForThreeDimensionsWithDifferentLambda(): vo $expectedDistance = 5.300; $actualDistance = $distanceMetric->distance($a, $b); - self::assertEquals($expectedDistance, $actualDistance, '', $delta = 0.001); + self::assertEqualsWithDelta($expectedDistance, $actualDistance, $delta = 0.001); } } diff --git a/tests/Math/Kernel/RBFTest.php b/tests/Math/Kernel/RBFTest.php index 15fbd2a0..3e4ce266 100644 --- a/tests/Math/Kernel/RBFTest.php +++ b/tests/Math/Kernel/RBFTest.php @@ -15,13 +15,13 @@ public function testComputeRBFKernelFunction(): void $rbf = new RBF($gamma = 0.001); self::assertEquals(1, $rbf->compute([1, 2], [1, 2])); - self::assertEquals(0.97336, $rbf->compute([1, 2, 3], [4, 5, 6]), '', $delta = 0.0001); - self::assertEquals(0.00011, $rbf->compute([4, 5], [1, 100]), '', $delta = 0.0001); + self::assertEqualsWithDelta(0.97336, $rbf->compute([1, 2, 3], [4, 5, 6]), $delta = 0.0001); + self::assertEqualsWithDelta(0.00011, $rbf->compute([4, 5], [1, 100]), $delta = 0.0001); $rbf = new RBF($gamma = 0.2); self::assertEquals(1, $rbf->compute([1, 2], [1, 2])); - self::assertEquals(0.00451, $rbf->compute([1, 2, 3], [4, 5, 6]), '', $delta = 0.0001); + self::assertEqualsWithDelta(0.00451, $rbf->compute([1, 2, 3], [4, 5, 6]), $delta = 0.0001); self::assertEquals(0, $rbf->compute([4, 5], [1, 100])); } diff --git a/tests/Math/LinearAlgebra/EigenvalueDecompositionTest.php b/tests/Math/LinearAlgebra/EigenvalueDecompositionTest.php index 73018d03..884da25b 100644 --- a/tests/Math/LinearAlgebra/EigenvalueDecompositionTest.php +++ b/tests/Math/LinearAlgebra/EigenvalueDecompositionTest.php @@ -21,11 +21,11 @@ public function testKnownSymmetricMatrixDecomposition(): void $decomp = new EigenvalueDecomposition($matrix); - self::assertEquals([0.0490833989, 1.28402771], $decomp->getRealEigenvalues(), '', 0.001); - self::assertEquals([ + self::assertEqualsWithDelta([0.0490833989, 1.28402771], $decomp->getRealEigenvalues(), 0.001); + self::assertEqualsWithDelta([ [-0.735178656, 0.677873399], [-0.677873399, -0.735178656], - ], $decomp->getEigenvectors(), '', 0.001); + ], $decomp->getEigenvectors(), 0.001); } public function testMatrixWithAllZeroRow(): void @@ -39,12 +39,12 @@ public function testMatrixWithAllZeroRow(): void $decomp = new EigenvalueDecomposition($matrix); - self::assertEquals([0.0, 6.0, 10.0], $decomp->getRealEigenvalues(), '', 0.0001); - self::assertEquals([ + self::assertEqualsWithDelta([0.0, 6.0, 10.0], $decomp->getRealEigenvalues(), 0.0001); + self::assertEqualsWithDelta([ [0, 0, 1], [0, 1, 0], [1, 0, 0], - ], $decomp->getEigenvectors(), '', 0.0001); + ], $decomp->getEigenvectors(), 0.0001); } public function testMatrixThatCauseErrorWithStrictComparision(): void @@ -58,12 +58,12 @@ public function testMatrixThatCauseErrorWithStrictComparision(): void $decomp = new EigenvalueDecomposition($matrix); - self::assertEquals([-5.2620873481, 1.0, 10.2620873481], $decomp->getRealEigenvalues(), '', 0.000001); - self::assertEquals([ + self::assertEqualsWithDelta([-5.2620873481, 1.0, 10.2620873481], $decomp->getRealEigenvalues(), 0.000001); + self::assertEqualsWithDelta([ [-0.3042688, -0.709960552, 0.63511928], [-0.9191450, 0.393919298, 0.0], [0.25018574, 0.5837667, 0.7724140], - ], $decomp->getEigenvectors(), '', 0.0001); + ], $decomp->getEigenvectors(), 0.0001); } public function testRandomSymmetricMatrixEigenPairs(): void @@ -98,7 +98,7 @@ public function testRandomSymmetricMatrixEigenPairs(): void $leftSide = $m1->multiply($m2)->toArray(); $rightSide = $m2->multiplyByScalar($lambda)->toArray(); - self::assertEquals($leftSide, $rightSide, '', $epsilon); + self::assertEqualsWithDelta($leftSide, $rightSide, $epsilon); } } } diff --git a/tests/Math/MatrixTest.php b/tests/Math/MatrixTest.php index 94d47e28..4c11caf8 100644 --- a/tests/Math/MatrixTest.php +++ b/tests/Math/MatrixTest.php @@ -60,7 +60,7 @@ public function testGetMatrixDeterminant(): void [1 / 4, 4, 1, 0, 2, 3 / 7], [1, 8, 7, 5, 4, 4 / 5], ]); - self::assertEquals(1116.5035, $matrix->getDeterminant(), '', $delta = 0.0001); + self::assertEqualsWithDelta(1116.5035, $matrix->getDeterminant(), $delta = 0.0001); } public function testMatrixTranspose(): void @@ -157,7 +157,7 @@ public function testInverseMatrix(): void [-1 / 2, 1 / 2, -1 / 2], ]; - self::assertEquals($inverseMatrix, $matrix->inverse()->toArray(), '', $delta = 0.0001); + self::assertEqualsWithDelta($inverseMatrix, $matrix->inverse()->toArray(), $delta = 0.0001); } public function testCrossOutMatrix(): void @@ -256,7 +256,7 @@ public function testDot(): void */ public function testFrobeniusNorm(array $matrix, float $norm): void { - self::assertEquals($norm, (new Matrix($matrix))->frobeniusNorm(), '', 0.0001); + self::assertEqualsWithDelta($norm, (new Matrix($matrix))->frobeniusNorm(), 0.0001); } public function dataProviderForFrobeniusNorm(): array diff --git a/tests/Math/Statistic/ANOVATest.php b/tests/Math/Statistic/ANOVATest.php index 2203bf17..acc79d9d 100644 --- a/tests/Math/Statistic/ANOVATest.php +++ b/tests/Math/Statistic/ANOVATest.php @@ -19,7 +19,7 @@ public function testOneWayF(): void $f = [1.47058824, 4.0, 3.0]; - self::assertEquals($f, ANOVA::oneWayF($samples), '', 0.00000001); + self::assertEqualsWithDelta($f, ANOVA::oneWayF($samples), 0.00000001); } public function testOneWayFWithDifferingSizes(): void @@ -29,7 +29,7 @@ public function testOneWayFWithDifferingSizes(): void [[1, 3, 3], [1, 3, 4]], ]; - self::assertEquals([0.6, 2.4, 1.24615385], ANOVA::oneWayF($samples), '', 0.00000001); + self::assertEqualsWithDelta([0.6, 2.4, 1.24615385], ANOVA::oneWayF($samples), 0.00000001); } public function testThrowExceptionOnTooSmallSamples(): void diff --git a/tests/Math/Statistic/CorrelationTest.php b/tests/Math/Statistic/CorrelationTest.php index 2d0334d2..98b72749 100644 --- a/tests/Math/Statistic/CorrelationTest.php +++ b/tests/Math/Statistic/CorrelationTest.php @@ -16,18 +16,18 @@ public function testPearsonCorrelation(): void $delta = 0.001; $x = [9300, 10565, 15000, 15000, 17764, 57000, 65940, 73676, 77006, 93739, 146088, 153260]; $y = [7100, 15500, 4400, 4400, 5900, 4600, 8800, 2000, 2750, 2550, 960, 1025]; - self::assertEquals(-0.641, Correlation::pearson($x, $y), '', $delta); + self::assertEqualsWithDelta(-0.641, Correlation::pearson($x, $y), $delta); //http://www.statisticshowto.com/how-to-compute-pearsons-correlation-coefficients/ $delta = 0.001; $x = [43, 21, 25, 42, 57, 59]; $y = [99, 65, 79, 75, 87, 82]; - self::assertEquals(0.549, Correlation::pearson($x, $y), '', $delta); + self::assertEqualsWithDelta(0.549, Correlation::pearson($x, $y), $delta); $delta = 0.001; $x = [60, 61, 62, 63, 65]; $y = [3.1, 3.6, 3.8, 4, 4.1]; - self::assertEquals(0.911, Correlation::pearson($x, $y), '', $delta); + self::assertEqualsWithDelta(0.911, Correlation::pearson($x, $y), $delta); } public function testThrowExceptionOnInvalidArgumentsForPearsonCorrelation(): void diff --git a/tests/Math/Statistic/CovarianceTest.php b/tests/Math/Statistic/CovarianceTest.php index dd98aeae..fe792c49 100644 --- a/tests/Math/Statistic/CovarianceTest.php +++ b/tests/Math/Statistic/CovarianceTest.php @@ -38,18 +38,18 @@ public function testSimpleCovariance(): void // Calculate only one covariance value: Cov(x, y) $cov1 = Covariance::fromDataset($matrix, 0, 0); - self::assertEquals($cov1, $knownCovariance[0][0], '', $epsilon); + self::assertEqualsWithDelta($cov1, $knownCovariance[0][0], $epsilon); $cov1 = Covariance::fromXYArrays($x, $x); - self::assertEquals($cov1, $knownCovariance[0][0], '', $epsilon); + self::assertEqualsWithDelta($cov1, $knownCovariance[0][0], $epsilon); $cov2 = Covariance::fromDataset($matrix, 0, 1); - self::assertEquals($cov2, $knownCovariance[0][1], '', $epsilon); + self::assertEqualsWithDelta($cov2, $knownCovariance[0][1], $epsilon); $cov2 = Covariance::fromXYArrays($x, $y); - self::assertEquals($cov2, $knownCovariance[0][1], '', $epsilon); + self::assertEqualsWithDelta($cov2, $knownCovariance[0][1], $epsilon); // Second: calculation cov matrix with automatic means for each column $covariance = Covariance::covarianceMatrix($matrix); - self::assertEquals($knownCovariance, $covariance, '', $epsilon); + self::assertEqualsWithDelta($knownCovariance, $covariance, $epsilon); // Thirdly, CovMatrix: Means are precalculated and given to the method $x = array_column($matrix, 0); @@ -58,7 +58,7 @@ public function testSimpleCovariance(): void $meanY = Mean::arithmetic($y); $covariance = Covariance::covarianceMatrix($matrix, [$meanX, $meanY]); - self::assertEquals($knownCovariance, $covariance, '', $epsilon); + self::assertEqualsWithDelta($knownCovariance, $covariance, $epsilon); } public function testThrowExceptionOnEmptyX(): void diff --git a/tests/Math/Statistic/GaussianTest.php b/tests/Math/Statistic/GaussianTest.php index b19c8db6..16b1c5f3 100644 --- a/tests/Math/Statistic/GaussianTest.php +++ b/tests/Math/Statistic/GaussianTest.php @@ -20,9 +20,9 @@ public function testPdf(): void $x = [0, 0.1, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0]; $pdf = [0.3989, 0.3969, 0.3520, 0.2419, 0.1295, 0.0539, 0.0175, 0.0044]; foreach ($x as $i => $v) { - self::assertEquals($pdf[$i], $g->pdf($v), '', $delta); + self::assertEqualsWithDelta($pdf[$i], $g->pdf($v), $delta); - self::assertEquals($pdf[$i], Gaussian::distributionPdf($mean, $std, $v), '', $delta); + self::assertEqualsWithDelta($pdf[$i], Gaussian::distributionPdf($mean, $std, $v), $delta); } } } diff --git a/tests/Math/Statistic/MeanTest.php b/tests/Math/Statistic/MeanTest.php index 6e5d8d78..640225d9 100644 --- a/tests/Math/Statistic/MeanTest.php +++ b/tests/Math/Statistic/MeanTest.php @@ -19,9 +19,9 @@ public function testArithmeticThrowExceptionOnEmptyArray(): void public function testArithmeticMean(): void { $delta = 0.01; - self::assertEquals(3.5, Mean::arithmetic([2, 5]), '', $delta); - self::assertEquals(41.16, Mean::arithmetic([43, 21, 25, 42, 57, 59]), '', $delta); - self::assertEquals(1.7, Mean::arithmetic([0.5, 0.5, 1.5, 2.5, 3.5]), '', $delta); + self::assertEqualsWithDelta(3.5, Mean::arithmetic([2, 5]), $delta); + self::assertEqualsWithDelta(41.16, Mean::arithmetic([43, 21, 25, 42, 57, 59]), $delta); + self::assertEqualsWithDelta(1.7, Mean::arithmetic([0.5, 0.5, 1.5, 2.5, 3.5]), $delta); } public function testMedianThrowExceptionOnEmptyArray(): void diff --git a/tests/Math/Statistic/StandardDeviationTest.php b/tests/Math/Statistic/StandardDeviationTest.php index e18c374b..7f4b435d 100644 --- a/tests/Math/Statistic/StandardDeviationTest.php +++ b/tests/Math/Statistic/StandardDeviationTest.php @@ -15,15 +15,15 @@ public function testStandardDeviationOfPopulationSample(): void //https://pl.wikipedia.org/wiki/Odchylenie_standardowe $delta = 0.001; $population = [5, 6, 8, 9]; - self::assertEquals(1.825, StandardDeviation::population($population), '', $delta); + self::assertEqualsWithDelta(1.825, StandardDeviation::population($population), $delta); //http://www.stat.wmich.edu/s216/book/node126.html $delta = 0.5; $population = [7100, 15500, 4400, 4400, 5900, 4600, 8800, 2000, 2750, 2550, 960, 1025]; - self::assertEquals(4079, StandardDeviation::population($population), '', $delta); + self::assertEqualsWithDelta(4079, StandardDeviation::population($population), $delta); $population = [9300, 10565, 15000, 15000, 17764, 57000, 65940, 73676, 77006, 93739, 146088, 153260]; - self::assertEquals(50989, StandardDeviation::population($population), '', $delta); + self::assertEqualsWithDelta(50989, StandardDeviation::population($population), $delta); } public function testThrowExceptionOnEmptyArrayIfNotSample(): void @@ -43,7 +43,7 @@ public function testThrowExceptionOnTooSmallArray(): void */ public function testSumOfSquares(array $numbers, float $sum): void { - self::assertEquals($sum, StandardDeviation::sumOfSquares($numbers), '', 0.0001); + self::assertEqualsWithDelta($sum, StandardDeviation::sumOfSquares($numbers), 0.0001); } public function dataProviderForSumOfSquaresDeviations(): array diff --git a/tests/Math/Statistic/VarianceTest.php b/tests/Math/Statistic/VarianceTest.php index 310acb60..2cda011a 100644 --- a/tests/Math/Statistic/VarianceTest.php +++ b/tests/Math/Statistic/VarianceTest.php @@ -14,7 +14,7 @@ final class VarianceTest extends TestCase */ public function testVarianceFromInt(array $numbers, float $variance): void { - self::assertEquals($variance, Variance::population($numbers), '', 0.001); + self::assertEqualsWithDelta($variance, Variance::population($numbers), 0.001); } public function dataProviderForPopulationVariance(): array diff --git a/tests/Metric/AccuracyTest.php b/tests/Metric/AccuracyTest.php index 792dd2fb..cbb21c56 100644 --- a/tests/Metric/AccuracyTest.php +++ b/tests/Metric/AccuracyTest.php @@ -51,6 +51,6 @@ public function testAccuracyOnDemoDataset(): void $expected = PHP_VERSION_ID >= 70100 ? 1 : 0.959; - self::assertEquals($expected, $accuracy, '', 0.01); + self::assertEqualsWithDelta($expected, $accuracy, 0.01); } } diff --git a/tests/Metric/ClassificationReportTest.php b/tests/Metric/ClassificationReportTest.php index 3258bc18..3feccc85 100644 --- a/tests/Metric/ClassificationReportTest.php +++ b/tests/Metric/ClassificationReportTest.php @@ -45,11 +45,11 @@ public function testClassificationReportGenerateWithStringLabels(): void 'f1score' => 0.49, // (2/3 + 0 + 4/5) / 3 = 22/45 ]; - self::assertEquals($precision, $report->getPrecision(), '', 0.01); - self::assertEquals($recall, $report->getRecall(), '', 0.01); - self::assertEquals($f1score, $report->getF1score(), '', 0.01); - self::assertEquals($support, $report->getSupport(), '', 0.01); - self::assertEquals($average, $report->getAverage(), '', 0.01); + self::assertEqualsWithDelta($precision, $report->getPrecision(), 0.01); + self::assertEqualsWithDelta($recall, $report->getRecall(), 0.01); + self::assertEqualsWithDelta($f1score, $report->getF1score(), 0.01); + self::assertEqualsWithDelta($support, $report->getSupport(), 0.01); + self::assertEqualsWithDelta($average, $report->getAverage(), 0.01); } public function testClassificationReportGenerateWithNumericLabels(): void @@ -85,11 +85,11 @@ public function testClassificationReportGenerateWithNumericLabels(): void 'f1score' => 0.49, ]; - self::assertEquals($precision, $report->getPrecision(), '', 0.01); - self::assertEquals($recall, $report->getRecall(), '', 0.01); - self::assertEquals($f1score, $report->getF1score(), '', 0.01); - self::assertEquals($support, $report->getSupport(), '', 0.01); - self::assertEquals($average, $report->getAverage(), '', 0.01); + self::assertEqualsWithDelta($precision, $report->getPrecision(), 0.01); + self::assertEqualsWithDelta($recall, $report->getRecall(), 0.01); + self::assertEqualsWithDelta($f1score, $report->getF1score(), 0.01); + self::assertEqualsWithDelta($support, $report->getSupport(), 0.01); + self::assertEqualsWithDelta($average, $report->getAverage(), 0.01); } public function testClassificationReportAverageOutOfRange(): void @@ -114,7 +114,7 @@ public function testClassificationReportMicroAverage(): void 'f1score' => 0.6, // Harmonic mean of precision and recall ]; - self::assertEquals($average, $report->getAverage(), '', 0.01); + self::assertEqualsWithDelta($average, $report->getAverage(), 0.01); } public function testClassificationReportMacroAverage(): void @@ -130,7 +130,7 @@ public function testClassificationReportMacroAverage(): void 'f1score' => 0.49, // (2/3 + 0 + 4/5) / 3 = 22/45 ]; - self::assertEquals($average, $report->getAverage(), '', 0.01); + self::assertEqualsWithDelta($average, $report->getAverage(), 0.01); } public function testClassificationReportWeightedAverage(): void @@ -146,7 +146,7 @@ public function testClassificationReportWeightedAverage(): void 'f1score' => 0.61, // (2/3 * 1 + 0 * 1 + 4/5 * 3) / 5 = 46/75 ]; - self::assertEquals($average, $report->getAverage(), '', 0.01); + self::assertEqualsWithDelta($average, $report->getAverage(), 0.01); } public function testPreventDivideByZeroWhenTruePositiveAndFalsePositiveSumEqualsZero(): void @@ -156,10 +156,10 @@ public function testPreventDivideByZeroWhenTruePositiveAndFalsePositiveSumEquals $report = new ClassificationReport($labels, $predicted); - self::assertEquals([ + self::assertEqualsWithDelta([ 1 => 0.0, 2 => 0.5, - ], $report->getPrecision(), '', 0.01); + ], $report->getPrecision(), 0.01); } public function testPreventDivideByZeroWhenTruePositiveAndFalseNegativeSumEqualsZero(): void @@ -169,11 +169,11 @@ public function testPreventDivideByZeroWhenTruePositiveAndFalseNegativeSumEquals $report = new ClassificationReport($labels, $predicted); - self::assertEquals([ + self::assertEqualsWithDelta([ 1 => 0.0, 2 => 1, 3 => 0, - ], $report->getPrecision(), '', 0.01); + ], $report->getPrecision(), 0.01); } public function testPreventDividedByZeroWhenPredictedLabelsAllNotMatch(): void @@ -183,11 +183,11 @@ public function testPreventDividedByZeroWhenPredictedLabelsAllNotMatch(): void $report = new ClassificationReport($labels, $predicted); - self::assertEquals([ + self::assertEqualsWithDelta([ 'precision' => 0, 'recall' => 0, 'f1score' => 0, - ], $report->getAverage(), '', 0.01); + ], $report->getAverage(), 0.01); } public function testPreventDividedByZeroWhenLabelsAreEmpty(): void @@ -197,10 +197,10 @@ public function testPreventDividedByZeroWhenLabelsAreEmpty(): void $report = new ClassificationReport($labels, $predicted); - self::assertEquals([ + self::assertEqualsWithDelta([ 'precision' => 0, 'recall' => 0, 'f1score' => 0, - ], $report->getAverage(), '', 0.01); + ], $report->getAverage(), 0.01); } } diff --git a/tests/NeuralNetwork/ActivationFunction/GaussianTest.php b/tests/NeuralNetwork/ActivationFunction/GaussianTest.php index 6876fd87..c44ae0f4 100644 --- a/tests/NeuralNetwork/ActivationFunction/GaussianTest.php +++ b/tests/NeuralNetwork/ActivationFunction/GaussianTest.php @@ -18,7 +18,7 @@ public function testGaussianActivationFunction(float $expected, $value): void { $gaussian = new Gaussian(); - self::assertEquals($expected, $gaussian->compute($value), '', 0.001); + self::assertEqualsWithDelta($expected, $gaussian->compute($value), 0.001); } public function gaussianProvider(): array @@ -41,7 +41,7 @@ public function testGaussianDerivative(float $expected, $value): void { $gaussian = new Gaussian(); $activatedValue = $gaussian->compute($value); - self::assertEquals($expected, $gaussian->differentiate($value, $activatedValue), '', 0.001); + self::assertEqualsWithDelta($expected, $gaussian->differentiate($value, $activatedValue), 0.001); } public function gaussianDerivativeProvider(): array diff --git a/tests/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php b/tests/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php index a6a244ff..8865c59c 100644 --- a/tests/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php +++ b/tests/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php @@ -18,7 +18,7 @@ public function testHyperbolicTangentActivationFunction(float $beta, float $expe { $tanh = new HyperbolicTangent($beta); - self::assertEquals($expected, $tanh->compute($value), '', 0.001); + self::assertEqualsWithDelta($expected, $tanh->compute($value), 0.001); } public function tanhProvider(): array @@ -42,7 +42,7 @@ public function testHyperbolicTangentDerivative(float $beta, float $expected, $v { $tanh = new HyperbolicTangent($beta); $activatedValue = $tanh->compute($value); - self::assertEquals($expected, $tanh->differentiate($value, $activatedValue), '', 0.001); + self::assertEqualsWithDelta($expected, $tanh->differentiate($value, $activatedValue), 0.001); } public function tanhDerivativeProvider(): array diff --git a/tests/NeuralNetwork/ActivationFunction/PReLUTest.php b/tests/NeuralNetwork/ActivationFunction/PReLUTest.php index a659204a..4aa069b3 100644 --- a/tests/NeuralNetwork/ActivationFunction/PReLUTest.php +++ b/tests/NeuralNetwork/ActivationFunction/PReLUTest.php @@ -18,7 +18,7 @@ public function testPReLUActivationFunction(float $beta, float $expected, $value { $prelu = new PReLU($beta); - self::assertEquals($expected, $prelu->compute($value), '', 0.001); + self::assertEqualsWithDelta($expected, $prelu->compute($value), 0.001); } public function preluProvider(): array diff --git a/tests/NeuralNetwork/ActivationFunction/SigmoidTest.php b/tests/NeuralNetwork/ActivationFunction/SigmoidTest.php index d98b39c5..d0cf22bb 100644 --- a/tests/NeuralNetwork/ActivationFunction/SigmoidTest.php +++ b/tests/NeuralNetwork/ActivationFunction/SigmoidTest.php @@ -18,7 +18,7 @@ public function testSigmoidActivationFunction(float $beta, float $expected, $val { $sigmoid = new Sigmoid($beta); - self::assertEquals($expected, $sigmoid->compute($value), '', 0.001); + self::assertEqualsWithDelta($expected, $sigmoid->compute($value), 0.001); } public function sigmoidProvider(): array @@ -42,7 +42,7 @@ public function testSigmoidDerivative(float $beta, float $expected, $value): voi { $sigmoid = new Sigmoid($beta); $activatedValue = $sigmoid->compute($value); - self::assertEquals($expected, $sigmoid->differentiate($value, $activatedValue), '', 0.001); + self::assertEqualsWithDelta($expected, $sigmoid->differentiate($value, $activatedValue), 0.001); } public function sigmoidDerivativeProvider(): array diff --git a/tests/NeuralNetwork/Network/LayeredNetworkTest.php b/tests/NeuralNetwork/Network/LayeredNetworkTest.php index a8658311..0a48ee86 100644 --- a/tests/NeuralNetwork/Network/LayeredNetworkTest.php +++ b/tests/NeuralNetwork/Network/LayeredNetworkTest.php @@ -8,8 +8,8 @@ use Phpml\NeuralNetwork\Layer; use Phpml\NeuralNetwork\Network\LayeredNetwork; use Phpml\NeuralNetwork\Node\Input; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use PHPUnit_Framework_MockObject_MockObject; class LayeredNetworkTest extends TestCase { @@ -56,7 +56,7 @@ public function testSetInputAndGetOutputWithCustomActivationFunctions(): void } /** - * @return LayeredNetwork|PHPUnit_Framework_MockObject_MockObject + * @return LayeredNetwork|MockObject */ private function getLayeredNetworkMock() { @@ -64,7 +64,7 @@ private function getLayeredNetworkMock() } /** - * @return ActivationFunction|PHPUnit_Framework_MockObject_MockObject + * @return ActivationFunction|MockObject */ private function getActivationFunctionMock() { diff --git a/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php b/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php index d7bf7e55..14951365 100644 --- a/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php +++ b/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php @@ -9,8 +9,8 @@ use Phpml\NeuralNetwork\Layer; use Phpml\NeuralNetwork\Network\MultilayerPerceptron; use Phpml\NeuralNetwork\Node\Neuron; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use PHPUnit_Framework_MockObject_MockObject; class MultilayerPerceptronTest extends TestCase { @@ -106,7 +106,7 @@ public function testLearningRateSetterWithLayerObject(): void } /** - * @return ActivationFunction|PHPUnit_Framework_MockObject_MockObject + * @return ActivationFunction|MockObject */ private function getActivationFunctionMock() { diff --git a/tests/NeuralNetwork/Node/Neuron/SynapseTest.php b/tests/NeuralNetwork/Node/Neuron/SynapseTest.php index 1e33f348..1374eadd 100644 --- a/tests/NeuralNetwork/Node/Neuron/SynapseTest.php +++ b/tests/NeuralNetwork/Node/Neuron/SynapseTest.php @@ -6,8 +6,8 @@ use Phpml\NeuralNetwork\Node\Neuron; use Phpml\NeuralNetwork\Node\Neuron\Synapse; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use PHPUnit_Framework_MockObject_MockObject; class SynapseTest extends TestCase { @@ -43,7 +43,7 @@ public function testSynapseWeightChange(): void /** * @param int|float $output * - * @return Neuron|PHPUnit_Framework_MockObject_MockObject + * @return Neuron|MockObject */ private function getNodeMock($output = 1) { diff --git a/tests/NeuralNetwork/Node/NeuronTest.php b/tests/NeuralNetwork/Node/NeuronTest.php index b1a77a80..448c885b 100644 --- a/tests/NeuralNetwork/Node/NeuronTest.php +++ b/tests/NeuralNetwork/Node/NeuronTest.php @@ -7,8 +7,8 @@ use Phpml\NeuralNetwork\ActivationFunction\BinaryStep; use Phpml\NeuralNetwork\Node\Neuron; use Phpml\NeuralNetwork\Node\Neuron\Synapse; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use PHPUnit_Framework_MockObject_MockObject; class NeuronTest extends TestCase { @@ -22,7 +22,7 @@ public function testNeuronInitialization(): void public function testNeuronActivationFunction(): void { - /** @var BinaryStep|PHPUnit_Framework_MockObject_MockObject $activationFunction */ + /** @var BinaryStep|MockObject $activationFunction */ $activationFunction = $this->getMockBuilder(BinaryStep::class)->getMock(); $activationFunction->method('compute')->with(0)->willReturn($output = 0.69); @@ -37,7 +37,7 @@ public function testNeuronWithSynapse(): void $neuron->addSynapse($synapse = $this->getSynapseMock()); self::assertEquals([$synapse], $neuron->getSynapses()); - self::assertEquals(0.88, $neuron->getOutput(), '', 0.01); + self::assertEqualsWithDelta(0.88, $neuron->getOutput(), 0.01); } public function testNeuronRefresh(): void @@ -46,15 +46,15 @@ public function testNeuronRefresh(): void $neuron->getOutput(); $neuron->addSynapse($this->getSynapseMock()); - self::assertEquals(0.5, $neuron->getOutput(), '', 0.01); + self::assertEqualsWithDelta(0.5, $neuron->getOutput(), 0.01); $neuron->reset(); - self::assertEquals(0.88, $neuron->getOutput(), '', 0.01); + self::assertEqualsWithDelta(0.88, $neuron->getOutput(), 0.01); } /** - * @return Synapse|PHPUnit_Framework_MockObject_MockObject + * @return Synapse|MockObject */ private function getSynapseMock(int $output = 2) { diff --git a/tests/PipelineTest.php b/tests/PipelineTest.php index 0ba91c66..31c4f36d 100644 --- a/tests/PipelineTest.php +++ b/tests/PipelineTest.php @@ -115,7 +115,7 @@ public function testPipelineTransformersWithTargets(): void $pipeline = new Pipeline([$selector = new SelectKBest(2)], new SVC()); $pipeline->train($samples, $targets); - self::assertEquals([1.47058823, 4.0, 3.0], $selector->scores(), '', 0.00000001); + self::assertEqualsWithDelta([1.47058823, 4.0, 3.0], $selector->scores(), 0.00000001); self::assertEquals(['b'], $pipeline->predict([[1, 3, 5]])); } diff --git a/tests/Preprocessing/ImputerTest.php b/tests/Preprocessing/ImputerTest.php index dcbb8073..b410854c 100644 --- a/tests/Preprocessing/ImputerTest.php +++ b/tests/Preprocessing/ImputerTest.php @@ -32,7 +32,7 @@ public function testComplementsMissingValuesWithMeanStrategyOnColumnAxis(): void $imputer = new Imputer(null, new MeanStrategy(), Imputer::AXIS_COLUMN, $data); $imputer->transform($data); - self::assertEquals($imputeData, $data, '', $delta = 0.01); + self::assertEqualsWithDelta($imputeData, $data, $delta = 0.01); } public function testComplementsMissingValuesWithMeanStrategyOnRowAxis(): void @@ -54,7 +54,7 @@ public function testComplementsMissingValuesWithMeanStrategyOnRowAxis(): void $imputer = new Imputer(null, new MeanStrategy(), Imputer::AXIS_ROW, $data); $imputer->transform($data); - self::assertEquals($imputeData, $data, '', $delta = 0.01); + self::assertEqualsWithDelta($imputeData, $data, $delta = 0.01); } public function testComplementsMissingValuesWithMediaStrategyOnColumnAxis(): void @@ -76,7 +76,7 @@ public function testComplementsMissingValuesWithMediaStrategyOnColumnAxis(): voi $imputer = new Imputer(null, new MedianStrategy(), Imputer::AXIS_COLUMN, $data); $imputer->transform($data); - self::assertEquals($imputeData, $data, '', $delta = 0.01); + self::assertEqualsWithDelta($imputeData, $data, $delta = 0.01); } public function testComplementsMissingValuesWithMediaStrategyOnRowAxis(): void @@ -98,7 +98,7 @@ public function testComplementsMissingValuesWithMediaStrategyOnRowAxis(): void $imputer = new Imputer(null, new MedianStrategy(), Imputer::AXIS_ROW, $data); $imputer->transform($data); - self::assertEquals($imputeData, $data, '', $delta = 0.01); + self::assertEqualsWithDelta($imputeData, $data, $delta = 0.01); } public function testComplementsMissingValuesWithMostFrequentStrategyOnColumnAxis(): void @@ -172,7 +172,7 @@ public function testImputerWorksOnFitSamples(): void $imputer = new Imputer(null, new MeanStrategy(), Imputer::AXIS_COLUMN, $trainData); $imputer->transform($data); - self::assertEquals($imputeData, $data, '', $delta = 0.01); + self::assertEqualsWithDelta($imputeData, $data, $delta = 0.01); } public function testThrowExceptionWhenTryingToTransformWithoutTrainSamples(): void diff --git a/tests/Preprocessing/NormalizerTest.php b/tests/Preprocessing/NormalizerTest.php index 53b07d86..ed6b2c5a 100644 --- a/tests/Preprocessing/NormalizerTest.php +++ b/tests/Preprocessing/NormalizerTest.php @@ -33,7 +33,7 @@ public function testNormalizeSamplesWithL2Norm(): void $normalizer = new Normalizer(); $normalizer->transform($samples); - self::assertEquals($normalized, $samples, '', $delta = 0.01); + self::assertEqualsWithDelta($normalized, $samples, $delta = 0.01); } public function testNormalizeSamplesWithL1Norm(): void @@ -53,7 +53,7 @@ public function testNormalizeSamplesWithL1Norm(): void $normalizer = new Normalizer(Normalizer::NORM_L1); $normalizer->transform($samples); - self::assertEquals($normalized, $samples, '', $delta = 0.01); + self::assertEqualsWithDelta($normalized, $samples, $delta = 0.01); } public function testFitNotChangeNormalizerBehavior(): void @@ -73,11 +73,11 @@ public function testFitNotChangeNormalizerBehavior(): void $normalizer = new Normalizer(); $normalizer->transform($samples); - self::assertEquals($normalized, $samples, '', $delta = 0.01); + self::assertEqualsWithDelta($normalized, $samples, $delta = 0.01); $normalizer->fit($samples); - self::assertEquals($normalized, $samples, '', $delta = 0.01); + self::assertEqualsWithDelta($normalized, $samples, $delta = 0.01); } public function testL1NormWithZeroSumCondition(): void @@ -97,7 +97,7 @@ public function testL1NormWithZeroSumCondition(): void $normalizer = new Normalizer(Normalizer::NORM_L1); $normalizer->transform($samples); - self::assertEquals($normalized, $samples, '', $delta = 0.01); + self::assertEqualsWithDelta($normalized, $samples, $delta = 0.01); } public function testStandardNorm(): void diff --git a/tests/Regression/LeastSquaresTest.php b/tests/Regression/LeastSquaresTest.php index 1142a37e..4d79c4f9 100644 --- a/tests/Regression/LeastSquaresTest.php +++ b/tests/Regression/LeastSquaresTest.php @@ -21,7 +21,7 @@ public function testPredictSingleFeatureSamples(): void $regression = new LeastSquares(); $regression->train($samples, $targets); - self::assertEquals(4.06, $regression->predict([64]), '', $delta); + self::assertEqualsWithDelta(4.06, $regression->predict([64]), $delta); //http://www.stat.wmich.edu/s216/book/node127.html $samples = [[9300], [10565], [15000], [15000], [17764], [57000], [65940], [73676], [77006], [93739], [146088], [153260]]; @@ -30,11 +30,11 @@ public function testPredictSingleFeatureSamples(): void $regression = new LeastSquares(); $regression->train($samples, $targets); - self::assertEquals(7659.35, $regression->predict([9300]), '', $delta); - self::assertEquals(5213.81, $regression->predict([57000]), '', $delta); - self::assertEquals(4188.13, $regression->predict([77006]), '', $delta); - self::assertEquals(7659.35, $regression->predict([9300]), '', $delta); - self::assertEquals(278.66, $regression->predict([153260]), '', $delta); + self::assertEqualsWithDelta(7659.35, $regression->predict([9300]), $delta); + self::assertEqualsWithDelta(5213.81, $regression->predict([57000]), $delta); + self::assertEqualsWithDelta(4188.13, $regression->predict([77006]), $delta); + self::assertEqualsWithDelta(7659.35, $regression->predict([9300]), $delta); + self::assertEqualsWithDelta(278.66, $regression->predict([153260]), $delta); } public function testPredictSingleFeatureSamplesWithMatrixTargets(): void @@ -48,7 +48,7 @@ public function testPredictSingleFeatureSamplesWithMatrixTargets(): void $regression = new LeastSquares(); $regression->train($samples, $targets); - self::assertEquals(4.06, $regression->predict([64]), '', $delta); + self::assertEqualsWithDelta(4.06, $regression->predict([64]), $delta); } public function testPredictMultiFeaturesSamples(): void @@ -62,10 +62,10 @@ public function testPredictMultiFeaturesSamples(): void $regression = new LeastSquares(); $regression->train($samples, $targets); - self::assertEquals(-800614.957, $regression->getIntercept(), '', $delta); - self::assertEquals([-0.0327, 404.14], $regression->getCoefficients(), '', $delta); - self::assertEquals(4094.82, $regression->predict([60000, 1996]), '', $delta); - self::assertEquals(5711.40, $regression->predict([60000, 2000]), '', $delta); + self::assertEqualsWithDelta(-800614.957, $regression->getIntercept(), $delta); + self::assertEqualsWithDelta([-0.0327, 404.14], $regression->getCoefficients(), $delta); + self::assertEqualsWithDelta(4094.82, $regression->predict([60000, 1996]), $delta); + self::assertEqualsWithDelta(5711.40, $regression->predict([60000, 2000]), $delta); } public function testSaveAndRestore(): void diff --git a/tests/Regression/SVRTest.php b/tests/Regression/SVRTest.php index 89099c0b..962a713b 100644 --- a/tests/Regression/SVRTest.php +++ b/tests/Regression/SVRTest.php @@ -21,7 +21,7 @@ public function testPredictSingleFeatureSamples(): void $regression = new SVR(Kernel::LINEAR); $regression->train($samples, $targets); - self::assertEquals(4.03, $regression->predict([64]), '', $delta); + self::assertEqualsWithDelta(4.03, $regression->predict([64]), $delta); } public function testPredictMultiFeaturesSamples(): void @@ -34,7 +34,7 @@ public function testPredictMultiFeaturesSamples(): void $regression = new SVR(Kernel::LINEAR); $regression->train($samples, $targets); - self::assertEquals([4109.82, 4112.28], $regression->predict([[60000, 1996], [60000, 2000]]), '', $delta); + self::assertEqualsWithDelta([4109.82, 4112.28], $regression->predict([[60000, 1996], [60000, 2000]]), $delta); } public function testSaveAndRestore(): void diff --git a/tests/SupportVectorMachine/SupportVectorMachineTest.php b/tests/SupportVectorMachine/SupportVectorMachineTest.php index e3bbc85c..b7b1942b 100644 --- a/tests/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/SupportVectorMachine/SupportVectorMachineTest.php @@ -59,8 +59,8 @@ public function testTrainCSVCModelWithProbabilityEstimate(): void ); $svm->train($samples, $labels); - self::assertContains(PHP_EOL.'probA ', $svm->getModel()); - self::assertContains(PHP_EOL.'probB ', $svm->getModel()); + self::assertStringContainsString(PHP_EOL.'probA ', $svm->getModel()); + self::assertStringContainsString(PHP_EOL.'probB ', $svm->getModel()); } public function testPredictSampleWithLinearKernel(): void From f6aa1a59b0525b8fca3d2786d661ab3e70904016 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 12 Apr 2019 07:49:30 +0200 Subject: [PATCH 306/328] Remove phpunit readAttributes deprecated methods (#372) --- ecs.yml | 3 +- src/Helper/Optimizer/Optimizer.php | 5 ++++ .../Network/MultilayerPerceptron.php | 10 +++++++ .../Training/Backpropagation.php | 5 ++++ tests/Helper/Optimizer/OptimizerTest.php | 4 +-- .../Network/MultilayerPerceptronTest.php | 30 ++++++++----------- 6 files changed, 35 insertions(+), 22 deletions(-) diff --git a/ecs.yml b/ecs.yml index b19571e5..21b30e9b 100644 --- a/ecs.yml +++ b/ecs.yml @@ -18,7 +18,8 @@ services: PhpCsFixer\Fixer\Operator\BinaryOperatorSpacesFixer: align_double_arrow: false align_equals: false - + PhpCsFixer\Fixer\PhpUnit\PhpUnitTestCaseStaticMethodCallsFixer: + call_type: 'self' # phpdoc PhpCsFixer\Fixer\Phpdoc\PhpdocSeparationFixer: ~ PhpCsFixer\Fixer\Phpdoc\PhpdocAlignFixer: ~ diff --git a/src/Helper/Optimizer/Optimizer.php b/src/Helper/Optimizer/Optimizer.php index 99a82ab3..54331e9e 100644 --- a/src/Helper/Optimizer/Optimizer.php +++ b/src/Helper/Optimizer/Optimizer.php @@ -48,6 +48,11 @@ public function setTheta(array $theta): self return $this; } + public function theta(): array + { + return $this->theta; + } + /** * Executes the optimization with the given samples & targets * and returns the weights diff --git a/src/NeuralNetwork/Network/MultilayerPerceptron.php b/src/NeuralNetwork/Network/MultilayerPerceptron.php index e9e6b516..beefb1e2 100644 --- a/src/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/NeuralNetwork/Network/MultilayerPerceptron.php @@ -129,6 +129,16 @@ public function getOutput(): array return $result; } + public function getLearningRate(): float + { + return $this->learningRate; + } + + public function getBackpropagation(): Backpropagation + { + return $this->backpropagation; + } + /** * @param mixed $target */ diff --git a/src/NeuralNetwork/Training/Backpropagation.php b/src/NeuralNetwork/Training/Backpropagation.php index 6c9af981..69a3e2a8 100644 --- a/src/NeuralNetwork/Training/Backpropagation.php +++ b/src/NeuralNetwork/Training/Backpropagation.php @@ -34,6 +34,11 @@ public function setLearningRate(float $learningRate): void $this->learningRate = $learningRate; } + public function getLearningRate(): float + { + return $this->learningRate; + } + /** * @param mixed $targetClass */ diff --git a/tests/Helper/Optimizer/OptimizerTest.php b/tests/Helper/Optimizer/OptimizerTest.php index 97af2d24..184f6c7c 100644 --- a/tests/Helper/Optimizer/OptimizerTest.php +++ b/tests/Helper/Optimizer/OptimizerTest.php @@ -26,9 +26,7 @@ public function testSetTheta(): void $optimizer = $this->getMockForAbstractClass(Optimizer::class, [2]); $object = $optimizer->setTheta([0.3, 1]); - $theta = self::getObjectAttribute($optimizer, 'theta'); - self::assertSame($object, $optimizer); - self::assertSame([0.3, 1], $theta); + self::assertSame([0.3, 1], $object->theta()); } } diff --git a/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php b/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php index 14951365..6123f9ba 100644 --- a/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php +++ b/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php @@ -55,14 +55,12 @@ public function testLearningRateSetter(): void [5, [3], [0, 1], 1000, null, 0.42] ); - self::assertEquals(0.42, self::readAttribute($mlp, 'learningRate')); - $backprop = self::readAttribute($mlp, 'backpropagation'); - self::assertEquals(0.42, self::readAttribute($backprop, 'learningRate')); + self::assertEquals(0.42, $mlp->getLearningRate()); + self::assertEquals(0.42, $mlp->getBackpropagation()->getLearningRate()); $mlp->setLearningRate(0.24); - self::assertEquals(0.24, self::readAttribute($mlp, 'learningRate')); - $backprop = self::readAttribute($mlp, 'backpropagation'); - self::assertEquals(0.24, self::readAttribute($backprop, 'learningRate')); + self::assertEquals(0.24, $mlp->getLearningRate()); + self::assertEquals(0.24, $mlp->getBackpropagation()->getLearningRate()); } public function testLearningRateSetterWithCustomActivationFunctions(): void @@ -75,14 +73,12 @@ public function testLearningRateSetterWithCustomActivationFunctions(): void [5, [[3, $activation_function], [5, $activation_function]], [0, 1], 1000, null, 0.42] ); - self::assertEquals(0.42, self::readAttribute($mlp, 'learningRate')); - $backprop = self::readAttribute($mlp, 'backpropagation'); - self::assertEquals(0.42, self::readAttribute($backprop, 'learningRate')); + self::assertEquals(0.42, $mlp->getLearningRate()); + self::assertEquals(0.42, $mlp->getBackpropagation()->getLearningRate()); $mlp->setLearningRate(0.24); - self::assertEquals(0.24, self::readAttribute($mlp, 'learningRate')); - $backprop = self::readAttribute($mlp, 'backpropagation'); - self::assertEquals(0.24, self::readAttribute($backprop, 'learningRate')); + self::assertEquals(0.24, $mlp->getLearningRate()); + self::assertEquals(0.24, $mlp->getBackpropagation()->getLearningRate()); } public function testLearningRateSetterWithLayerObject(): void @@ -95,14 +91,12 @@ public function testLearningRateSetterWithLayerObject(): void [5, [new Layer(3, Neuron::class, $activation_function), new Layer(5, Neuron::class, $activation_function)], [0, 1], 1000, null, 0.42] ); - self::assertEquals(0.42, self::readAttribute($mlp, 'learningRate')); - $backprop = self::readAttribute($mlp, 'backpropagation'); - self::assertEquals(0.42, self::readAttribute($backprop, 'learningRate')); + self::assertEquals(0.42, $mlp->getLearningRate()); + self::assertEquals(0.42, $mlp->getBackpropagation()->getLearningRate()); $mlp->setLearningRate(0.24); - self::assertEquals(0.24, self::readAttribute($mlp, 'learningRate')); - $backprop = self::readAttribute($mlp, 'backpropagation'); - self::assertEquals(0.24, self::readAttribute($backprop, 'learningRate')); + self::assertEquals(0.24, $mlp->getLearningRate()); + self::assertEquals(0.24, $mlp->getBackpropagation()->getLearningRate()); } /** From 8544cf7083bb90214c251c394a18a9f2e36b9e22 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Fri, 10 May 2019 23:10:05 +0200 Subject: [PATCH 307/328] Implement regression metrics (#373) --- src/Metric/Regression.php | 86 +++++++++++++++++++++++++++++ tests/Metric/RegressionTest.php | 97 +++++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 src/Metric/Regression.php create mode 100644 tests/Metric/RegressionTest.php diff --git a/src/Metric/Regression.php b/src/Metric/Regression.php new file mode 100644 index 00000000..9f0e0249 --- /dev/null +++ b/src/Metric/Regression.php @@ -0,0 +1,86 @@ + $target) { + $errors[] = (($target - $predictions[$index]) ** 2); + } + + return Mean::arithmetic($errors); + } + + public static function meanSquaredLogarithmicError(array $targets, array $predictions): float + { + self::assertCountEquals($targets, $predictions); + + $errors = []; + foreach ($targets as $index => $target) { + $errors[] = (log(1 + $target) - log(1 + $predictions[$index])) ** 2; + } + + return Mean::arithmetic($errors); + } + + public static function meanAbsoluteError(array $targets, array $predictions): float + { + self::assertCountEquals($targets, $predictions); + + $errors = []; + foreach ($targets as $index => $target) { + $errors[] = abs($target - $predictions[$index]); + } + + return Mean::arithmetic($errors); + } + + public static function medianAbsoluteError(array $targets, array $predictions): float + { + self::assertCountEquals($targets, $predictions); + + $errors = []; + foreach ($targets as $index => $target) { + $errors[] = abs($target - $predictions[$index]); + } + + return (float) Mean::median($errors); + } + + public static function r2Score(array $targets, array $predictions): float + { + self::assertCountEquals($targets, $predictions); + + return Correlation::pearson($targets, $predictions) ** 2; + } + + public static function maxError(array $targets, array $predictions): float + { + self::assertCountEquals($targets, $predictions); + + $errors = []; + foreach ($targets as $index => $target) { + $errors[] = abs($target - $predictions[$index]); + } + + return (float) max($errors); + } + + private static function assertCountEquals(array &$targets, array &$predictions): void + { + if (count($targets) !== count($predictions)) { + throw new InvalidArgumentException('Targets count must be equal with predictions count'); + } + } +} diff --git a/tests/Metric/RegressionTest.php b/tests/Metric/RegressionTest.php new file mode 100644 index 00000000..1f75cfd9 --- /dev/null +++ b/tests/Metric/RegressionTest.php @@ -0,0 +1,97 @@ + Date: Sun, 12 May 2019 20:04:39 +0200 Subject: [PATCH 308/328] Implement DecisionTreeRegressor (#375) --- src/Regression/DecisionTreeRegressor.php | 144 ++++++++++++++ src/Tree/CART.php | 176 ++++++++++++++++++ src/Tree/Node.php | 9 + src/Tree/Node/AverageNode.php | 45 +++++ src/Tree/Node/BinaryNode.php | 83 +++++++++ src/Tree/Node/DecisionNode.php | 107 +++++++++++ src/Tree/Node/LeafNode.php | 9 + src/Tree/Node/PurityNode.php | 14 ++ .../Regression/DecisionTreeRegressorTest.php | 68 +++++++ tests/Tree/Node/BinaryNodeTest.php | 47 +++++ tests/Tree/Node/DecisionNodeTest.php | 57 ++++++ 11 files changed, 759 insertions(+) create mode 100644 src/Regression/DecisionTreeRegressor.php create mode 100644 src/Tree/CART.php create mode 100644 src/Tree/Node.php create mode 100644 src/Tree/Node/AverageNode.php create mode 100644 src/Tree/Node/BinaryNode.php create mode 100644 src/Tree/Node/DecisionNode.php create mode 100644 src/Tree/Node/LeafNode.php create mode 100644 src/Tree/Node/PurityNode.php create mode 100644 tests/Regression/DecisionTreeRegressorTest.php create mode 100644 tests/Tree/Node/BinaryNodeTest.php create mode 100644 tests/Tree/Node/DecisionNodeTest.php diff --git a/src/Regression/DecisionTreeRegressor.php b/src/Regression/DecisionTreeRegressor.php new file mode 100644 index 00000000..6260a030 --- /dev/null +++ b/src/Regression/DecisionTreeRegressor.php @@ -0,0 +1,144 @@ +columns = range(0, $features - 1); + $this->maxFeatures = $this->maxFeatures ?? (int) round(sqrt($features)); + + $this->grow($samples, $targets); + + $this->columns = []; + } + + public function predict(array $samples) + { + if ($this->bare()) { + throw new InvalidOperationException('Regressor must be trained first'); + } + + $predictions = []; + + foreach ($samples as $sample) { + $node = $this->search($sample); + + $predictions[] = $node instanceof AverageNode + ? $node->outcome() + : null; + } + + return $predictions; + } + + protected function split(array $samples, array $targets): DecisionNode + { + $bestVariance = INF; + $bestColumn = $bestValue = null; + $bestGroups = []; + + shuffle($this->columns); + + foreach (array_slice($this->columns, 0, $this->maxFeatures) as $column) { + $values = array_unique(array_column($samples, $column)); + + foreach ($values as $value) { + $groups = $this->partition($column, $value, $samples, $targets); + + $variance = $this->splitImpurity($groups); + + if ($variance < $bestVariance) { + $bestColumn = $column; + $bestValue = $value; + $bestGroups = $groups; + $bestVariance = $variance; + } + + if ($variance <= $this->tolerance) { + break 2; + } + } + } + + return new DecisionNode($bestColumn, $bestValue, $bestGroups, $bestVariance); + } + + protected function terminate(array $targets): BinaryNode + { + return new AverageNode(Mean::arithmetic($targets), Variance::population($targets), count($targets)); + } + + protected function splitImpurity(array $groups): float + { + $samplesCount = (int) array_sum(array_map(static function (array $group) { + return count($group[0]); + }, $groups)); + + $impurity = 0.; + + foreach ($groups as $group) { + $k = count($group[1]); + + if ($k < 2) { + continue 1; + } + + $variance = Variance::population($group[1]); + + $impurity += ($k / $samplesCount) * $variance; + } + + return $impurity; + } + + /** + * @param int|float $value + */ + private function partition(int $column, $value, array $samples, array $targets): array + { + $leftSamples = $leftTargets = $rightSamples = $rightTargets = []; + foreach ($samples as $index => $sample) { + if ($sample[$column] < $value) { + $leftSamples[] = $sample; + $leftTargets[] = $targets[$index]; + } else { + $rightSamples[] = $sample; + $rightTargets[] = $targets[$index]; + } + } + + return [ + [$leftSamples, $leftTargets], + [$rightSamples, $rightTargets], + ]; + } +} diff --git a/src/Tree/CART.php b/src/Tree/CART.php new file mode 100644 index 00000000..5ed15044 --- /dev/null +++ b/src/Tree/CART.php @@ -0,0 +1,176 @@ +maxDepth = $maxDepth; + $this->maxLeafSize = $maxLeafSize; + $this->minPurityIncrease = $minPurityIncrease; + } + + public function root(): ?DecisionNode + { + return $this->root; + } + + public function height(): int + { + return $this->root !== null ? $this->root->height() : 0; + } + + public function balance(): int + { + return $this->root !== null ? $this->root->balance() : 0; + } + + public function bare(): bool + { + return $this->root === null; + } + + public function grow(array $samples, array $targets): void + { + $this->featureCount = count($samples[0]); + $depth = 1; + $this->root = $this->split($samples, $targets); + $stack = [[$this->root, $depth]]; + + while ($stack) { + [$current, $depth] = array_pop($stack) ?? []; + + [$left, $right] = $current->groups(); + + $current->cleanup(); + + $depth++; + + if ($left === [] || $right === []) { + $node = $this->terminate(array_merge($left[1], $right[1])); + + $current->attachLeft($node); + $current->attachRight($node); + + continue 1; + } + + if ($depth >= $this->maxDepth) { + $current->attachLeft($this->terminate($left[1])); + $current->attachRight($this->terminate($right[1])); + + continue 1; + } + + if (count($left[1]) > $this->maxLeafSize) { + $node = $this->split($left[0], $left[1]); + + if ($node->purityIncrease() + 1e-8 > $this->minPurityIncrease) { + $current->attachLeft($node); + + $stack[] = [$node, $depth]; + } else { + $current->attachLeft($this->terminate($left[1])); + } + } else { + $current->attachLeft($this->terminate($left[1])); + } + + if (count($right[1]) > $this->maxLeafSize) { + $node = $this->split($right[0], $right[1]); + + if ($node->purityIncrease() + 1e-8 > $this->minPurityIncrease) { + $current->attachRight($node); + + $stack[] = [$node, $depth]; + } else { + $current->attachRight($this->terminate($right[1])); + } + } else { + $current->attachRight($this->terminate($right[1])); + } + } + } + + public function search(array $sample): ?BinaryNode + { + $current = $this->root; + + while ($current) { + if ($current instanceof DecisionNode) { + $value = $current->value(); + + if (is_string($value)) { + if ($sample[$current->column()] === $value) { + $current = $current->left(); + } else { + $current = $current->right(); + } + } else { + if ($sample[$current->column()] < $value) { + $current = $current->left(); + } else { + $current = $current->right(); + } + } + + continue 1; + } + + if ($current instanceof LeafNode) { + break 1; + } + } + + return $current; + } + + abstract protected function split(array $samples, array $targets): DecisionNode; + + abstract protected function terminate(array $targets): BinaryNode; +} diff --git a/src/Tree/Node.php b/src/Tree/Node.php new file mode 100644 index 00000000..3176b625 --- /dev/null +++ b/src/Tree/Node.php @@ -0,0 +1,9 @@ +outcome = $outcome; + $this->impurity = $impurity; + $this->samplesCount = $samplesCount; + } + + public function outcome(): float + { + return $this->outcome; + } + + public function impurity(): float + { + return $this->impurity; + } + + public function samplesCount(): int + { + return $this->samplesCount; + } +} diff --git a/src/Tree/Node/BinaryNode.php b/src/Tree/Node/BinaryNode.php new file mode 100644 index 00000000..c6797b5b --- /dev/null +++ b/src/Tree/Node/BinaryNode.php @@ -0,0 +1,83 @@ +parent; + } + + public function left(): ?self + { + return $this->left; + } + + public function right(): ?self + { + return $this->right; + } + + public function height(): int + { + return 1 + max($this->left !== null ? $this->left->height() : 0, $this->right !== null ? $this->right->height() : 0); + } + + public function balance(): int + { + return ($this->right !== null ? $this->right->height() : 0) - ($this->left !== null ? $this->left->height() : 0); + } + + public function setParent(?self $node = null): void + { + $this->parent = $node; + } + + public function attachLeft(self $node): void + { + $node->setParent($this); + $this->left = $node; + } + + public function detachLeft(): void + { + if ($this->left !== null) { + $this->left->setParent(); + $this->left = null; + } + } + + public function attachRight(self $node): void + { + $node->setParent($this); + $this->right = $node; + } + + public function detachRight(): void + { + if ($this->right !== null) { + $this->right->setParent(); + $this->right = null; + } + } +} diff --git a/src/Tree/Node/DecisionNode.php b/src/Tree/Node/DecisionNode.php new file mode 100644 index 00000000..f621fed4 --- /dev/null +++ b/src/Tree/Node/DecisionNode.php @@ -0,0 +1,107 @@ +column = $column; + $this->value = $value; + $this->groups = $groups; + $this->impurity = $impurity; + $this->samplesCount = (int) array_sum(array_map(function (array $group) { + return count($group[0]); + }, $groups)); + } + + public function column(): int + { + return $this->column; + } + + /** + * @return mixed + */ + public function value() + { + return $this->value; + } + + public function groups(): array + { + return $this->groups; + } + + public function impurity(): float + { + return $this->impurity; + } + + public function samplesCount(): int + { + return $this->samplesCount; + } + + public function purityIncrease(): float + { + $impurity = $this->impurity; + + if ($this->left() instanceof PurityNode) { + $impurity -= $this->left()->impurity() + * ($this->left()->samplesCount() / $this->samplesCount); + } + + if ($this->right() instanceof PurityNode) { + $impurity -= $this->right()->impurity() + * ($this->right()->samplesCount() / $this->samplesCount); + } + + return $impurity; + } + + public function cleanup(): void + { + $this->groups = [[], []]; + } +} diff --git a/src/Tree/Node/LeafNode.php b/src/Tree/Node/LeafNode.php new file mode 100644 index 00000000..ebb848e9 --- /dev/null +++ b/src/Tree/Node/LeafNode.php @@ -0,0 +1,9 @@ +train($samples, $targets); + + self::assertEqualsWithDelta([4.05], $regression->predict([[64]]), $delta); + + $samples = [[9300], [10565], [15000], [15000], [17764], [57000], [65940], [73676], [77006], [93739], [146088], [153260]]; + $targets = [7100, 15500, 4400, 4400, 5900, 4600, 8800, 2000, 2750, 2550, 960, 1025]; + + $regression = new DecisionTreeRegressor(); + $regression->train($samples, $targets); + + self::assertEqualsWithDelta([11300.0], $regression->predict([[9300]]), $delta); + self::assertEqualsWithDelta([5250.0], $regression->predict([[57000]]), $delta); + self::assertEqualsWithDelta([2433.33], $regression->predict([[77006]]), $delta); + self::assertEqualsWithDelta([11300.0], $regression->predict([[9300]]), $delta); + self::assertEqualsWithDelta([992.5], $regression->predict([[153260]]), $delta); + } + + public function testPreventPredictWhenNotTrained(): void + { + $regression = new DecisionTreeRegressor(); + + $this->expectException(InvalidOperationException::class); + + $regression->predict([[1]]); + } + + public function testSaveAndRestore(): void + { + $samples = [[60], [61], [62], [63], [65]]; + $targets = [3.1, 3.6, 3.8, 4, 4.1]; + + $regression = new DecisionTreeRegressor(4); + $regression->train($samples, $targets); + + $testSamples = [[9300], [10565], [15000]]; + $predicted = $regression->predict($testSamples); + + $filename = 'least-squares-test-'.random_int(100, 999).'-'.uniqid('', false); + $filepath = (string) tempnam(sys_get_temp_dir(), $filename); + $modelManager = new ModelManager(); + $modelManager->saveToFile($regression, $filepath); + + $restoredRegression = $modelManager->restoreFromFile($filepath); + self::assertEquals($regression, $restoredRegression); + self::assertEquals($predicted, $restoredRegression->predict($testSamples)); + } +} diff --git a/tests/Tree/Node/BinaryNodeTest.php b/tests/Tree/Node/BinaryNodeTest.php new file mode 100644 index 00000000..43db4181 --- /dev/null +++ b/tests/Tree/Node/BinaryNodeTest.php @@ -0,0 +1,47 @@ +height()); + self::assertEquals(0, $node->balance()); + } + + public function testAttachDetachLeft(): void + { + $node = new BinaryNode(); + $node->attachLeft(new BinaryNode()); + + self::assertEquals(2, $node->height()); + self::assertEquals(-1, $node->balance()); + + $node->detachLeft(); + + self::assertEquals(1, $node->height()); + self::assertEquals(0, $node->balance()); + } + + public function testAttachDetachRight(): void + { + $node = new BinaryNode(); + $node->attachRight(new BinaryNode()); + + self::assertEquals(2, $node->height()); + self::assertEquals(1, $node->balance()); + + $node->detachRight(); + + self::assertEquals(1, $node->height()); + self::assertEquals(0, $node->balance()); + } +} diff --git a/tests/Tree/Node/DecisionNodeTest.php b/tests/Tree/Node/DecisionNodeTest.php new file mode 100644 index 00000000..2db3482c --- /dev/null +++ b/tests/Tree/Node/DecisionNodeTest.php @@ -0,0 +1,57 @@ +column()); + self::assertEquals(2, $node->samplesCount()); + } + + public function testImpurityIncrease(): void + { + $node = new DecisionNode(2, 4, [ + [[[1, 2, 3]], [1]], + [[[2, 3, 4]], [2]], + ], 400); + + $node->attachRight(new DecisionNode(2, 4, [ + [[[1, 2, 3]], [1]], + [[[2, 3, 4]], [2]], + ], 200)); + + $node->attachLeft(new DecisionNode(2, 4, [ + [[[1, 2, 3]], [1]], + [[[2, 3, 4]], [2]], + ], 100)); + + self::assertEquals(100, $node->purityIncrease()); + } + + public function testThrowExceptionOnInvalidGroupsCount(): void + { + $this->expectException(InvalidArgumentException::class); + + new DecisionNode(2, 3, [], 200); + } + + public function testThrowExceptionOnInvalidImpurity(): void + { + $this->expectException(InvalidArgumentException::class); + + new DecisionNode(2, 3, [[], []], -2); + } +} From 1e1d794655b8d7c8bb7d4bfe4e5a6d2b2c6e6497 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 12 May 2019 21:27:21 +0200 Subject: [PATCH 309/328] Fix DecisionTreeRegressor for big dataset (#376) --- README.md | 2 ++ src/Regression/DecisionTreeRegressor.php | 22 +++++++++++++++++++ src/Tree/CART.php | 2 +- .../Regression/DecisionTreeRegressorTest.php | 15 +++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3449ee0d..83e2c318 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ Public datasets are available in a separate repository [php-ai/php-ml-datasets]( * Regression * [Least Squares](http://php-ml.readthedocs.io/en/latest/machine-learning/regression/least-squares/) * [SVR](http://php-ml.readthedocs.io/en/latest/machine-learning/regression/svr/) + * DecisionTreeRegressor * Clustering * [k-Means](http://php-ml.readthedocs.io/en/latest/machine-learning/clustering/k-means/) * [DBSCAN](http://php-ml.readthedocs.io/en/latest/machine-learning/clustering/dbscan/) @@ -87,6 +88,7 @@ Public datasets are available in a separate repository [php-ai/php-ml-datasets]( * [Accuracy](http://php-ml.readthedocs.io/en/latest/machine-learning/metric/accuracy/) * [Confusion Matrix](http://php-ml.readthedocs.io/en/latest/machine-learning/metric/confusion-matrix/) * [Classification Report](http://php-ml.readthedocs.io/en/latest/machine-learning/metric/classification-report/) + * Regression * Workflow * [Pipeline](http://php-ml.readthedocs.io/en/latest/machine-learning/workflow/pipeline) * Neural Network diff --git a/src/Regression/DecisionTreeRegressor.php b/src/Regression/DecisionTreeRegressor.php index 6260a030..e066009e 100644 --- a/src/Regression/DecisionTreeRegressor.php +++ b/src/Regression/DecisionTreeRegressor.php @@ -4,6 +4,7 @@ namespace Phpml\Regression; +use Phpml\Exception\InvalidArgumentException; use Phpml\Exception\InvalidOperationException; use Phpml\Math\Statistic\Mean; use Phpml\Math\Statistic\Variance; @@ -29,6 +30,27 @@ final class DecisionTreeRegressor extends CART implements Regression */ protected $columns = []; + public function __construct( + int $maxDepth = PHP_INT_MAX, + int $maxLeafSize = 3, + float $minPurityIncrease = 0., + ?int $maxFeatures = null, + float $tolerance = 1e-4 + ) { + if ($maxFeatures !== null && $maxFeatures < 1) { + throw new InvalidArgumentException('Max features must be greater than 0'); + } + + if ($tolerance < 0.) { + throw new InvalidArgumentException('Tolerance must be equal or greater than 0'); + } + + $this->maxFeatures = $maxFeatures; + $this->tolerance = $tolerance; + + parent::__construct($maxDepth, $maxLeafSize, $minPurityIncrease); + } + public function train(array $samples, array $targets): void { $features = count($samples[0]); diff --git a/src/Tree/CART.php b/src/Tree/CART.php index 5ed15044..eb8cfe77 100644 --- a/src/Tree/CART.php +++ b/src/Tree/CART.php @@ -91,7 +91,7 @@ public function grow(array $samples, array $targets): void $depth++; - if ($left === [] || $right === []) { + if ($left[1] === [] || $right[1] === []) { $node = $this->terminate(array_merge($left[1], $right[1])); $current->attachLeft($node); diff --git a/tests/Regression/DecisionTreeRegressorTest.php b/tests/Regression/DecisionTreeRegressorTest.php index 845d24a6..046ce5d5 100644 --- a/tests/Regression/DecisionTreeRegressorTest.php +++ b/tests/Regression/DecisionTreeRegressorTest.php @@ -4,6 +4,7 @@ namespace Phpml\Tests\Regression; +use Phpml\Exception\InvalidArgumentException; use Phpml\Exception\InvalidOperationException; use Phpml\ModelManager; use Phpml\Regression\DecisionTreeRegressor; @@ -45,6 +46,20 @@ public function testPreventPredictWhenNotTrained(): void $regression->predict([[1]]); } + public function testMaxFeaturesLowerThanOne(): void + { + $this->expectException(InvalidArgumentException::class); + + new DecisionTreeRegressor(5, 3, 0.0, 0); + } + + public function testToleranceSmallerThanZero(): void + { + $this->expectException(InvalidArgumentException::class); + + new DecisionTreeRegressor(5, 3, 0.0, 20, -1); + } + public function testSaveAndRestore(): void { $samples = [[60], [61], [62], [63], [65]]; From 717f236ca932ed2336f68d327f1a3a52103d323e Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 12 May 2019 22:25:17 +0200 Subject: [PATCH 310/328] Implement NumberConverter (#377) --- src/FeatureExtraction/TfIdfTransformer.php | 2 +- .../TokenCountVectorizer.php | 2 +- src/FeatureSelection/SelectKBest.php | 2 +- src/FeatureSelection/VarianceThreshold.php | 2 +- src/Preprocessing/Imputer.php | 2 +- src/Preprocessing/LabelEncoder.php | 2 +- src/Preprocessing/Normalizer.php | 2 +- src/Preprocessing/NumberConverter.php | 47 +++++++++++++++++++ src/Transformer.php | 2 +- tests/Preprocessing/NumberConverterTest.php | 47 +++++++++++++++++++ 10 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 src/Preprocessing/NumberConverter.php create mode 100644 tests/Preprocessing/NumberConverterTest.php diff --git a/src/FeatureExtraction/TfIdfTransformer.php b/src/FeatureExtraction/TfIdfTransformer.php index d1ac35db..34f75330 100644 --- a/src/FeatureExtraction/TfIdfTransformer.php +++ b/src/FeatureExtraction/TfIdfTransformer.php @@ -30,7 +30,7 @@ public function fit(array $samples, ?array $targets = null): void } } - public function transform(array &$samples): void + public function transform(array &$samples, ?array &$targets = null): void { foreach ($samples as &$sample) { foreach ($sample as $index => &$feature) { diff --git a/src/FeatureExtraction/TokenCountVectorizer.php b/src/FeatureExtraction/TokenCountVectorizer.php index afd5f339..5cc5e8d3 100644 --- a/src/FeatureExtraction/TokenCountVectorizer.php +++ b/src/FeatureExtraction/TokenCountVectorizer.php @@ -46,7 +46,7 @@ public function fit(array $samples, ?array $targets = null): void $this->buildVocabulary($samples); } - public function transform(array &$samples): void + public function transform(array &$samples, ?array &$targets = null): void { array_walk($samples, function (string &$sample): void { $this->transformSample($sample); diff --git a/src/FeatureSelection/SelectKBest.php b/src/FeatureSelection/SelectKBest.php index 36b4245a..16e52782 100644 --- a/src/FeatureSelection/SelectKBest.php +++ b/src/FeatureSelection/SelectKBest.php @@ -56,7 +56,7 @@ public function fit(array $samples, ?array $targets = null): void $this->keepColumns = array_slice($sorted, 0, $this->k, true); } - public function transform(array &$samples): void + public function transform(array &$samples, ?array &$targets = null): void { if ($this->keepColumns === null) { return; diff --git a/src/FeatureSelection/VarianceThreshold.php b/src/FeatureSelection/VarianceThreshold.php index 5ca23323..3bbc29da 100644 --- a/src/FeatureSelection/VarianceThreshold.php +++ b/src/FeatureSelection/VarianceThreshold.php @@ -48,7 +48,7 @@ public function fit(array $samples, ?array $targets = null): void } } - public function transform(array &$samples): void + public function transform(array &$samples, ?array &$targets = null): void { foreach ($samples as &$sample) { $sample = array_values(array_intersect_key($sample, $this->keepColumns)); diff --git a/src/Preprocessing/Imputer.php b/src/Preprocessing/Imputer.php index e5b5af84..88ee2dd6 100644 --- a/src/Preprocessing/Imputer.php +++ b/src/Preprocessing/Imputer.php @@ -49,7 +49,7 @@ public function fit(array $samples, ?array $targets = null): void $this->samples = $samples; } - public function transform(array &$samples): void + public function transform(array &$samples, ?array &$targets = null): void { if ($this->samples === []) { throw new InvalidOperationException('Missing training samples for Imputer.'); diff --git a/src/Preprocessing/LabelEncoder.php b/src/Preprocessing/LabelEncoder.php index 9b5df2c1..1e612a17 100644 --- a/src/Preprocessing/LabelEncoder.php +++ b/src/Preprocessing/LabelEncoder.php @@ -22,7 +22,7 @@ public function fit(array $samples, ?array $targets = null): void } } - public function transform(array &$samples): void + public function transform(array &$samples, ?array &$targets = null): void { foreach ($samples as &$sample) { $sample = $this->classes[(string) $sample]; diff --git a/src/Preprocessing/Normalizer.php b/src/Preprocessing/Normalizer.php index 9888e0e5..5ba43e65 100644 --- a/src/Preprocessing/Normalizer.php +++ b/src/Preprocessing/Normalizer.php @@ -66,7 +66,7 @@ public function fit(array $samples, ?array $targets = null): void $this->fitted = true; } - public function transform(array &$samples): void + public function transform(array &$samples, ?array &$targets = null): void { $methods = [ self::NORM_L1 => 'normalizeL1', diff --git a/src/Preprocessing/NumberConverter.php b/src/Preprocessing/NumberConverter.php new file mode 100644 index 00000000..68247b13 --- /dev/null +++ b/src/Preprocessing/NumberConverter.php @@ -0,0 +1,47 @@ +transformTargets = $transformTargets; + $this->nonNumericPlaceholder = $nonNumericPlaceholder; + } + + public function fit(array $samples, ?array $targets = null): void + { + //nothing to do + } + + public function transform(array &$samples, ?array &$targets = null): void + { + foreach ($samples as &$sample) { + foreach ($sample as &$feature) { + $feature = is_numeric($feature) ? (float) $feature : $this->nonNumericPlaceholder; + } + } + + if ($this->transformTargets && is_array($targets)) { + foreach ($targets as &$target) { + $target = is_numeric($target) ? (float) $target : $this->nonNumericPlaceholder; + } + } + } +} diff --git a/src/Transformer.php b/src/Transformer.php index 7350e2ce..3a9b91db 100644 --- a/src/Transformer.php +++ b/src/Transformer.php @@ -11,5 +11,5 @@ interface Transformer */ public function fit(array $samples, ?array $targets = null): void; - public function transform(array &$samples): void; + public function transform(array &$samples, ?array &$targets = null): void; } diff --git a/tests/Preprocessing/NumberConverterTest.php b/tests/Preprocessing/NumberConverterTest.php new file mode 100644 index 00000000..287b7393 --- /dev/null +++ b/tests/Preprocessing/NumberConverterTest.php @@ -0,0 +1,47 @@ +transform($samples, $targets); + + self::assertEquals([[1.0, -4.0], [2.0, 3.0], [3.0, 112.5], [5.0, 0.0004]], $samples); + self::assertEquals(['1', '1', '2', '2'], $targets); + } + + public function testConvertTargets(): void + { + $samples = [['1', '-4'], ['2.0', 3.0], ['3', '112.5'], ['5', '0.0004']]; + $targets = ['1', '1', '2', 'not']; + + $converter = new NumberConverter(true); + $converter->transform($samples, $targets); + + self::assertEquals([[1.0, -4.0], [2.0, 3.0], [3.0, 112.5], [5.0, 0.0004]], $samples); + self::assertEquals([1.0, 1.0, 2.0, null], $targets); + } + + public function testConvertWithPlaceholder(): void + { + $samples = [['invalid'], ['13.5']]; + $targets = ['invalid', '2']; + + $converter = new NumberConverter(true, 'missing'); + $converter->transform($samples, $targets); + + self::assertEquals([['missing'], [13.5]], $samples); + self::assertEquals(['missing', 2.0], $targets); + } +} From 417174d1432483afdc7407d6f08b2b8386cbd7fc Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 12 May 2019 22:41:31 +0200 Subject: [PATCH 311/328] Implement ColumnFilter preprocessor (#378) --- src/Preprocessing/ColumnFilter.php | 42 ++++++++++++++++++++++++ tests/Preprocessing/ColumnFilterTest.php | 27 +++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 src/Preprocessing/ColumnFilter.php create mode 100644 tests/Preprocessing/ColumnFilterTest.php diff --git a/src/Preprocessing/ColumnFilter.php b/src/Preprocessing/ColumnFilter.php new file mode 100644 index 00000000..afe2db7d --- /dev/null +++ b/src/Preprocessing/ColumnFilter.php @@ -0,0 +1,42 @@ +datasetColumns = array_map(static function (string $column): string { + return $column; + }, $datasetColumns); + $this->filterColumns = array_map(static function (string $column): string { + return $column; + }, $filterColumns); + } + + public function fit(array $samples, ?array $targets = null): void + { + //nothing to do + } + + public function transform(array &$samples, ?array &$targets = null): void + { + $keys = array_intersect($this->datasetColumns, $this->filterColumns); + + foreach ($samples as &$sample) { + $sample = array_values(array_intersect_key($sample, $keys)); + } + } +} diff --git a/tests/Preprocessing/ColumnFilterTest.php b/tests/Preprocessing/ColumnFilterTest.php new file mode 100644 index 00000000..243c7ebb --- /dev/null +++ b/tests/Preprocessing/ColumnFilterTest.php @@ -0,0 +1,27 @@ +transform($samples); + + self::assertEquals([[100000, 4], [120000, 12], [200000, 0]], $samples); + } +} From c1c9873bf16690e3fc112060489c314e14501494 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Mon, 13 May 2019 20:12:42 +0200 Subject: [PATCH 312/328] Add Andrew to license (#380) --- LICENSE | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index c90077cb..bcb78954 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ The MIT License (MIT) -Copyright (c) 2016-2018 Arkadiusz Kondas +Copyright (c) 2016-2019 Arkadiusz Kondas +Copyright (c) 2018 Andrew DalPino Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From ff118eb2ba157c804919686a0a833179d0a0d0d6 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Mon, 13 May 2019 22:10:34 +0200 Subject: [PATCH 313/328] Implement LambdaTransformer (#381) --- src/Preprocessing/LambdaTransformer.php | 30 +++++++++++++++++++ tests/Preprocessing/LambdaTransformerTest.php | 28 +++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 src/Preprocessing/LambdaTransformer.php create mode 100644 tests/Preprocessing/LambdaTransformerTest.php diff --git a/src/Preprocessing/LambdaTransformer.php b/src/Preprocessing/LambdaTransformer.php new file mode 100644 index 00000000..f6b5a8be --- /dev/null +++ b/src/Preprocessing/LambdaTransformer.php @@ -0,0 +1,30 @@ +lambda = $lambda; + } + + public function fit(array $samples, ?array $targets = null): void + { + // nothing to do + } + + public function transform(array &$samples, ?array &$targets = null): void + { + foreach ($samples as &$sample) { + $sample = call_user_func($this->lambda, $sample); + } + } +} diff --git a/tests/Preprocessing/LambdaTransformerTest.php b/tests/Preprocessing/LambdaTransformerTest.php new file mode 100644 index 00000000..6f46f3ea --- /dev/null +++ b/tests/Preprocessing/LambdaTransformerTest.php @@ -0,0 +1,28 @@ +transform($samples); + + self::assertEquals([3, 7, 11], $samples); + } +} From b500f0b6480b6eee984ab9043f7f08613d4ba321 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 14 May 2019 21:26:25 +0200 Subject: [PATCH 314/328] Implement FeatureUnion :rocket: (#382) --- README.md | 4 ++ src/FeatureUnion.php | 72 +++++++++++++++++++++++++ src/Metric/Regression.php | 2 +- src/Pipeline.php | 51 ++++++++++-------- tests/FeatureUnionTest.php | 105 +++++++++++++++++++++++++++++++++++++ tests/PipelineTest.php | 35 +++++++++---- 6 files changed, 235 insertions(+), 34 deletions(-) create mode 100644 src/FeatureUnion.php create mode 100644 tests/FeatureUnionTest.php diff --git a/README.md b/README.md index 83e2c318..3fef8ed6 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,7 @@ Public datasets are available in a separate repository [php-ai/php-ml-datasets]( * Regression * Workflow * [Pipeline](http://php-ml.readthedocs.io/en/latest/machine-learning/workflow/pipeline) + * FeatureUnion * Neural Network * [Multilayer Perceptron Classifier](http://php-ml.readthedocs.io/en/latest/machine-learning/neural-network/multilayer-perceptron-classifier/) * Cross Validation @@ -103,6 +104,9 @@ Public datasets are available in a separate repository [php-ai/php-ml-datasets]( * [Normalization](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/normalization/) * [Imputation missing values](http://php-ml.readthedocs.io/en/latest/machine-learning/preprocessing/imputation-missing-values/) * LabelEncoder + * LambdaTransformer + * NumberConverter + * ColumnFilter * Feature Extraction * [Token Count Vectorizer](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-extraction/token-count-vectorizer/) * NGramTokenizer diff --git a/src/FeatureUnion.php b/src/FeatureUnion.php new file mode 100644 index 00000000..645a4219 --- /dev/null +++ b/src/FeatureUnion.php @@ -0,0 +1,72 @@ +pipelines = array_map(static function (Pipeline $pipeline): Pipeline { + return $pipeline; + }, $pipelines); + } + + public function fit(array $samples, ?array $targets = null): void + { + $originSamples = $samples; + foreach ($this->pipelines as $pipeline) { + foreach ($pipeline->getTransformers() as $transformer) { + $transformer->fit($samples, $targets); + $transformer->transform($samples, $targets); + } + $samples = $originSamples; + } + } + + public function transform(array &$samples, ?array &$targets = null): void + { + $this->transformSamples($samples, $targets); + } + + public function fitAndTransform(array &$samples, ?array &$targets = null): void + { + $this->transformSamples($samples, $targets, true); + } + + private function transformSamples(array &$samples, ?array &$targets = null, bool $fit = false): void + { + $union = []; + $originSamples = $samples; + foreach ($this->pipelines as $pipeline) { + foreach ($pipeline->getTransformers() as $transformer) { + if ($fit) { + $transformer->fit($samples, $targets); + } + $transformer->transform($samples, $targets); + } + + foreach ($samples as $index => $sample) { + $union[$index] = array_merge($union[$index] ?? [], is_array($sample) ? $sample : [$sample]); + } + $samples = $originSamples; + } + + $samples = $union; + } +} diff --git a/src/Metric/Regression.php b/src/Metric/Regression.php index 9f0e0249..c833f6aa 100644 --- a/src/Metric/Regression.php +++ b/src/Metric/Regression.php @@ -28,7 +28,7 @@ public static function meanSquaredLogarithmicError(array $targets, array $predic $errors = []; foreach ($targets as $index => $target) { - $errors[] = (log(1 + $target) - log(1 + $predictions[$index])) ** 2; + $errors[] = log((1 + $target) / (1 + $predictions[$index])) ** 2; } return Mean::arithmetic($errors); diff --git a/src/Pipeline.php b/src/Pipeline.php index 41188f37..421abb52 100644 --- a/src/Pipeline.php +++ b/src/Pipeline.php @@ -4,7 +4,9 @@ namespace Phpml; -class Pipeline implements Estimator +use Phpml\Exception\InvalidOperationException; + +class Pipeline implements Estimator, Transformer { /** * @var Transformer[] @@ -12,29 +14,18 @@ class Pipeline implements Estimator private $transformers = []; /** - * @var Estimator + * @var Estimator|null */ private $estimator; /** * @param Transformer[] $transformers */ - public function __construct(array $transformers, Estimator $estimator) - { - foreach ($transformers as $transformer) { - $this->addTransformer($transformer); - } - - $this->estimator = $estimator; - } - - public function addTransformer(Transformer $transformer): void - { - $this->transformers[] = $transformer; - } - - public function setEstimator(Estimator $estimator): void + public function __construct(array $transformers, ?Estimator $estimator = null) { + $this->transformers = array_map(static function (Transformer $transformer): Transformer { + return $transformer; + }, $transformers); $this->estimator = $estimator; } @@ -46,16 +37,20 @@ public function getTransformers(): array return $this->transformers; } - public function getEstimator(): Estimator + public function getEstimator(): ?Estimator { return $this->estimator; } public function train(array $samples, array $targets): void { + if ($this->estimator === null) { + throw new InvalidOperationException('Pipeline without estimator can\'t use train method'); + } + foreach ($this->transformers as $transformer) { $transformer->fit($samples, $targets); - $transformer->transform($samples); + $transformer->transform($samples, $targets); } $this->estimator->train($samples, $targets); @@ -66,15 +61,27 @@ public function train(array $samples, array $targets): void */ public function predict(array $samples) { - $this->transformSamples($samples); + if ($this->estimator === null) { + throw new InvalidOperationException('Pipeline without estimator can\'t use predict method'); + } + + $this->transform($samples); return $this->estimator->predict($samples); } - private function transformSamples(array &$samples): void + public function fit(array $samples, ?array $targets = null): void + { + foreach ($this->transformers as $transformer) { + $transformer->fit($samples, $targets); + $transformer->transform($samples, $targets); + } + } + + public function transform(array &$samples, ?array &$targets = null): void { foreach ($this->transformers as $transformer) { - $transformer->transform($samples); + $transformer->transform($samples, $targets); } } } diff --git a/tests/FeatureUnionTest.php b/tests/FeatureUnionTest.php new file mode 100644 index 00000000..0a903b4c --- /dev/null +++ b/tests/FeatureUnionTest.php @@ -0,0 +1,105 @@ +fitAndTransform($samples, $targets); + + self::assertEquals([ + [0, 23.0, 100000.0], + [1, 23.0, 200000.0], + [1, 43.0, 150000.0], + [0, 33.0, 150000.0], + ], $samples); + self::assertEquals([1, 2, 1, 3], $targets); + } + + public function testFitAndTransformSeparate(): void + { + $columns = ['age', 'income', 'sex']; + $trainSamples = [ + ['23', '100000', 'male'], + ['23', '200000', 'female'], + ['43', '150000', 'female'], + ['33', 'n/a', 'male'], + ]; + $testSamples = [ + ['43', '500000', 'female'], + ['13', 'n/a', 'male'], + ['53', 'n/a', 'male'], + ['43', 'n/a', 'female'], + ]; + + $union = new FeatureUnion([ + new Pipeline([ + new ColumnFilter($columns, ['sex']), + new LambdaTransformer(function (array $sample) { + return $sample[0]; + }), + new LabelEncoder(), + ]), + new Pipeline([ + new ColumnFilter($columns, ['age', 'income']), + new NumberConverter(), + new Imputer(null, new MeanStrategy(), Imputer::AXIS_COLUMN), + ]), + ]); + + $union->fit($trainSamples); + $union->transform($testSamples); + + self::assertEquals([ + [1, 43.0, 500000.0], + [0, 13.0, 150000.0], + [0, 53.0, 150000.0], + [1, 43.0, 150000.0], + ], $testSamples); + } + + public function testNotAllowForEmptyPipelines(): void + { + $this->expectException(InvalidArgumentException::class); + + new FeatureUnion([]); + } +} diff --git a/tests/PipelineTest.php b/tests/PipelineTest.php index 31c4f36d..f905c8b3 100644 --- a/tests/PipelineTest.php +++ b/tests/PipelineTest.php @@ -11,9 +11,9 @@ use Phpml\ModelManager; use Phpml\Pipeline; use Phpml\Preprocessing\Imputer; +use Phpml\Preprocessing\Imputer\Strategy\MeanStrategy; use Phpml\Preprocessing\Imputer\Strategy\MostFrequentStrategy; use Phpml\Preprocessing\Normalizer; -use Phpml\Regression\SVR; use Phpml\Tokenization\WordTokenizer; use PHPUnit\Framework\TestCase; @@ -32,16 +32,6 @@ public function testPipelineConstruction(): void self::assertEquals($estimator, $pipeline->getEstimator()); } - public function testPipelineEstimatorSetter(): void - { - $pipeline = new Pipeline([new TfIdfTransformer()], new SVC()); - - $estimator = new SVR(); - $pipeline->setEstimator($estimator); - - self::assertEquals($estimator, $pipeline->getEstimator()); - } - public function testPipelineWorkflow(): void { $transformers = [ @@ -119,6 +109,29 @@ public function testPipelineTransformersWithTargets(): void self::assertEquals(['b'], $pipeline->predict([[1, 3, 5]])); } + public function testPipelineAsTransformer(): void + { + $pipeline = new Pipeline([ + new Imputer(null, new MeanStrategy()), + ]); + + $trainSamples = [ + [10, 20, 30], + [20, 30, 40], + [30, 40, 50], + ]; + + $pipeline->fit($trainSamples); + + $testSamples = [ + [null, null, null], + ]; + + $pipeline->transform($testSamples); + + self::assertEquals([[20.0, 30.0, 40.0]], $testSamples); + } + public function testSaveAndRestore(): void { $pipeline = new Pipeline([ From 3baf1520e314e149c74a5521d09f4be49aacf2b9 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Tue, 14 May 2019 22:43:08 +0200 Subject: [PATCH 315/328] Update dependencies and phpstan (#383) --- composer.lock | 343 +++++++++++++++++--------------- src/Clustering/KMeans/Space.php | 4 + src/Dataset/FilesDataset.php | 15 +- 3 files changed, 196 insertions(+), 166 deletions(-) diff --git a/composer.lock b/composer.lock index e270c978..4eb374c9 100644 --- a/composer.lock +++ b/composer.lock @@ -170,16 +170,16 @@ }, { "name": "doctrine/annotations", - "version": "v1.6.0", + "version": "v1.6.1", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5" + "reference": "53120e0eb10355388d6ccbe462f1fea34ddadb24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", - "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/53120e0eb10355388d6ccbe462f1fea34ddadb24", + "reference": "53120e0eb10355388d6ccbe462f1fea34ddadb24", "shasum": "" }, "require": { @@ -234,7 +234,7 @@ "docblock", "parser" ], - "time": "2017-12-06T07:11:42+00:00" + "time": "2019-03-25T19:12:02+00:00" }, { "name": "doctrine/inflector", @@ -415,16 +415,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v2.14.2", + "version": "v2.15.0", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "ff401e58261ffc5934a58f795b3f95b355e276cb" + "reference": "adfab51ae979ee8b0fcbc55aa231ec2786cb1f91" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/ff401e58261ffc5934a58f795b3f95b355e276cb", - "reference": "ff401e58261ffc5934a58f795b3f95b355e276cb", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/adfab51ae979ee8b0fcbc55aa231ec2786cb1f91", + "reference": "adfab51ae979ee8b0fcbc55aa231ec2786cb1f91", "shasum": "" }, "require": { @@ -452,10 +452,10 @@ "mikey179/vfsstream": "^1.6", "php-coveralls/php-coveralls": "^2.1", "php-cs-fixer/accessible-object": "^1.0", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.0.1", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.0.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.1", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.1", "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1", - "phpunitgoodpractices/traits": "^1.5.1", + "phpunitgoodpractices/traits": "^1.8", "symfony/phpunit-bridge": "^4.0" }, "suggest": { @@ -468,6 +468,11 @@ "php-cs-fixer" ], "type": "application", + "extra": { + "branch-alias": { + "dev-master": "2.15-dev" + } + }, "autoload": { "psr-4": { "PhpCsFixer\\": "src/" @@ -499,20 +504,20 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2019-02-17T17:44:13+00:00" + "time": "2019-05-06T07:13:51+00:00" }, { "name": "illuminate/contracts", - "version": "v5.8.4", + "version": "v5.8.17", "source": { "type": "git", "url": "https://github.com/illuminate/contracts.git", - "reference": "3e3a9a654adbf798e05491a5dbf90112df1effde" + "reference": "acd524087bcebcc0979748e84ccc0876395ae497" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/contracts/zipball/3e3a9a654adbf798e05491a5dbf90112df1effde", - "reference": "3e3a9a654adbf798e05491a5dbf90112df1effde", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/acd524087bcebcc0979748e84ccc0876395ae497", + "reference": "acd524087bcebcc0979748e84ccc0876395ae497", "shasum": "" }, "require": { @@ -543,20 +548,20 @@ ], "description": "The Illuminate Contracts package.", "homepage": "https://laravel.com", - "time": "2019-02-18T18:37:54+00:00" + "time": "2019-05-14T10:54:47+00:00" }, { "name": "illuminate/support", - "version": "v5.8.4", + "version": "v5.8.17", "source": { "type": "git", "url": "https://github.com/illuminate/support.git", - "reference": "07062f5750872a31e086ff37a7c50ac18b8c417c" + "reference": "128c6aaa1599811a04fd10c2d68e1213e433da3f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/support/zipball/07062f5750872a31e086ff37a7c50ac18b8c417c", - "reference": "07062f5750872a31e086ff37a7c50ac18b8c417c", + "url": "https://api.github.com/repos/illuminate/support/zipball/128c6aaa1599811a04fd10c2d68e1213e433da3f", + "reference": "128c6aaa1599811a04fd10c2d68e1213e433da3f", "shasum": "" }, "require": { @@ -604,7 +609,7 @@ ], "description": "The Illuminate Support package.", "homepage": "https://laravel.com", - "time": "2019-03-12T13:17:00+00:00" + "time": "2019-05-14T13:37:34+00:00" }, { "name": "jean85/pretty-package-versions", @@ -798,16 +803,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.8.1", + "version": "1.9.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8" + "reference": "e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", - "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72", + "reference": "e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72", "shasum": "" }, "require": { @@ -842,20 +847,20 @@ "object", "object graph" ], - "time": "2018-06-11T23:09:50+00:00" + "time": "2019-04-07T13:18:21+00:00" }, { "name": "nesbot/carbon", - "version": "2.16.0", + "version": "2.17.1", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "dd16fedc022180ea4292a03aabe95e9895677911" + "reference": "96acbc0c03782e8115156dd4dd8b736267155066" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/dd16fedc022180ea4292a03aabe95e9895677911", - "reference": "dd16fedc022180ea4292a03aabe95e9895677911", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/96acbc0c03782e8115156dd4dd8b736267155066", + "reference": "96acbc0c03782e8115156dd4dd8b736267155066", "shasum": "" }, "require": { @@ -865,9 +870,9 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.14 || ^3.0", - "kylekatarnls/multi-tester": "^0.1", + "kylekatarnls/multi-tester": "^1.1", "phpmd/phpmd": "^2.6", - "phpstan/phpstan": "^0.10.8", + "phpstan/phpstan": "^0.11", "phpunit/phpunit": "^7.5 || ^8.0", "squizlabs/php_codesniffer": "^3.4" }, @@ -902,7 +907,7 @@ "datetime", "time" ], - "time": "2019-03-12T09:31:40+00:00" + "time": "2019-04-27T18:04:27+00:00" }, { "name": "nette/finder", @@ -1579,16 +1584,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "4.3.0", + "version": "4.3.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "94fd0001232e47129dd3504189fa1c7225010d08" + "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", - "reference": "94fd0001232e47129dd3504189fa1c7225010d08", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", + "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", "shasum": "" }, "require": { @@ -1626,7 +1631,7 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2017-11-30T07:14:17+00:00" + "time": "2019-04-30T17:48:53+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -1740,16 +1745,16 @@ }, { "name": "phpstan/phpstan-phpunit", - "version": "0.11", + "version": "0.11.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "70c22d44b96a21a4952fc13021a5a63cc83f5c81" + "reference": "0d339995c3c6acc56bc912959f436298c70d13ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/70c22d44b96a21a4952fc13021a5a63cc83f5c81", - "reference": "70c22d44b96a21a4952fc13021a5a63cc83f5c81", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/0d339995c3c6acc56bc912959f436298c70d13ab", + "reference": "0d339995c3c6acc56bc912959f436298c70d13ab", "shasum": "" }, "require": { @@ -1771,10 +1776,15 @@ "satooshi/php-coveralls": "^1.0", "slevomat/coding-standard": "^4.5.2" }, - "type": "library", + "type": "phpstan-extension", "extra": { "branch-alias": { "dev-master": "0.11-dev" + }, + "phpstan": { + "includes": [ + "extension.neon" + ] } }, "autoload": { @@ -1787,20 +1797,20 @@ "MIT" ], "description": "PHPUnit extensions and rules for PHPStan", - "time": "2018-12-22T14:05:04+00:00" + "time": "2019-05-10T20:33:17+00:00" }, { "name": "phpstan/phpstan-shim", - "version": "0.11.4", + "version": "0.11.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-shim.git", - "reference": "70e1a346907142449ac085745f158aa715b4e0b8" + "reference": "f38e0658f497517aff0635f4a622858a5d20c37f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-shim/zipball/70e1a346907142449ac085745f158aa715b4e0b8", - "reference": "70e1a346907142449ac085745f158aa715b4e0b8", + "url": "https://api.github.com/repos/phpstan/phpstan-shim/zipball/f38e0658f497517aff0635f4a622858a5d20c37f", + "reference": "f38e0658f497517aff0635f4a622858a5d20c37f", "shasum": "" }, "require": { @@ -1808,7 +1818,7 @@ }, "replace": { "nikic/php-parser": "^4.0.2", - "phpstan/phpdoc-parser": "^0.3", + "phpstan/phpdoc-parser": "^0.3.3", "phpstan/phpstan": "self.version" }, "bin": [ @@ -1831,7 +1841,7 @@ "MIT" ], "description": "PHPStan Phar distribution", - "time": "2019-03-14T15:24:47+00:00" + "time": "2019-05-08T19:07:51+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -2133,16 +2143,16 @@ }, { "name": "phpunit/phpunit", - "version": "8.0.5", + "version": "8.1.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "19cbed2120839772c4a00e8b28456b0c77d1a7b4" + "reference": "01392d4b5878aa617e8d9bc7a529e5febc8fe956" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/19cbed2120839772c4a00e8b28456b0c77d1a7b4", - "reference": "19cbed2120839772c4a00e8b28456b0c77d1a7b4", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/01392d4b5878aa617e8d9bc7a529e5febc8fe956", + "reference": "01392d4b5878aa617e8d9bc7a529e5febc8fe956", "shasum": "" }, "require": { @@ -2185,7 +2195,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "8.0-dev" + "dev-master": "8.1-dev" } }, "autoload": { @@ -2211,7 +2221,7 @@ "testing", "xunit" ], - "time": "2019-03-16T07:33:46+00:00" + "time": "2019-05-14T04:57:31+00:00" }, { "name": "psr/cache", @@ -2570,16 +2580,16 @@ }, { "name": "sebastian/environment", - "version": "4.1.0", + "version": "4.2.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "6fda8ce1974b62b14935adc02a9ed38252eca656" + "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6fda8ce1974b62b14935adc02a9ed38252eca656", - "reference": "6fda8ce1974b62b14935adc02a9ed38252eca656", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/f2a2c8e1c97c11ace607a7a667d73d47c19fe404", + "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404", "shasum": "" }, "require": { @@ -2594,7 +2604,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -2619,7 +2629,7 @@ "environment", "hhvm" ], - "time": "2019-02-01T05:27:49+00:00" + "time": "2019-05-05T09:05:15+00:00" }, { "name": "sebastian/exporter", @@ -3023,30 +3033,30 @@ }, { "name": "slam/php-cs-fixer-extensions", - "version": "v1.18.0", + "version": "v1.19.0", "source": { "type": "git", "url": "https://github.com/Slamdunk/php-cs-fixer-extensions.git", - "reference": "da18f089d1c559915d3c25d5e8783c7b7d272d1d" + "reference": "1cc36ca952e49579bfa94964194de7008862b958" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Slamdunk/php-cs-fixer-extensions/zipball/da18f089d1c559915d3c25d5e8783c7b7d272d1d", - "reference": "da18f089d1c559915d3c25d5e8783c7b7d272d1d", + "url": "https://api.github.com/repos/Slamdunk/php-cs-fixer-extensions/zipball/1cc36ca952e49579bfa94964194de7008862b958", + "reference": "1cc36ca952e49579bfa94964194de7008862b958", "shasum": "" }, "require": { - "friendsofphp/php-cs-fixer": "^2.14", - "php": "^7.1" + "friendsofphp/php-cs-fixer": "^2.15", + "php": "^7.2" }, "require-dev": { - "phpstan/phpstan": "^0.10", - "phpstan/phpstan-phpunit": "^0.10", + "phpstan/phpstan": "^0.11", + "phpstan/phpstan-phpunit": "^0.11", "phpunit/phpunit": "^7.5", "roave/security-advisories": "dev-master", "slam/php-debug-r": "^1.4", - "slam/phpstan-extensions": "^2.0", - "thecodingmachine/phpstan-strict-rules": "^0.10" + "slam/phpstan-extensions": "^3.0", + "thecodingmachine/phpstan-strict-rules": "^0.11" }, "type": "library", "autoload": { @@ -3066,7 +3076,7 @@ } ], "description": "Slam extension of friendsofphp/php-cs-fixer", - "time": "2019-01-07T15:02:12+00:00" + "time": "2019-05-06T08:55:25+00:00" }, { "name": "slevomat/coding-standard", @@ -3110,16 +3120,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.4.1", + "version": "3.4.2", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5b4333b4010625d29580eb4a41f1e53251be6baa" + "reference": "b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5b4333b4010625d29580eb4a41f1e53251be6baa", - "reference": "5b4333b4010625d29580eb4a41f1e53251be6baa", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8", + "reference": "b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8", "shasum": "" }, "require": { @@ -3157,20 +3167,20 @@ "phpcs", "standards" ], - "time": "2019-03-19T03:22:27+00:00" + "time": "2019-04-10T23:49:02+00:00" }, { "name": "symfony/cache", - "version": "v4.2.4", + "version": "v4.2.8", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "b5c650406953f2f44a37c4f3ac66152fafc22c66" + "reference": "9e64db924324700e19ef4f21c2c279a35ff9bdff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/b5c650406953f2f44a37c4f3ac66152fafc22c66", - "reference": "b5c650406953f2f44a37c4f3ac66152fafc22c66", + "url": "https://api.github.com/repos/symfony/cache/zipball/9e64db924324700e19ef4f21c2c279a35ff9bdff", + "reference": "9e64db924324700e19ef4f21c2c279a35ff9bdff", "shasum": "" }, "require": { @@ -3234,20 +3244,20 @@ "caching", "psr6" ], - "time": "2019-02-23T15:17:42+00:00" + "time": "2019-04-16T09:36:45+00:00" }, { "name": "symfony/config", - "version": "v4.2.4", + "version": "v4.2.8", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "7f70d79c7a24a94f8e98abb988049403a53d7b31" + "reference": "0e745ead307d5dcd4e163e94a47ec04b1428943f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/7f70d79c7a24a94f8e98abb988049403a53d7b31", - "reference": "7f70d79c7a24a94f8e98abb988049403a53d7b31", + "url": "https://api.github.com/repos/symfony/config/zipball/0e745ead307d5dcd4e163e94a47ec04b1428943f", + "reference": "0e745ead307d5dcd4e163e94a47ec04b1428943f", "shasum": "" }, "require": { @@ -3297,20 +3307,20 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2019-02-23T15:17:42+00:00" + "time": "2019-04-01T14:03:25+00:00" }, { "name": "symfony/console", - "version": "v4.2.4", + "version": "v4.2.8", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "9dc2299a016497f9ee620be94524e6c0af0280a9" + "reference": "e2840bb38bddad7a0feaf85931e38fdcffdb2f81" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/9dc2299a016497f9ee620be94524e6c0af0280a9", - "reference": "9dc2299a016497f9ee620be94524e6c0af0280a9", + "url": "https://api.github.com/repos/symfony/console/zipball/e2840bb38bddad7a0feaf85931e38fdcffdb2f81", + "reference": "e2840bb38bddad7a0feaf85931e38fdcffdb2f81", "shasum": "" }, "require": { @@ -3369,20 +3379,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2019-02-23T15:17:42+00:00" + "time": "2019-04-08T14:23:48+00:00" }, { "name": "symfony/contracts", - "version": "v1.0.2", + "version": "v1.1.0", "source": { "type": "git", "url": "https://github.com/symfony/contracts.git", - "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf" + "reference": "d3636025e8253c6144358ec0a62773cae588395b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/contracts/zipball/1aa7ab2429c3d594dd70689604b5cf7421254cdf", - "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf", + "url": "https://api.github.com/repos/symfony/contracts/zipball/d3636025e8253c6144358ec0a62773cae588395b", + "reference": "d3636025e8253c6144358ec0a62773cae588395b", "shasum": "" }, "require": { @@ -3390,19 +3400,22 @@ }, "require-dev": { "psr/cache": "^1.0", - "psr/container": "^1.0" + "psr/container": "^1.0", + "symfony/polyfill-intl-idn": "^1.10" }, "suggest": { "psr/cache": "When using the Cache contracts", "psr/container": "When using the Service contracts", "symfony/cache-contracts-implementation": "", + "symfony/event-dispatcher-implementation": "", + "symfony/http-client-contracts-implementation": "", "symfony/service-contracts-implementation": "", "symfony/translation-contracts-implementation": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" } }, "autoload": { @@ -3437,20 +3450,20 @@ "interoperability", "standards" ], - "time": "2018-12-05T08:06:11+00:00" + "time": "2019-04-27T14:29:50+00:00" }, { "name": "symfony/debug", - "version": "v4.2.4", + "version": "v4.2.8", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "de73f48977b8eaf7ce22814d66e43a1662cc864f" + "reference": "2d279b6bb1d582dd5740d4d3251ae8c18812ed37" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/de73f48977b8eaf7ce22814d66e43a1662cc864f", - "reference": "de73f48977b8eaf7ce22814d66e43a1662cc864f", + "url": "https://api.github.com/repos/symfony/debug/zipball/2d279b6bb1d582dd5740d4d3251ae8c18812ed37", + "reference": "2d279b6bb1d582dd5740d4d3251ae8c18812ed37", "shasum": "" }, "require": { @@ -3493,20 +3506,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2019-03-03T18:11:24+00:00" + "time": "2019-04-11T11:27:41+00:00" }, { "name": "symfony/dependency-injection", - "version": "v4.2.4", + "version": "v4.2.8", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "cdadb3765df7c89ac93628743913b92bb91f1704" + "reference": "d161c0c8bc77ad6fdb8f5083b9e34c3015d43eb1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/cdadb3765df7c89ac93628743913b92bb91f1704", - "reference": "cdadb3765df7c89ac93628743913b92bb91f1704", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/d161c0c8bc77ad6fdb8f5083b9e34c3015d43eb1", + "reference": "d161c0c8bc77ad6fdb8f5083b9e34c3015d43eb1", "shasum": "" }, "require": { @@ -3566,20 +3579,20 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2019-02-23T15:17:42+00:00" + "time": "2019-04-27T11:48:17+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.2.4", + "version": "v4.2.8", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "3354d2e6af986dd71f68b4e5cf4a933ab58697fb" + "reference": "fbce53cd74ac509cbe74b6f227622650ab759b02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/3354d2e6af986dd71f68b4e5cf4a933ab58697fb", - "reference": "3354d2e6af986dd71f68b4e5cf4a933ab58697fb", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/fbce53cd74ac509cbe74b6f227622650ab759b02", + "reference": "fbce53cd74ac509cbe74b6f227622650ab759b02", "shasum": "" }, "require": { @@ -3630,11 +3643,11 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2019-02-23T15:17:42+00:00" + "time": "2019-04-06T13:51:08+00:00" }, { "name": "symfony/filesystem", - "version": "v4.2.4", + "version": "v4.2.8", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", @@ -3684,16 +3697,16 @@ }, { "name": "symfony/finder", - "version": "v4.2.4", + "version": "v4.2.8", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "267b7002c1b70ea80db0833c3afe05f0fbde580a" + "reference": "e45135658bd6c14b61850bf131c4f09a55133f69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/267b7002c1b70ea80db0833c3afe05f0fbde580a", - "reference": "267b7002c1b70ea80db0833c3afe05f0fbde580a", + "url": "https://api.github.com/repos/symfony/finder/zipball/e45135658bd6c14b61850bf131c4f09a55133f69", + "reference": "e45135658bd6c14b61850bf131c4f09a55133f69", "shasum": "" }, "require": { @@ -3729,20 +3742,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2019-02-23T15:42:05+00:00" + "time": "2019-04-06T13:51:08+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.2.4", + "version": "v4.2.8", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "850a667d6254ccf6c61d853407b16f21c4579c77" + "reference": "1ea878bd3af18f934dedb8c0de60656a9a31a718" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/850a667d6254ccf6c61d853407b16f21c4579c77", - "reference": "850a667d6254ccf6c61d853407b16f21c4579c77", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/1ea878bd3af18f934dedb8c0de60656a9a31a718", + "reference": "1ea878bd3af18f934dedb8c0de60656a9a31a718", "shasum": "" }, "require": { @@ -3783,20 +3796,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2019-02-26T08:03:39+00:00" + "time": "2019-05-01T08:36:31+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.2.4", + "version": "v4.2.8", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "895ceccaa8149f9343e6134e607c21da42d73b7a" + "reference": "a7713bc522f1a1cdf0b39f809fa4542523fc3114" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/895ceccaa8149f9343e6134e607c21da42d73b7a", - "reference": "895ceccaa8149f9343e6134e607c21da42d73b7a", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/a7713bc522f1a1cdf0b39f809fa4542523fc3114", + "reference": "a7713bc522f1a1cdf0b39f809fa4542523fc3114", "shasum": "" }, "require": { @@ -3872,20 +3885,20 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2019-03-03T19:38:09+00:00" + "time": "2019-05-01T13:31:08+00:00" }, { "name": "symfony/options-resolver", - "version": "v4.2.4", + "version": "v4.2.8", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "3896e5a7d06fd15fa4947694c8dcdd371ff147d1" + "reference": "fd4a5f27b7cd085b489247b9890ebca9f3e10044" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/3896e5a7d06fd15fa4947694c8dcdd371ff147d1", - "reference": "3896e5a7d06fd15fa4947694c8dcdd371ff147d1", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/fd4a5f27b7cd085b489247b9890ebca9f3e10044", + "reference": "fd4a5f27b7cd085b489247b9890ebca9f3e10044", "shasum": "" }, "require": { @@ -3926,7 +3939,7 @@ "configuration", "options" ], - "time": "2019-02-23T15:17:42+00:00" + "time": "2019-04-10T16:20:36+00:00" }, { "name": "symfony/polyfill-ctype", @@ -4161,16 +4174,16 @@ }, { "name": "symfony/process", - "version": "v4.2.4", + "version": "v4.2.8", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "6c05edb11fbeff9e2b324b4270ecb17911a8b7ad" + "reference": "8cf39fb4ccff793340c258ee7760fd40bfe745fe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/6c05edb11fbeff9e2b324b4270ecb17911a8b7ad", - "reference": "6c05edb11fbeff9e2b324b4270ecb17911a8b7ad", + "url": "https://api.github.com/repos/symfony/process/zipball/8cf39fb4ccff793340c258ee7760fd40bfe745fe", + "reference": "8cf39fb4ccff793340c258ee7760fd40bfe745fe", "shasum": "" }, "require": { @@ -4206,11 +4219,11 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2019-01-24T22:05:03+00:00" + "time": "2019-04-10T16:20:36+00:00" }, { "name": "symfony/stopwatch", - "version": "v4.2.4", + "version": "v4.2.8", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", @@ -4260,16 +4273,16 @@ }, { "name": "symfony/translation", - "version": "v4.2.4", + "version": "v4.2.8", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "748464177a77011f8f4cdd076773862ce4915f8f" + "reference": "181a426dd129cb496f12d7e7555f6d0b37a7615b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/748464177a77011f8f4cdd076773862ce4915f8f", - "reference": "748464177a77011f8f4cdd076773862ce4915f8f", + "url": "https://api.github.com/repos/symfony/translation/zipball/181a426dd129cb496f12d7e7555f6d0b37a7615b", + "reference": "181a426dd129cb496f12d7e7555f6d0b37a7615b", "shasum": "" }, "require": { @@ -4291,7 +4304,9 @@ "symfony/console": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0", "symfony/finder": "~2.8|~3.0|~4.0", + "symfony/http-kernel": "~3.4|~4.0", "symfony/intl": "~3.4|~4.0", + "symfony/var-dumper": "~3.4|~4.0", "symfony/yaml": "~3.4|~4.0" }, "suggest": { @@ -4329,20 +4344,20 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2019-02-27T03:31:50+00:00" + "time": "2019-05-01T12:55:36+00:00" }, { "name": "symfony/var-exporter", - "version": "v4.2.4", + "version": "v4.2.8", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "d8bf4424c232b55f4c1816037d3077a89258557e" + "reference": "57e00f3e0a3deee65b67cf971455b98afeacca46" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/d8bf4424c232b55f4c1816037d3077a89258557e", - "reference": "d8bf4424c232b55f4c1816037d3077a89258557e", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/57e00f3e0a3deee65b67cf971455b98afeacca46", + "reference": "57e00f3e0a3deee65b67cf971455b98afeacca46", "shasum": "" }, "require": { @@ -4389,20 +4404,20 @@ "instantiate", "serialize" ], - "time": "2019-01-16T20:35:37+00:00" + "time": "2019-04-09T20:09:28+00:00" }, { "name": "symfony/yaml", - "version": "v4.2.4", + "version": "v4.2.8", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "761fa560a937fd7686e5274ff89dcfa87a5047df" + "reference": "6712daf03ee25b53abb14e7e8e0ede1a770efdb1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/761fa560a937fd7686e5274ff89dcfa87a5047df", - "reference": "761fa560a937fd7686e5274ff89dcfa87a5047df", + "url": "https://api.github.com/repos/symfony/yaml/zipball/6712daf03ee25b53abb14e7e8e0ede1a770efdb1", + "reference": "6712daf03ee25b53abb14e7e8e0ede1a770efdb1", "shasum": "" }, "require": { @@ -4448,7 +4463,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2019-02-23T15:17:42+00:00" + "time": "2019-03-30T15:58:42+00:00" }, { "name": "symplify/better-phpdoc-parser", @@ -4654,16 +4669,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.1.0", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + "reference": "1c42705be2b6c1de5904f8afacef5895cab44bf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/1c42705be2b6c1de5904f8afacef5895cab44bf8", + "reference": "1c42705be2b6c1de5904f8afacef5895cab44bf8", "shasum": "" }, "require": { @@ -4690,7 +4705,7 @@ } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2017-04-07T12:08:54+00:00" + "time": "2019-04-04T09:56:43+00:00" }, { "name": "webmozart/assert", diff --git a/src/Clustering/KMeans/Space.php b/src/Clustering/KMeans/Space.php index f9f57f5f..33463159 100644 --- a/src/Clustering/KMeans/Space.php +++ b/src/Clustering/KMeans/Space.php @@ -167,6 +167,10 @@ protected function iterate(array $clusters): bool foreach ($cluster as $point) { $closest = $point->getClosest($clusters); + if ($closest === null) { + continue; + } + if ($closest !== $cluster) { $attach[$closest] ?? $attach[$closest] = new SplObjectStorage(); $detach[$cluster] ?? $detach[$cluster] = new SplObjectStorage(); diff --git a/src/Dataset/FilesDataset.php b/src/Dataset/FilesDataset.php index daa7192c..e24d908a 100644 --- a/src/Dataset/FilesDataset.php +++ b/src/Dataset/FilesDataset.php @@ -19,7 +19,13 @@ public function __construct(string $rootPath) private function scanRootPath(string $rootPath): void { - foreach (glob($rootPath.DIRECTORY_SEPARATOR.'*', GLOB_ONLYDIR) as $dir) { + $dirs = glob($rootPath.DIRECTORY_SEPARATOR.'*', GLOB_ONLYDIR); + + if ($dirs === false) { + throw new DatasetException(sprintf('An error occurred during directory "%s" scan', $rootPath)); + } + + foreach ($dirs as $dir) { $this->scanDir($dir); } } @@ -28,7 +34,12 @@ private function scanDir(string $dir): void { $target = basename($dir); - foreach (array_filter(glob($dir.DIRECTORY_SEPARATOR.'*'), 'is_file') as $file) { + $files = glob($dir.DIRECTORY_SEPARATOR.'*'); + if ($files === false) { + return; + } + + foreach (array_filter($files, 'is_file') as $file) { $this->samples[] = file_get_contents($file); $this->targets[] = $target; } From 4590d5cc324249a8ae374c702e6197bc94347d3a Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Wed, 15 May 2019 08:00:46 +0200 Subject: [PATCH 316/328] Implement OneHotEncoder (#384) --- CHANGELOG.md | 9 +++- README.md | 1 + src/Preprocessing/OneHotEncoder.php | 66 +++++++++++++++++++++++ tests/Preprocessing/OneHotEncoderTest.php | 66 +++++++++++++++++++++++ 4 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 src/Preprocessing/OneHotEncoder.php create mode 100644 tests/Preprocessing/OneHotEncoderTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 662a086a..b403887f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [0.9.0] - Unreleased ### Added - [Preprocessing] Implement LabelEncoder +- [Preprocessing] Implement ColumnFilter +- [Preprocessing] Implement LambdaTransformer +- [Preprocessing] Implement NumberConverter +- [Preprocessing] Implement OneHotEncoder +- [Workflow] Implement FeatureUnion +- [Metric] Add Regression metrics: meanSquaredError, meanSquaredLogarithmicError, meanAbsoluteError, medianAbsoluteError, r2Score, maxError +- [Regression] Implement DecisionTreeRegressor ## [0.8.0] - 2019-03-20 ### Added diff --git a/README.md b/README.md index 3fef8ed6..f34a49ab 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,7 @@ Public datasets are available in a separate repository [php-ai/php-ml-datasets]( * LambdaTransformer * NumberConverter * ColumnFilter + * OneHotEncoder * Feature Extraction * [Token Count Vectorizer](http://php-ml.readthedocs.io/en/latest/machine-learning/feature-extraction/token-count-vectorizer/) * NGramTokenizer diff --git a/src/Preprocessing/OneHotEncoder.php b/src/Preprocessing/OneHotEncoder.php new file mode 100644 index 00000000..c9d4d0af --- /dev/null +++ b/src/Preprocessing/OneHotEncoder.php @@ -0,0 +1,66 @@ +ignoreUnknown = $ignoreUnknown; + } + + public function fit(array $samples, ?array $targets = null): void + { + foreach (array_keys(array_values(current($samples))) as $column) { + $this->fitColumn($column, array_values(array_unique(array_column($samples, $column)))); + } + } + + public function transform(array &$samples, ?array &$targets = null): void + { + foreach ($samples as &$sample) { + $sample = $this->transformSample(array_values($sample)); + } + } + + private function fitColumn(int $column, array $values): void + { + $count = count($values); + foreach ($values as $index => $value) { + $map = array_fill(0, $count, 0); + $map[$index] = 1; + $this->categories[$column][$value] = $map; + } + } + + private function transformSample(array $sample): array + { + $encoded = []; + foreach ($sample as $column => $feature) { + if (!isset($this->categories[$column][$feature]) && !$this->ignoreUnknown) { + throw new InvalidArgumentException(sprintf('Missing category "%s" for column %s in trained encoder', $feature, $column)); + } + + $encoded = array_merge( + $encoded, + $this->categories[$column][$feature] ?? array_fill(0, count($this->categories[$column]), 0) + ); + } + + return $encoded; + } +} diff --git a/tests/Preprocessing/OneHotEncoderTest.php b/tests/Preprocessing/OneHotEncoderTest.php new file mode 100644 index 00000000..a5666b75 --- /dev/null +++ b/tests/Preprocessing/OneHotEncoderTest.php @@ -0,0 +1,66 @@ +fit($samples); + $encoder->transform($samples); + + self::assertEquals([ + [1, 0, 1, 0, 1, 0], + [0, 1, 1, 0, 1, 0], + [1, 0, 0, 1, 0, 1], + [0, 1, 0, 1, 1, 0], + ], $samples); + } + + public function testThrowExceptionWhenUnknownCategory(): void + { + $encoder = new OneHotEncoder(); + $encoder->fit([ + ['fish', 'New York', 'regression'], + ['dog', 'New York', 'regression'], + ['fish', 'Vancouver', 'classification'], + ['dog', 'Vancouver', 'regression'], + ]); + $samples = [['fish', 'New York', 'ka boom']]; + + $this->expectException(InvalidArgumentException::class); + + $encoder->transform($samples); + } + + public function testIgnoreMissingCategory(): void + { + $encoder = new OneHotEncoder(true); + $encoder->fit([ + ['fish', 'New York', 'regression'], + ['dog', 'New York', 'regression'], + ['fish', 'Vancouver', 'classification'], + ['dog', 'Vancouver', 'regression'], + ]); + $samples = [['ka', 'boom', 'riko']]; + $encoder->transform($samples); + + self::assertEquals([ + [0, 0, 0, 0, 0, 0], + ], $samples); + } +} From 1a856c90999c8f3049adea1b9fcd74256f601420 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sat, 22 Jun 2019 22:54:47 +0200 Subject: [PATCH 317/328] Fix division by zero in ANOVA for small size dataset (#391) --- src/Math/Statistic/ANOVA.php | 4 +++ tests/FeatureSelection/SelectKBestTest.php | 42 ++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/Math/Statistic/ANOVA.php b/src/Math/Statistic/ANOVA.php index d233f84b..f89309ef 100644 --- a/src/Math/Statistic/ANOVA.php +++ b/src/Math/Statistic/ANOVA.php @@ -45,6 +45,10 @@ public static function oneWayF(array $samples): array return $s / $dfbn; }, $ssbn); $msw = array_map(function ($s) use ($dfwn) { + if ($dfwn === 0) { + return 1; + } + return $s / $dfwn; }, $sswn); diff --git a/tests/FeatureSelection/SelectKBestTest.php b/tests/FeatureSelection/SelectKBestTest.php index ebf119b5..5239954a 100644 --- a/tests/FeatureSelection/SelectKBestTest.php +++ b/tests/FeatureSelection/SelectKBestTest.php @@ -61,6 +61,48 @@ public function testSelectKBestWithRegressionScoring(): void ); } + public function testSelectKBestIssue386(): void + { + $samples = [ + [ + 0.0006729998475705993, + 0.0, + 0.999999773507577, + 0.0, + 0.0, + 6.66666515671718E-7, + 3.33333257835859E-6, + 6.66666515671718E-6, + ], + [ + 0.0006729998475849566, + 0.0, + 0.9999997735289103, + 0.0, + 0.0, + 6.666665156859402E-7, + 3.3333325784297012E-6, + 1.3333330313718804E-6, + ], + ]; + + $targets = [15.5844, 4.45284]; + + $selector = new SelectKBest(2); + $selector->fit($samples, $targets); + + self::assertEquals([ + -2.117582368135751E-22, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0097419586828951E-28, + 0.0, + 1.4222215779620095E-11, + ], $selector->scores()); + } + public function testThrowExceptionOnEmptyTargets(): void { $this->expectException(InvalidArgumentException::class); From dcf92063273f5bab022cbbad01736474113608ee Mon Sep 17 00:00:00 2001 From: Andrew Feeney Date: Mon, 28 Oct 2019 17:49:47 +1100 Subject: [PATCH 318/328] Fix grammatical error, and make wording consistent (#410) - "File can't be open" should be "File can't be opened" - Use cannot instead of "can not" or "can't" for consistency --- src/ModelManager.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ModelManager.php b/src/ModelManager.php index 057e0ead..349f8489 100644 --- a/src/ModelManager.php +++ b/src/ModelManager.php @@ -12,29 +12,29 @@ class ModelManager public function saveToFile(Estimator $estimator, string $filepath): void { if (!is_writable(dirname($filepath))) { - throw new FileException(sprintf('File "%s" can\'t be saved.', basename($filepath))); + throw new FileException(sprintf('File "%s" cannot be saved.', basename($filepath))); } $serialized = serialize($estimator); if (!isset($serialized[0])) { - throw new SerializeException(sprintf('Class "%s" can not be serialized.', gettype($estimator))); + throw new SerializeException(sprintf('Class "%s" cannot be serialized.', gettype($estimator))); } $result = file_put_contents($filepath, $serialized, LOCK_EX); if ($result === false) { - throw new FileException(sprintf('File "%s" can\'t be saved.', basename($filepath))); + throw new FileException(sprintf('File "%s" cannot be saved.', basename($filepath))); } } public function restoreFromFile(string $filepath): Estimator { if (!file_exists($filepath) || !is_readable($filepath)) { - throw new FileException(sprintf('File "%s" can\'t be open.', basename($filepath))); + throw new FileException(sprintf('File "%s" cannot be opened.', basename($filepath))); } $object = unserialize((string) file_get_contents($filepath), [Estimator::class]); if ($object === false) { - throw new SerializeException(sprintf('"%s" can not be unserialized.', basename($filepath))); + throw new SerializeException(sprintf('"%s" cannot be unserialized.', basename($filepath))); } return $object; From f30e576c704465e8c8abf4827f665ccaa1184882 Mon Sep 17 00:00:00 2001 From: Attila Bakos Date: Fri, 1 Nov 2019 09:31:17 +0000 Subject: [PATCH 319/328] Fix typo in Features list (#413) --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 8ce2751b..3457f686 100644 --- a/docs/index.md +++ b/docs/index.md @@ -56,7 +56,7 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( ## Features -* Association rule Lerning +* Association rule Learning * [Apriori](machine-learning/association/apriori.md) * Classification * [SVC](machine-learning/classification/svc.md) From 7d5c6b15a4389f6f10818a6e542532570de84f5e Mon Sep 17 00:00:00 2001 From: Attila Bakos Date: Sat, 2 Nov 2019 10:41:34 +0000 Subject: [PATCH 320/328] Updates to the documentation (linguistic corrections) (#414) * Fix typo in Features list * Update distance.md documentation * Fix grammatical mistakes in documentation * Fix grammatical mistakes in documentation * Fix grammatical mistakes in documentation * Fix grammatical mistakes in documentation * Fix grammatical mistakes in documentation * Fix grammatical mistakes in documentation * Fix grammatical mistakes in documentation * Fix grammatical mistakes in documentation * Fix grammatical mistakes in documentation --- docs/machine-learning/association/apriori.md | 8 ++++---- .../classification/k-nearest-neighbors.md | 4 ++-- .../classification/naive-bayes.md | 4 ++-- docs/machine-learning/classification/svc.md | 6 +++--- docs/machine-learning/clustering/dbscan.md | 4 ++-- docs/machine-learning/clustering/k-means.md | 10 +++++----- .../cross-validation/random-split.md | 6 +++--- .../stratified-random-split.md | 10 +++++----- .../datasets/array-dataset.md | 6 +++--- docs/machine-learning/datasets/csv-dataset.md | 4 ++-- .../datasets/files-dataset.md | 4 ++-- .../datasets/mnist-dataset.md | 4 ++-- docs/machine-learning/datasets/svm-dataset.md | 2 +- .../feature-extraction/tf-idf-transformer.md | 6 +++--- .../token-count-vectorizer.md | 4 ++-- .../feature-selection/selectkbest.md | 12 +++++------ .../feature-selection/variance-threshold.md | 10 +++++----- docs/machine-learning/metric/accuracy.md | 4 ++-- .../metric/classification-report.md | 2 +- .../metric/confusion-matrix.md | 2 +- .../multilayer-perceptron-classifier.md | 5 ++--- .../imputation-missing-values.md | 2 +- .../regression/least-squares.md | 12 +++++------ docs/machine-learning/regression/svr.md | 4 ++-- docs/machine-learning/workflow/pipeline.md | 8 ++++---- docs/math/distance.md | 20 +++++++++---------- docs/math/statistic.md | 2 +- 27 files changed, 82 insertions(+), 83 deletions(-) diff --git a/docs/machine-learning/association/apriori.md b/docs/machine-learning/association/apriori.md index bbf829ba..779ef289 100644 --- a/docs/machine-learning/association/apriori.md +++ b/docs/machine-learning/association/apriori.md @@ -15,7 +15,7 @@ $associator = new Apriori($support = 0.5, $confidence = 0.5); ### Train -To train a associator simply provide train samples and labels (as `array`). Example: +To train an associator, simply provide train samples and labels (as `array`). Example: ``` $samples = [['alpha', 'beta', 'epsilon'], ['alpha', 'beta', 'theta'], ['alpha', 'beta', 'epsilon'], ['alpha', 'beta', 'theta']]; @@ -31,7 +31,7 @@ You can train the associator using multiple data sets, predictions will be based ### Predict -To predict sample label use `predict` method. You can provide one sample or array of samples: +To predict sample label use the `predict` method. You can provide one sample or array of samples: ``` $associator->predict(['alpha','theta']); @@ -43,7 +43,7 @@ $associator->predict([['alpha','epsilon'],['beta','theta']]); ### Associating -Get generated association rules simply use `rules` method. +To get generated association rules, simply use the `rules` method. ``` $associator->getRules(); @@ -52,7 +52,7 @@ $associator->getRules(); ### Frequent item sets -Generating k-length frequent item sets simply use `apriori` method. +To generate k-length frequent item sets, simply use the `apriori` method. ``` $associator->apriori(); diff --git a/docs/machine-learning/classification/k-nearest-neighbors.md b/docs/machine-learning/classification/k-nearest-neighbors.md index a4eb96ca..a4ba53b0 100644 --- a/docs/machine-learning/classification/k-nearest-neighbors.md +++ b/docs/machine-learning/classification/k-nearest-neighbors.md @@ -14,7 +14,7 @@ $classifier = new KNearestNeighbors($k=3, new Minkowski($lambda=4)); ## Train -To train a classifier simply provide train samples and labels (as `array`). Example: +To train a classifier, simply provide train samples and labels (as `array`). Example: ``` $samples = [[1, 3], [1, 4], [2, 4], [3, 1], [4, 1], [4, 2]]; @@ -28,7 +28,7 @@ You can train the classifier using multiple data sets, predictions will be based ## Predict -To predict sample label use `predict` method. You can provide one sample or array of samples: +To predict sample label use the `predict` method. You can provide one sample or array of samples: ``` $classifier->predict([3, 2]); diff --git a/docs/machine-learning/classification/naive-bayes.md b/docs/machine-learning/classification/naive-bayes.md index af3b3577..57fcdcf6 100644 --- a/docs/machine-learning/classification/naive-bayes.md +++ b/docs/machine-learning/classification/naive-bayes.md @@ -4,7 +4,7 @@ Classifier based on applying Bayes' theorem with strong (naive) independence ass ### Train -To train a classifier simply provide train samples and labels (as `array`). Example: +To train a classifier, simply provide train samples and labels (as `array`). Example: ``` $samples = [[5, 1, 1], [1, 5, 1], [1, 1, 5]]; @@ -18,7 +18,7 @@ You can train the classifier using multiple data sets, predictions will be based ### Predict -To predict sample label use `predict` method. You can provide one sample or array of samples: +To predict sample label use the `predict` method. You can provide one sample or array of samples: ``` $classifier->predict([3, 1, 1]); diff --git a/docs/machine-learning/classification/svc.md b/docs/machine-learning/classification/svc.md index 99b4da01..3d87b62c 100644 --- a/docs/machine-learning/classification/svc.md +++ b/docs/machine-learning/classification/svc.md @@ -21,7 +21,7 @@ $classifier = new SVC(Kernel::RBF, $cost = 1000, $degree = 3, $gamma = 6); ### Train -To train a classifier simply provide train samples and labels (as `array`). Example: +To train a classifier, simply provide train samples and labels (as `array`). Example: ``` use Phpml\Classification\SVC; @@ -38,7 +38,7 @@ You can train the classifier using multiple data sets, predictions will be based ### Predict -To predict sample label use `predict` method. You can provide one sample or array of samples: +To predict sample label use the `predict` method. You can provide one sample or array of samples: ``` $classifier->predict([3, 2]); @@ -74,7 +74,7 @@ $classifier = new SVC( $classifier->train($samples, $labels); ``` -Then use `predictProbability` method instead of `predict`: +Then use the `predictProbability` method instead of `predict`: ``` $classifier->predictProbability([3, 2]); diff --git a/docs/machine-learning/clustering/dbscan.md b/docs/machine-learning/clustering/dbscan.md index c82a195e..ce011986 100644 --- a/docs/machine-learning/clustering/dbscan.md +++ b/docs/machine-learning/clustering/dbscan.md @@ -16,12 +16,12 @@ $dbscan = new DBSCAN($epsilon = 2, $minSamples = 3, new Minkowski($lambda=4)); ### Clustering -To divide the samples into clusters simply use `cluster` method. It's return the `array` of clusters with samples inside. +To divide the samples into clusters, simply use the `cluster` method. It returns the `array` of clusters with samples inside. ``` $samples = [[1, 1], [8, 7], [1, 2], [7, 8], [2, 1], [8, 9]]; $dbscan = new DBSCAN($epsilon = 2, $minSamples = 3); $dbscan->cluster($samples); -// return [0=>[[1, 1], ...], 1=>[[8, 7], ...]] +// return [0=>[[1, 1], ...], 1=>[[8, 7], ...]] ``` diff --git a/docs/machine-learning/clustering/k-means.md b/docs/machine-learning/clustering/k-means.md index 661f7172..132c2dc6 100644 --- a/docs/machine-learning/clustering/k-means.md +++ b/docs/machine-learning/clustering/k-means.md @@ -1,6 +1,6 @@ # K-means clustering -The K-Means algorithm clusters data by trying to separate samples in n groups of equal variance, minimizing a criterion known as the inertia or within-cluster sum-of-squares. +The K-Means algorithm clusters data by trying to separate samples in n groups of equal variance, minimizing a criterion known as the inertia or within-cluster sum-of-squares. This algorithm requires the number of clusters to be specified. ### Constructor Parameters @@ -15,11 +15,11 @@ $kmeans = new KMeans(4, KMeans::INIT_RANDOM); ### Clustering -To divide the samples into clusters simply use `cluster` method. It's return the `array` of clusters with samples inside. +To divide the samples into clusters, simply use the `cluster` method. It returns the `array` of clusters with samples inside. ``` $samples = [[1, 1], [8, 7], [1, 2], [7, 8], [2, 1], [8, 9]]; -Or if you need to keep your indentifiers along with yours samples you can use array keys as labels. +Or if you need to keep your identifiers along with yours samples you can use array keys as labels. $samples = [ 'Label1' => [1, 1], 'Label2' => [8, 7], 'Label3' => [1, 2]]; $kmeans = new KMeans(2); @@ -32,8 +32,8 @@ $kmeans->cluster($samples); #### kmeans++ (default) K-means++ method selects initial cluster centers for k-mean clustering in a smart way to speed up convergence. -It use the DASV seeding method consists of finding good initial centroids for the clusters. +It uses the DASV seeding method consists of finding good initial centroids for the clusters. #### random -Random initialization method chooses completely random centroid. It get the space boundaries to avoid placing clusters centroid too far from samples data. +Random initialization method chooses completely random centroid. It gets the space boundaries to avoid placing cluster centroids too far from samples data. diff --git a/docs/machine-learning/cross-validation/random-split.md b/docs/machine-learning/cross-validation/random-split.md index edfdded5..a5bf4022 100644 --- a/docs/machine-learning/cross-validation/random-split.md +++ b/docs/machine-learning/cross-validation/random-split.md @@ -1,20 +1,20 @@ # Random Split -One of the simplest methods from Cross-validation is implemented as `RandomSpilt` class. Samples are split to two groups: train group and test group. You can adjust number of samples in each group. +One of the simplest methods from Cross-validation is implemented as `RandomSpilt` class. Samples are split to two groups: train group and test group. You can adjust the number of samples in each group. ### Constructor Parameters * $dataset - object that implements `Dataset` interface * $testSize - a fraction of test split (float, from 0 to 1, default: 0.3) * $seed - seed for random generator (e.g. for tests) - + ``` $randomSplit = new RandomSplit($dataset, 0.2); ``` ### Samples and labels groups -To get samples or labels from test and train group you can use getters: +To get samples or labels from test and train group, you can use getters: ``` $dataset = new RandomSplit($dataset, 0.3, 1234); diff --git a/docs/machine-learning/cross-validation/stratified-random-split.md b/docs/machine-learning/cross-validation/stratified-random-split.md index d3f53be0..1a6caa19 100644 --- a/docs/machine-learning/cross-validation/stratified-random-split.md +++ b/docs/machine-learning/cross-validation/stratified-random-split.md @@ -1,22 +1,22 @@ # Stratified Random Split -Analogously to `RandomSpilt` class samples are split to two groups: train group and test group. +Analogously to `RandomSpilt` class, samples are split to two groups: train group and test group. Distribution of samples takes into account their targets and trying to divide them equally. -You can adjust number of samples in each group. +You can adjust the number of samples in each group. ### Constructor Parameters * $dataset - object that implements `Dataset` interface * $testSize - a fraction of test split (float, from 0 to 1, default: 0.3) * $seed - seed for random generator (e.g. for tests) - + ``` $split = new StratifiedRandomSplit($dataset, 0.2); ``` ### Samples and labels groups -To get samples or labels from test and train group you can use getters: +To get samples or labels from test and train group, you can use getters: ``` $dataset = new StratifiedRandomSplit($dataset, 0.3, 1234); @@ -41,4 +41,4 @@ $dataset = new ArrayDataset( $split = new StratifiedRandomSplit($dataset, 0.5); ``` -Split will have equals amount of each target. Two of the target `a` and two of `b`. +Split will have equal amounts of each target. Two of the target `a` and two of `b`. diff --git a/docs/machine-learning/datasets/array-dataset.md b/docs/machine-learning/datasets/array-dataset.md index 8bbcc37a..87bae48c 100644 --- a/docs/machine-learning/datasets/array-dataset.md +++ b/docs/machine-learning/datasets/array-dataset.md @@ -2,7 +2,7 @@ Helper class that holds data as PHP `array` type. Implements the `Dataset` interface which is used heavily in other classes. -### Constructors Parameters +### Constructor Parameters * $samples - (array) of samples * $labels - (array) of labels @@ -15,7 +15,7 @@ $dataset = new ArrayDataset([[1, 1], [2, 1], [3, 2], [4, 1]], ['a', 'a', 'b', 'b ### Samples and labels -To get samples or labels you can use getters: +To get samples or labels, you can use getters: ``` $dataset->getSamples(); @@ -24,7 +24,7 @@ $dataset->getTargets(); ### Remove columns -You can remove columns by index numbers, for example: +You can remove columns by their index numbers, for example: ``` use Phpml\Dataset\ArrayDataset; diff --git a/docs/machine-learning/datasets/csv-dataset.md b/docs/machine-learning/datasets/csv-dataset.md index d2efaaaf..557b7fce 100644 --- a/docs/machine-learning/datasets/csv-dataset.md +++ b/docs/machine-learning/datasets/csv-dataset.md @@ -2,11 +2,11 @@ Helper class that loads data from CSV file. It extends the `ArrayDataset`. -### Constructors Parameters +### Constructor Parameters * $filepath - (string) path to `.csv` file * $features - (int) number of columns that are features (starts from first column), last column must be a label -* $headingRow - (bool) define is file have a heading row (if `true` then first row will be ignored) +* $headingRow - (bool) define if the file has a heading row (if `true` then first row will be ignored) ``` $dataset = new CsvDataset('dataset.csv', 2, true); diff --git a/docs/machine-learning/datasets/files-dataset.md b/docs/machine-learning/datasets/files-dataset.md index f050cfda..6d55b3f5 100644 --- a/docs/machine-learning/datasets/files-dataset.md +++ b/docs/machine-learning/datasets/files-dataset.md @@ -2,7 +2,7 @@ Helper class that loads dataset from files. Use folder names as targets. It extends the `ArrayDataset`. -### Constructors Parameters +### Constructor Parameters * $rootPath - (string) path to root folder that contains files dataset @@ -42,7 +42,7 @@ data ... ``` -Load files data with `FilesDataset`: +Load files data with `FilesDataset`: ``` use Phpml\Dataset\FilesDataset; diff --git a/docs/machine-learning/datasets/mnist-dataset.md b/docs/machine-learning/datasets/mnist-dataset.md index 1ed50816..5c7a76e4 100644 --- a/docs/machine-learning/datasets/mnist-dataset.md +++ b/docs/machine-learning/datasets/mnist-dataset.md @@ -1,6 +1,6 @@ # MnistDataset -Helper class that load data from MNIST dataset: [http://yann.lecun.com/exdb/mnist/](http://yann.lecun.com/exdb/mnist/) +Helper class that loads data from MNIST dataset: [http://yann.lecun.com/exdb/mnist/](http://yann.lecun.com/exdb/mnist/) > The MNIST database of handwritten digits, available from this page, has a training set of 60,000 examples, and a test set of 10,000 examples. It is a subset of a larger set available from NIST. The digits have been size-normalized and centered in a fixed-size image. It is a good database for people who want to try learning techniques and pattern recognition methods on real-world data while spending minimal efforts on preprocessing and formatting. @@ -18,7 +18,7 @@ $trainDataset = new MnistDataset('train-images-idx3-ubyte', 'train-labels-idx1-u ### Samples and labels -To get samples or labels you can use getters: +To get samples or labels, you can use getters: ``` $dataset->getSamples(); diff --git a/docs/machine-learning/datasets/svm-dataset.md b/docs/machine-learning/datasets/svm-dataset.md index 8ac1c268..93a8cfb4 100644 --- a/docs/machine-learning/datasets/svm-dataset.md +++ b/docs/machine-learning/datasets/svm-dataset.md @@ -2,7 +2,7 @@ Helper class that loads data from SVM-Light format file. It extends the `ArrayDataset`. -### Constructors Parameters +### Constructor Parameters * $filepath - (string) path to the file diff --git a/docs/machine-learning/feature-extraction/tf-idf-transformer.md b/docs/machine-learning/feature-extraction/tf-idf-transformer.md index c592b8d6..4ac2e5dd 100644 --- a/docs/machine-learning/feature-extraction/tf-idf-transformer.md +++ b/docs/machine-learning/feature-extraction/tf-idf-transformer.md @@ -19,7 +19,7 @@ $transformer = new TfIdfTransformer($samples); ### Transformation -To transform a collection of text samples use `transform` method. Example: +To transform a collection of text samples, use the `transform` method. Example: ``` use Phpml\FeatureExtraction\TfIdfTransformer; @@ -28,7 +28,7 @@ $samples = [ [0 => 1, 1 => 1, 2 => 2, 3 => 1, 4 => 0, 5 => 0], [0 => 1, 1 => 1, 2 => 0, 3 => 0, 4 => 2, 5 => 3], ]; - + $transformer = new TfIdfTransformer($samples); $transformer->transform($samples); @@ -38,5 +38,5 @@ $samples = [ [0 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 0.602, 5 => 0.903], ]; */ - + ``` diff --git a/docs/machine-learning/feature-extraction/token-count-vectorizer.md b/docs/machine-learning/feature-extraction/token-count-vectorizer.md index 4dc52604..7d9405ef 100644 --- a/docs/machine-learning/feature-extraction/token-count-vectorizer.md +++ b/docs/machine-learning/feature-extraction/token-count-vectorizer.md @@ -16,7 +16,7 @@ $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer()); ### Transformation -To transform a collection of text samples use `transform` method. Example: +To transform a collection of text samples, use the `transform` method. Example: ``` $samples = [ @@ -42,7 +42,7 @@ $vectorizer->transform($samples); ### Vocabulary -You can extract vocabulary using `getVocabulary()` method. Example: +You can extract vocabulary using the `getVocabulary()` method. Example: ``` $vectorizer->getVocabulary(); diff --git a/docs/machine-learning/feature-selection/selectkbest.md b/docs/machine-learning/feature-selection/selectkbest.md index 2d8024cd..71d1ff92 100644 --- a/docs/machine-learning/feature-selection/selectkbest.md +++ b/docs/machine-learning/feature-selection/selectkbest.md @@ -5,7 +5,7 @@ ## Constructor Parameters * $k (int) - number of top features to select, rest will be removed (default: 10) -* $scoringFunction (ScoringFunction) - function that take samples and targets and return array with scores (default: ANOVAFValue) +* $scoringFunction (ScoringFunction) - function that takes samples and targets and returns an array with scores (default: ANOVAFValue) ```php use Phpml\FeatureSelection\SelectKBest; @@ -27,13 +27,13 @@ $selector->fit($samples = $dataset->getSamples(), $dataset->getTargets()); $selector->transform($samples); /* -$samples[0] = [1.4, 0.2]; +$samples[0] = [1.4, 0.2]; */ ``` ## Scores -You can get a array with the calculated score for each feature. +You can get an array with the calculated score for each feature. A higher value means that a given feature is better suited for learning. Of course, the rating depends on the scoring function used. @@ -56,7 +56,7 @@ $selector->scores(); float(1179.0343277002) [3]=> float(959.32440572573) -} +} */ ``` @@ -70,11 +70,11 @@ For classification: The test is applied to samples from two or more groups, possibly with differing sizes. For regression: - - **UnivariateLinearRegression** + - **UnivariateLinearRegression** Quick linear model for testing the effect of a single regressor, sequentially for many regressors. This is done in 2 steps: - 1. The cross correlation between each regressor and the target is computed, that is, ((X[:, i] - mean(X[:, i])) * (y - mean_y)) / (std(X[:, i]) *std(y)). - - 2. It is converted to an F score + - 2. It is converted to an F score ## Pipeline diff --git a/docs/machine-learning/feature-selection/variance-threshold.md b/docs/machine-learning/feature-selection/variance-threshold.md index 9c942e79..40218951 100644 --- a/docs/machine-learning/feature-selection/variance-threshold.md +++ b/docs/machine-learning/feature-selection/variance-threshold.md @@ -1,7 +1,7 @@ # Variance Threshold -`VarianceThreshold` is a simple baseline approach to feature selection. -It removes all features whose variance doesn’t meet some threshold. +`VarianceThreshold` is a simple baseline approach to feature selection. +It removes all features whose variance doesn’t meet some threshold. By default, it removes all zero-variance features, i.e. features that have the same value in all samples. ## Constructor Parameters @@ -16,10 +16,10 @@ $transformer = new VarianceThreshold(0.15); ## Example of use -As an example, suppose that we have a dataset with boolean features and +As an example, suppose that we have a dataset with boolean features and we want to remove all features that are either one or zero (on or off) -in more than 80% of the samples. -Boolean features are Bernoulli random variables, and the variance of such +in more than 80% of the samples. +Boolean features are Bernoulli random variables, and the variance of such variables is given by ``` Var[X] = p(1 - p) diff --git a/docs/machine-learning/metric/accuracy.md b/docs/machine-learning/metric/accuracy.md index 50459737..efdab23a 100644 --- a/docs/machine-learning/metric/accuracy.md +++ b/docs/machine-learning/metric/accuracy.md @@ -1,10 +1,10 @@ # Accuracy -Class for calculate classifier accuracy. +Class for calculating classifier accuracy. ### Score -To calculate classifier accuracy score use `score` static method. Parameters: +To calculate classifier accuracy score, use the `score` static method. Parameters: * $actualLabels - (array) true sample labels * $predictedLabels - (array) predicted labels (e.x. from test group) diff --git a/docs/machine-learning/metric/classification-report.md b/docs/machine-learning/metric/classification-report.md index 53f125b8..f5591a83 100644 --- a/docs/machine-learning/metric/classification-report.md +++ b/docs/machine-learning/metric/classification-report.md @@ -1,6 +1,6 @@ # Classification Report -Class for calculate main classifier metrics: precision, recall, F1 score and support. +Class for calculating main classifier metrics: precision, recall, F1 score and support. ### Report diff --git a/docs/machine-learning/metric/confusion-matrix.md b/docs/machine-learning/metric/confusion-matrix.md index b07443ac..4ff08c9c 100644 --- a/docs/machine-learning/metric/confusion-matrix.md +++ b/docs/machine-learning/metric/confusion-matrix.md @@ -1,6 +1,6 @@ # Confusion Matrix -Class for compute confusion matrix to evaluate the accuracy of a classification. +Class for computing confusion matrix to evaluate the accuracy of a classification. ### Example (all targets) diff --git a/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md b/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md index 7365a715..976d475f 100644 --- a/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md +++ b/docs/machine-learning/neural-network/multilayer-perceptron-classifier.md @@ -39,8 +39,7 @@ $mlp = new MLPClassifier(4, [$layer1, $layer2], ['a', 'b', 'c']); ## Train -To train a MLP simply provide train samples and labels (as array). Example: - +To train a MLP, simply provide train samples and labels (as array). Example: ``` $mlp->train( @@ -71,7 +70,7 @@ $mlp->setLearningRate(0.1); ## Predict -To predict sample label use predict method. You can provide one sample or array of samples: +To predict sample label use the `predict` method. You can provide one sample or array of samples: ``` $mlp->predict([[1, 1, 1, 1], [0, 0, 0, 0]]); diff --git a/docs/machine-learning/preprocessing/imputation-missing-values.md b/docs/machine-learning/preprocessing/imputation-missing-values.md index 219db22c..302d89d8 100644 --- a/docs/machine-learning/preprocessing/imputation-missing-values.md +++ b/docs/machine-learning/preprocessing/imputation-missing-values.md @@ -49,7 +49,7 @@ $data = [ ``` -You can also use `$samples` constructer parameter instead of `fit` method: +You can also use the `$samples` constructor parameter instead of the `fit` method: ``` use Phpml\Preprocessing\Imputer; diff --git a/docs/machine-learning/regression/least-squares.md b/docs/machine-learning/regression/least-squares.md index 84a32791..5505f13e 100644 --- a/docs/machine-learning/regression/least-squares.md +++ b/docs/machine-learning/regression/least-squares.md @@ -1,10 +1,10 @@ # LeastSquares Linear Regression -Linear model that use least squares method to approximate solution. +Linear model that uses least squares method to approximate solution. ### Train -To train a model simply provide train samples and targets values (as `array`). Example: +To train a model, simply provide train samples and targets values (as `array`). Example: ``` $samples = [[60], [61], [62], [63], [65]]; @@ -18,7 +18,7 @@ You can train the model using multiple data sets, predictions will be based on a ### Predict -To predict sample target value use `predict` method with sample to check (as `array`). Example: +To predict sample target value, use the `predict` method with sample to check (as `array`). Example: ``` $regression->predict([64]); @@ -27,8 +27,8 @@ $regression->predict([64]); ### Multiple Linear Regression -The term multiple attached to linear regression means that there are two or more sample parameters used to predict target. -For example you can use: mileage and production year to predict price of a car. +The term multiple attached to linear regression means that there are two or more sample parameters used to predict target. +For example you can use: mileage and production year to predict the price of a car. ``` $samples = [[73676, 1996], [77006, 1998], [10565, 2000], [146088, 1995], [15000, 2001], [65940, 2000], [9300, 2000], [93739, 1996], [153260, 1994], [17764, 2002], [57000, 1998], [15000, 2000]]; @@ -42,7 +42,7 @@ $regression->predict([60000, 1996]) ### Intercept and Coefficients -After you train your model you can get the intercept and coefficients array. +After you train your model, you can get the intercept and coefficients array. ``` $regression->getIntercept(); diff --git a/docs/machine-learning/regression/svr.md b/docs/machine-learning/regression/svr.md index 1678f5fc..14f9e6ab 100644 --- a/docs/machine-learning/regression/svr.md +++ b/docs/machine-learning/regression/svr.md @@ -21,7 +21,7 @@ $regression = new SVR(Kernel::LINEAR, $degree = 3, $epsilon=10.0); ### Train -To train a model simply provide train samples and targets values (as `array`). Example: +To train a model, simply provide train samples and targets values (as `array`). Example: ``` use Phpml\Regression\SVR; @@ -38,7 +38,7 @@ You can train the model using multiple data sets, predictions will be based on a ### Predict -To predict sample target value use `predict` method. You can provide one sample or array of samples: +To predict sample target value, use the `predict` method. You can provide one sample or array of samples: ``` $regression->predict([64]) diff --git a/docs/machine-learning/workflow/pipeline.md b/docs/machine-learning/workflow/pipeline.md index 34465eb5..b89b88ed 100644 --- a/docs/machine-learning/workflow/pipeline.md +++ b/docs/machine-learning/workflow/pipeline.md @@ -5,13 +5,12 @@ In machine learning, it is common to run a sequence of algorithms to process and * Split each document’s text into tokens. * Convert each document’s words into a numerical feature vector ([Token Count Vectorizer](machine-learning/feature-extraction/token-count-vectorizer/)). * Learn a prediction model using the feature vectors and labels. - -PHP-ML represents such a workflow as a Pipeline, which consists sequence of transformers and a estimator. +PHP-ML represents such a workflow as a Pipeline, which consists of a sequence of transformers and an estimator. ### Constructor Parameters -* $transformers (array|Transformer[]) - sequence of objects that implements Transformer interface +* $transformers (array|Transformer[]) - sequence of objects that implements the Transformer interface * $estimator (Estimator) - estimator that can train and predict ``` @@ -29,7 +28,8 @@ $pipeline = new Pipeline($transformers, $estimator); ### Example -First our pipeline replace missing value, then normalize samples and finally train SVC estimator. Thus prepared pipeline repeats each transformation step for predicted sample. +First, our pipeline replaces the missing value, then normalizes samples and finally trains the SVC estimator. +Thus prepared pipeline repeats each transformation step for predicted sample. ``` use Phpml\Classification\SVC; diff --git a/docs/math/distance.md b/docs/math/distance.md index 69707425..c7c3a989 100644 --- a/docs/math/distance.md +++ b/docs/math/distance.md @@ -4,7 +4,7 @@ Selected algorithms require the use of a function for calculating the distance. ### Euclidean -Class for calculation Euclidean distance. +Class for calculating Euclidean distance. ![euclidean](https://upload.wikimedia.org/math/8/4/9/849f040fd10bb86f7c85eb0bbe3566a4.png "Euclidean Distance") @@ -13,7 +13,7 @@ To calculate Euclidean distance: ``` $a = [4, 6]; $b = [2, 5]; - + $euclidean = new Euclidean(); $euclidean->distance($a, $b); // return 2.2360679774998 @@ -21,7 +21,7 @@ $euclidean->distance($a, $b); ### Manhattan -Class for calculation Manhattan distance. +Class for calculating Manhattan distance. ![manhattan](https://upload.wikimedia.org/math/4/c/5/4c568bd1d76a6b15e19cb2ac3ad75350.png "Manhattan Distance") @@ -30,7 +30,7 @@ To calculate Manhattan distance: ``` $a = [4, 6]; $b = [2, 5]; - + $manhattan = new Manhattan(); $manhattan->distance($a, $b); // return 3 @@ -38,7 +38,7 @@ $manhattan->distance($a, $b); ### Chebyshev -Class for calculation Chebyshev distance. +Class for calculating Chebyshev distance. ![chebyshev](https://upload.wikimedia.org/math/7/1/2/71200f7dbb43b3bcfbcbdb9e02ab0a0c.png "Chebyshev Distance") @@ -47,7 +47,7 @@ To calculate Chebyshev distance: ``` $a = [4, 6]; $b = [2, 5]; - + $chebyshev = new Chebyshev(); $chebyshev->distance($a, $b); // return 2 @@ -55,7 +55,7 @@ $chebyshev->distance($a, $b); ### Minkowski -Class for calculation Minkowski distance. +Class for calculating Minkowski distance. ![minkowski](https://upload.wikimedia.org/math/a/a/0/aa0c62083c12390cb15ac3217de88e66.png "Minkowski Distance") @@ -64,7 +64,7 @@ To calculate Minkowski distance: ``` $a = [4, 6]; $b = [2, 5]; - + $minkowski = new Minkowski(); $minkowski->distance($a, $b); // return 2.080 @@ -83,7 +83,7 @@ $minkowski->distance($a, $b); ### Custom distance -To apply your own function of distance use `Distance` interface. Example +To apply your own function of distance use the `Distance` interface. Example: ``` class CustomDistance implements Distance @@ -103,7 +103,7 @@ class CustomDistance implements Distance $distance[] = $a[$i] * $b[$i]; } - return min($distance); + return min($distance); } } ``` diff --git a/docs/math/statistic.md b/docs/math/statistic.md index 626828e9..a677a58e 100644 --- a/docs/math/statistic.md +++ b/docs/math/statistic.md @@ -7,7 +7,7 @@ Selected statistical methods. Correlation coefficients are used in statistics to measure how strong a relationship is between two variables. There are several types of correlation coefficient. ### Pearson correlation - + Pearson’s correlation or Pearson correlation is a correlation coefficient commonly used in linear regression. Example: From 0d5944132984e3ca29df347cc128959b788b7894 Mon Sep 17 00:00:00 2001 From: Marcin Michalski <57528542+marmichalski@users.noreply.github.com> Date: Fri, 8 Nov 2019 15:28:42 +0100 Subject: [PATCH 321/328] Update phpunit, phpbench, easy coding standard (#415) --- composer.json | 5 +- composer.lock | 1814 +++++++++-------- ecs.yml | 8 +- src/Math/Matrix.php | 6 +- src/ModelManager.php | 4 +- tests/Classification/Ensemble/BaggingTest.php | 4 +- .../Ensemble/RandomForestTest.php | 6 +- .../NeuralNetwork/Node/Neuron/SynapseTest.php | 4 +- tests/NeuralNetwork/Node/NeuronTest.php | 2 +- 9 files changed, 930 insertions(+), 923 deletions(-) diff --git a/composer.json b/composer.json index 52e783c3..19616b03 100644 --- a/composer.json +++ b/composer.json @@ -23,13 +23,12 @@ "php": "^7.2" }, "require-dev": { - "phpbench/phpbench": "^0.14.0", + "phpbench/phpbench": "^0.16.0", "phpstan/phpstan-phpunit": "^0.11", "phpstan/phpstan-shim": "^0.11", "phpstan/phpstan-strict-rules": "^0.11", "phpunit/phpunit": "^8.0", - "symplify/coding-standard": "^5.1", - "symplify/easy-coding-standard": "^5.1" + "symplify/easy-coding-standard": "^6.0" }, "config": { "preferred-install": "dist", diff --git a/composer.lock b/composer.lock index 4eb374c9..23641d3c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,30 +4,37 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b329ea9fc7b690ad2d498e85a445d214", + "content-hash": "8c178ab74a43d9b06449af4fcb823df1", "packages": [], "packages-dev": [ { "name": "beberlei/assert", - "version": "v2.9.6", + "version": "v3.2.6", "source": { "type": "git", "url": "https://github.com/beberlei/assert.git", - "reference": "ec9e4cf0b63890edce844ee3922e2b95a526e936" + "reference": "99508be011753690fe108ded450f5caaae180cfa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/beberlei/assert/zipball/ec9e4cf0b63890edce844ee3922e2b95a526e936", - "reference": "ec9e4cf0b63890edce844ee3922e2b95a526e936", + "url": "https://api.github.com/repos/beberlei/assert/zipball/99508be011753690fe108ded450f5caaae180cfa", + "reference": "99508be011753690fe108ded450f5caaae180cfa", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-mbstring": "*", - "php": ">=5.3" + "ext-simplexml": "*", + "php": "^7" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.1.1", - "phpunit/phpunit": "^4.8.35|^5.7" + "friendsofphp/php-cs-fixer": "*", + "phpstan/phpstan-shim": "*", + "phpunit/phpunit": ">=6.0.0 <8" + }, + "suggest": { + "ext-intl": "Needed to allow Assertion::count(), Assertion::isCountable(), Assertion::minCount(), and Assertion::maxCount() to operate on ResourceBundles" }, "type": "library", "autoload": { @@ -60,7 +67,7 @@ "assertion", "validation" ], - "time": "2018-06-11T17:15:25+00:00" + "time": "2019-10-10T10:33:57+00:00" }, { "name": "composer/semver", @@ -126,24 +133,24 @@ }, { "name": "composer/xdebug-handler", - "version": "1.3.2", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "d17708133b6c276d6e42ef887a877866b909d892" + "reference": "cbe23383749496fe0f373345208b79568e4bc248" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/d17708133b6c276d6e42ef887a877866b909d892", - "reference": "d17708133b6c276d6e42ef887a877866b909d892", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/cbe23383749496fe0f373345208b79568e4bc248", + "reference": "cbe23383749496fe0f373345208b79568e4bc248", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0", + "php": "^5.3.2 || ^7.0 || ^8.0", "psr/log": "^1.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8" }, "type": "library", "autoload": { @@ -161,25 +168,25 @@ "email": "john-stevenson@blueyonder.co.uk" } ], - "description": "Restarts a process without xdebug.", + "description": "Restarts a process without Xdebug.", "keywords": [ "Xdebug", "performance" ], - "time": "2019-01-28T20:25:53+00:00" + "time": "2019-11-06T16:40:04+00:00" }, { "name": "doctrine/annotations", - "version": "v1.6.1", + "version": "v1.8.0", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "53120e0eb10355388d6ccbe462f1fea34ddadb24" + "reference": "904dca4eb10715b92569fbcd79e201d5c349b6bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/53120e0eb10355388d6ccbe462f1fea34ddadb24", - "reference": "53120e0eb10355388d6ccbe462f1fea34ddadb24", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/904dca4eb10715b92569fbcd79e201d5c349b6bc", + "reference": "904dca4eb10715b92569fbcd79e201d5c349b6bc", "shasum": "" }, "require": { @@ -188,12 +195,12 @@ }, "require-dev": { "doctrine/cache": "1.*", - "phpunit/phpunit": "^6.4" + "phpunit/phpunit": "^7.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.6.x-dev" + "dev-master": "1.7.x-dev" } }, "autoload": { @@ -206,72 +213,10 @@ "MIT" ], "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, { "name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com" }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Docblock Annotations Parser", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "time": "2019-03-25T19:12:02+00:00" - }, - { - "name": "doctrine/inflector", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/inflector.git", - "reference": "5527a48b7313d15261292c149e55e26eae771b0a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/5527a48b7313d15261292c149e55e26eae771b0a", - "reference": "5527a48b7313d15261292c149e55e26eae771b0a", - "shasum": "" - }, - "require": { - "php": "^7.1" - }, - "require-dev": { - "phpunit/phpunit": "^6.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -280,10 +225,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -293,15 +234,14 @@ "email": "schmittjoh@gmail.com" } ], - "description": "Common String Manipulations with regard to casing and singular/plural rules.", + "description": "Docblock Annotations Parser", "homepage": "http://www.doctrine-project.org", "keywords": [ - "inflection", - "pluralize", - "singularize", - "string" + "annotations", + "docblock", + "parser" ], - "time": "2018-01-09T20:05:19+00:00" + "time": "2019-10-01T18:55:10+00:00" }, { "name": "doctrine/instantiator", @@ -361,30 +301,35 @@ }, { "name": "doctrine/lexer", - "version": "v1.0.1", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + "reference": "e17f069ede36f7534b95adec71910ed1b49c74ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/e17f069ede36f7534b95adec71910ed1b49c74ea", + "reference": "e17f069ede36f7534b95adec71910ed1b49c74ea", "shasum": "" }, "require": { - "php": ">=5.3.2" + "php": "^7.2" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11.8", + "phpunit/phpunit": "^8.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { - "psr-0": { - "Doctrine\\Common\\Lexer\\": "lib/" + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" } }, "notification-url": "https://packagist.org/downloads/", @@ -392,39 +337,42 @@ "MIT" ], "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, { "name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com" }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, { "name": "Johannes Schmitt", "email": "schmittjoh@gmail.com" } ], - "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "http://www.doctrine-project.org", + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", "keywords": [ + "annotations", + "docblock", "lexer", - "parser" + "parser", + "php" ], - "time": "2014-09-09T13:34:57+00:00" + "time": "2019-07-30T19:33:28+00:00" }, { "name": "friendsofphp/php-cs-fixer", - "version": "v2.15.0", + "version": "v2.16.0", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "adfab51ae979ee8b0fcbc55aa231ec2786cb1f91" + "reference": "ceaff36bee1ed3f1bbbedca36d2528c0826c336d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/adfab51ae979ee8b0fcbc55aa231ec2786cb1f91", - "reference": "adfab51ae979ee8b0fcbc55aa231ec2786cb1f91", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/ceaff36bee1ed3f1bbbedca36d2528c0826c336d", + "reference": "ceaff36bee1ed3f1bbbedca36d2528c0826c336d", "shasum": "" }, "require": { @@ -454,9 +402,10 @@ "php-cs-fixer/accessible-object": "^1.0", "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.1", "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.1", - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.1", "phpunitgoodpractices/traits": "^1.8", - "symfony/phpunit-bridge": "^4.0" + "symfony/phpunit-bridge": "^4.3", + "symfony/yaml": "^3.0 || ^4.0" }, "suggest": { "ext-mbstring": "For handling non-UTF8 characters in cache signature.", @@ -468,11 +417,6 @@ "php-cs-fixer" ], "type": "application", - "extra": { - "branch-alias": { - "dev-master": "2.15-dev" - } - }, "autoload": { "psr-4": { "PhpCsFixer\\": "src/" @@ -494,122 +438,17 @@ "MIT" ], "authors": [ - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - }, { "name": "Fabien Potencier", "email": "fabien@symfony.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "time": "2019-05-06T07:13:51+00:00" - }, - { - "name": "illuminate/contracts", - "version": "v5.8.17", - "source": { - "type": "git", - "url": "https://github.com/illuminate/contracts.git", - "reference": "acd524087bcebcc0979748e84ccc0876395ae497" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/illuminate/contracts/zipball/acd524087bcebcc0979748e84ccc0876395ae497", - "reference": "acd524087bcebcc0979748e84ccc0876395ae497", - "shasum": "" - }, - "require": { - "php": "^7.1.3", - "psr/container": "^1.0", - "psr/simple-cache": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.8-dev" - } - }, - "autoload": { - "psr-4": { - "Illuminate\\Contracts\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - } - ], - "description": "The Illuminate Contracts package.", - "homepage": "https://laravel.com", - "time": "2019-05-14T10:54:47+00:00" - }, - { - "name": "illuminate/support", - "version": "v5.8.17", - "source": { - "type": "git", - "url": "https://github.com/illuminate/support.git", - "reference": "128c6aaa1599811a04fd10c2d68e1213e433da3f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/illuminate/support/zipball/128c6aaa1599811a04fd10c2d68e1213e433da3f", - "reference": "128c6aaa1599811a04fd10c2d68e1213e433da3f", - "shasum": "" - }, - "require": { - "doctrine/inflector": "^1.1", - "ext-json": "*", - "ext-mbstring": "*", - "illuminate/contracts": "5.8.*", - "nesbot/carbon": "^1.26.3 || ^2.0", - "php": "^7.1.3" - }, - "conflict": { - "tightenco/collect": "<5.5.33" - }, - "suggest": { - "illuminate/filesystem": "Required to use the composer class (5.8.*).", - "moontoast/math": "Required to use ordered UUIDs (^1.1).", - "ramsey/uuid": "Required to use Str::uuid() (^3.7).", - "symfony/process": "Required to use the composer class (^4.2).", - "symfony/var-dumper": "Required to use the dd function (^4.2).", - "vlucas/phpdotenv": "Required to use the env helper (^3.3)." - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.8-dev" - } - }, - "autoload": { - "psr-4": { - "Illuminate\\Support\\": "" }, - "files": [ - "helpers.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" } ], - "description": "The Illuminate Support package.", - "homepage": "https://laravel.com", - "time": "2019-05-14T13:37:34+00:00" + "description": "A tool to automatically fix PHP code style", + "time": "2019-11-03T13:31:09+00:00" }, { "name": "jean85/pretty-package-versions", @@ -664,30 +503,27 @@ }, { "name": "lstrojny/functional-php", - "version": "1.9.0", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/lstrojny/functional-php.git", - "reference": "f5b3b4424dcddb406d3dcfcae0d1bc0060099a78" + "reference": "809947093034cb9db1ce69b8b4536f4bbb6fe93e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lstrojny/functional-php/zipball/f5b3b4424dcddb406d3dcfcae0d1bc0060099a78", - "reference": "f5b3b4424dcddb406d3dcfcae0d1bc0060099a78", + "url": "https://api.github.com/repos/lstrojny/functional-php/zipball/809947093034cb9db1ce69b8b4536f4bbb6fe93e", + "reference": "809947093034cb9db1ce69b8b4536f4bbb6fe93e", "shasum": "" }, "require": { "php": "~7" }, "require-dev": { - "phpunit/phpunit": "~6" + "friendsofphp/php-cs-fixer": "^2.14", + "phpunit/phpunit": "~6", + "squizlabs/php_codesniffer": "~3.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2-dev" - } - }, "autoload": { "psr-4": { "Functional\\": "src/Functional" @@ -702,6 +538,7 @@ "src/Functional/Compose.php", "src/Functional/Concat.php", "src/Functional/Contains.php", + "src/Functional/Converge.php", "src/Functional/Curry.php", "src/Functional/CurryN.php", "src/Functional/Difference.php", @@ -759,6 +596,7 @@ "src/Functional/ReduceRight.php", "src/Functional/Reindex.php", "src/Functional/Reject.php", + "src/Functional/Repeat.php", "src/Functional/Retry.php", "src/Functional/Select.php", "src/Functional/SelectKeys.php", @@ -772,6 +610,8 @@ "src/Functional/Tap.php", "src/Functional/Tail.php", "src/Functional/TailRecursion.php", + "src/Functional/TakeLeft.php", + "src/Functional/TakeRight.php", "src/Functional/True.php", "src/Functional/Truthy.php", "src/Functional/Unique.php", @@ -799,20 +639,20 @@ "keywords": [ "functional" ], - "time": "2018-12-03T16:47:05+00:00" + "time": "2019-09-26T09:38:13+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.9.1", + "version": "1.9.3", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72" + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72", - "reference": "e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea", + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea", "shasum": "" }, "require": { @@ -847,80 +687,20 @@ "object", "object graph" ], - "time": "2019-04-07T13:18:21+00:00" - }, - { - "name": "nesbot/carbon", - "version": "2.17.1", - "source": { - "type": "git", - "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "96acbc0c03782e8115156dd4dd8b736267155066" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/96acbc0c03782e8115156dd4dd8b736267155066", - "reference": "96acbc0c03782e8115156dd4dd8b736267155066", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": "^7.1.8 || ^8.0", - "symfony/translation": "^3.4 || ^4.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.14 || ^3.0", - "kylekatarnls/multi-tester": "^1.1", - "phpmd/phpmd": "^2.6", - "phpstan/phpstan": "^0.11", - "phpunit/phpunit": "^7.5 || ^8.0", - "squizlabs/php_codesniffer": "^3.4" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Carbon\\Laravel\\ServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Carbon\\": "src/Carbon/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Brian Nesbitt", - "email": "brian@nesbot.com", - "homepage": "http://nesbot.com" - } - ], - "description": "A simple API extension for DateTime.", - "homepage": "http://carbon.nesbot.com", - "keywords": [ - "date", - "datetime", - "time" - ], - "time": "2019-04-27T18:04:27+00:00" + "time": "2019-08-09T12:45:53+00:00" }, { "name": "nette/finder", - "version": "v2.5.0", + "version": "v2.5.1", "source": { "type": "git", "url": "https://github.com/nette/finder.git", - "reference": "6be1b83ea68ac558aff189d640abe242e0306fe2" + "reference": "14164e1ddd69e9c5f627ff82a10874b3f5bba5fe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/finder/zipball/6be1b83ea68ac558aff189d640abe242e0306fe2", - "reference": "6be1b83ea68ac558aff189d640abe242e0306fe2", + "url": "https://api.github.com/repos/nette/finder/zipball/14164e1ddd69e9c5f627ff82a10874b3f5bba5fe", + "reference": "14164e1ddd69e9c5f627ff82a10874b3f5bba5fe", "shasum": "" }, "require": { @@ -969,30 +749,27 @@ "iterator", "nette" ], - "time": "2019-02-28T18:13:25+00:00" + "time": "2019-07-11T18:02:17+00:00" }, { "name": "nette/robot-loader", - "version": "v3.1.1", + "version": "v3.2.0", "source": { "type": "git", "url": "https://github.com/nette/robot-loader.git", - "reference": "3e8d75d6d976e191bdf46752ca40a286671219d2" + "reference": "0712a0e39ae7956d6a94c0ab6ad41aa842544b5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/robot-loader/zipball/3e8d75d6d976e191bdf46752ca40a286671219d2", - "reference": "3e8d75d6d976e191bdf46752ca40a286671219d2", + "url": "https://api.github.com/repos/nette/robot-loader/zipball/0712a0e39ae7956d6a94c0ab6ad41aa842544b5c", + "reference": "0712a0e39ae7956d6a94c0ab6ad41aa842544b5c", "shasum": "" }, "require": { "ext-tokenizer": "*", - "nette/finder": "^2.3 || ^3.0", - "nette/utils": "^2.4 || ^3.0", - "php": ">=5.6.0" - }, - "conflict": { - "nette/nette": "<2.2" + "nette/finder": "^2.5", + "nette/utils": "^3.0", + "php": ">=7.1" }, "require-dev": { "nette/tester": "^2.0", @@ -1001,7 +778,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -1025,7 +802,7 @@ "homepage": "https://nette.org/contributors" } ], - "description": "🍀 Nette RobotLoader: high performance and comfortable autoloader that will search and autoload classes within your application.", + "description": "? Nette RobotLoader: high performance and comfortable autoloader that will search and autoload classes within your application.", "homepage": "https://nette.org", "keywords": [ "autoload", @@ -1034,27 +811,24 @@ "nette", "trait" ], - "time": "2019-03-01T20:23:02+00:00" + "time": "2019-03-08T21:57:24+00:00" }, { "name": "nette/utils", - "version": "v2.5.3", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "17b9f76f2abd0c943adfb556e56f2165460b15ce" + "reference": "c133e18c922dcf3ad07673077d92d92cef25a148" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/17b9f76f2abd0c943adfb556e56f2165460b15ce", - "reference": "17b9f76f2abd0c943adfb556e56f2165460b15ce", + "url": "https://api.github.com/repos/nette/utils/zipball/c133e18c922dcf3ad07673077d92d92cef25a148", + "reference": "c133e18c922dcf3ad07673077d92d92cef25a148", "shasum": "" }, "require": { - "php": ">=5.6.0" - }, - "conflict": { - "nette/nette": "<2.2" + "php": ">=7.1" }, "require-dev": { "nette/tester": "~2.0", @@ -1063,23 +837,21 @@ "suggest": { "ext-gd": "to use Image", "ext-iconv": "to use Strings::webalize() and toAscii()", - "ext-intl": "for script transliteration in Strings::webalize() and toAscii()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", "ext-json": "to use Nette\\Utils\\Json", "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", "ext-xml": "to use Strings::length() etc. when mbstring is not available" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "3.0-dev" } }, "autoload": { "classmap": [ "src/" - ], - "files": [ - "src/loader.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -1116,38 +888,39 @@ "utility", "validation" ], - "time": "2018-09-18T10:22:16+00:00" + "time": "2019-10-21T20:40:16+00:00" }, { "name": "ocramius/package-versions", - "version": "1.4.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/Ocramius/PackageVersions.git", - "reference": "a4d4b60d0e60da2487bd21a2c6ac089f85570dbb" + "reference": "1d32342b8c1eb27353c8887c366147b4c2da673c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/a4d4b60d0e60da2487bd21a2c6ac089f85570dbb", - "reference": "a4d4b60d0e60da2487bd21a2c6ac089f85570dbb", + "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/1d32342b8c1eb27353c8887c366147b4c2da673c", + "reference": "1d32342b8c1eb27353c8887c366147b4c2da673c", "shasum": "" }, "require": { "composer-plugin-api": "^1.0.0", - "php": "^7.1.0" + "php": "^7.3.0" }, "require-dev": { - "composer/composer": "^1.6.3", - "doctrine/coding-standard": "^5.0.1", + "composer/composer": "^1.8.6", + "doctrine/coding-standard": "^6.0.0", "ext-zip": "*", - "infection/infection": "^0.7.1", - "phpunit/phpunit": "^7.0.0" + "infection/infection": "^0.13.4", + "phpunit/phpunit": "^8.2.5", + "vimeo/psalm": "^3.4.9" }, "type": "composer-plugin", "extra": { "class": "PackageVersions\\Installer", "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "1.6.x-dev" } }, "autoload": { @@ -1166,7 +939,7 @@ } ], "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", - "time": "2019-02-21T12:16:21+00:00" + "time": "2019-07-17T15:49:50+00:00" }, { "name": "paragonie/random_compat", @@ -1457,47 +1230,49 @@ }, { "name": "phpbench/phpbench", - "version": "0.14.0", + "version": "0.16.10", "source": { "type": "git", "url": "https://github.com/phpbench/phpbench.git", - "reference": "ea2c7ca1cdbfa952b8d50c4f36fc233dbfabe3c9" + "reference": "00c18b1ab87dbda66e8972c8602a14dd08c69914" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpbench/phpbench/zipball/ea2c7ca1cdbfa952b8d50c4f36fc233dbfabe3c9", - "reference": "ea2c7ca1cdbfa952b8d50c4f36fc233dbfabe3c9", + "url": "https://api.github.com/repos/phpbench/phpbench/zipball/00c18b1ab87dbda66e8972c8602a14dd08c69914", + "reference": "00c18b1ab87dbda66e8972c8602a14dd08c69914", "shasum": "" }, "require": { - "beberlei/assert": "^2.4", + "beberlei/assert": "^2.4 || ^3.0", "doctrine/annotations": "^1.2.7", "ext-dom": "*", "ext-json": "*", "ext-pcre": "*", "ext-reflection": "*", "ext-spl": "*", - "lstrojny/functional-php": "1.0|^1.2.3", - "php": "^7.0", - "phpbench/container": "~1.0", + "lstrojny/functional-php": "1.0 || ^1.2.3", + "php": "^7.1", + "phpbench/container": "~1.2", "phpbench/dom": "~0.2.0", - "seld/jsonlint": "^1.0", - "symfony/console": "^2.6|^3.0|^4.0", - "symfony/debug": "^2.4|^3.0|^4.0", - "symfony/filesystem": "^2.4|^3.0|^4.0", - "symfony/finder": "^2.4|^3.0|^4.0", - "symfony/options-resolver": "^2.6|^3.0|^4.0", - "symfony/process": "^2.1|^3.0|^4.0" + "seld/jsonlint": "^1.1", + "symfony/console": "^3.2 || ^4.0", + "symfony/debug": "^2.4 || ^3.0 || ^4.0", + "symfony/filesystem": "^2.4 || ^3.0 || ^4.0", + "symfony/finder": "^2.4 || ^3.0 || ^4.0", + "symfony/options-resolver": "^2.6 || ^3.0 || ^4.0", + "symfony/process": "^2.1 || ^3.0 || ^4.0", + "webmozart/path-util": "^2.3" }, "require-dev": { "doctrine/dbal": "^2.4", - "liip/rmt": "^1.2", + "friendsofphp/php-cs-fixer": "^2.13.1", "padraic/phar-updater": "^1.0", - "phpstan/phpstan": "^0.8.5", - "phpunit/phpunit": "^6.0" + "phpstan/phpstan": "^0.10.7", + "phpunit/phpunit": "^6.5 || ^7.0" }, "suggest": { - "ext-xdebug": "For XDebug profiling extension." + "ext-curl": "For (web) reports extension", + "ext-xdebug": "For Xdebug profiling extension." }, "bin": [ "bin/phpbench" @@ -1512,7 +1287,8 @@ "psr-4": { "PhpBench\\": "lib/", "PhpBench\\Extensions\\Dbal\\": "extensions/dbal/lib/", - "PhpBench\\Extensions\\XDebug\\": "extensions/xdebug/lib/" + "PhpBench\\Extensions\\XDebug\\": "extensions/xdebug/lib/", + "PhpBench\\Extensions\\Reports\\": "extensions/reports/lib/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1526,39 +1302,37 @@ } ], "description": "PHP Benchmarking Framework", - "time": "2017-12-05T15:55:57+00:00" + "time": "2019-09-01T08:08:02+00:00" }, { "name": "phpdocumentor/reflection-common", - "version": "1.0.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", "shasum": "" }, "require": { - "php": ">=5.5" + "php": ">=7.1" }, "require-dev": { - "phpunit/phpunit": "^4.6" + "phpunit/phpunit": "~6" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src" - ] + "phpDocumentor\\Reflection\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1580,30 +1354,30 @@ "reflection", "static analysis" ], - "time": "2017-09-11T18:02:19+00:00" + "time": "2018-08-07T13:53:10+00:00" }, { "name": "phpdocumentor/reflection-docblock", - "version": "4.3.1", + "version": "4.3.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c" + "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", - "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/b83ff7cfcfee7827e1e78b637a5904fe6a96698e", + "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e", "shasum": "" }, "require": { "php": "^7.0", - "phpdocumentor/reflection-common": "^1.0.0", - "phpdocumentor/type-resolver": "^0.4.0", + "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0", + "phpdocumentor/type-resolver": "~0.4 || ^1.0.0", "webmozart/assert": "^1.0" }, "require-dev": { - "doctrine/instantiator": "~1.0.5", + "doctrine/instantiator": "^1.0.5", "mockery/mockery": "^1.0", "phpunit/phpunit": "^6.4" }, @@ -1631,41 +1405,40 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2019-04-30T17:48:53+00:00" + "time": "2019-09-12T14:27:41+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "0.4.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0", - "phpdocumentor/reflection-common": "^1.0" + "php": "^7.1", + "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^5.2||^4.8.24" + "ext-tokenizer": "^7.1", + "mockery/mockery": "~1", + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1678,26 +1451,27 @@ "email": "me@mikevanriel.com" } ], - "time": "2017-07-14T14:27:02+00:00" + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "time": "2019-08-22T18:11:29+00:00" }, { "name": "phpspec/prophecy", - "version": "1.8.0", + "version": "1.9.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" + "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/f6811d96d97bdf400077a0cc100ae56aa32b9203", + "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", "sebastian/comparator": "^1.1|^2.0|^3.0", "sebastian/recursion-context": "^1.0|^2.0|^3.0" }, @@ -1712,8 +1486,8 @@ } }, "autoload": { - "psr-0": { - "Prophecy\\": "src/" + "psr-4": { + "Prophecy\\": "src/Prophecy" } }, "notification-url": "https://packagist.org/downloads/", @@ -1741,27 +1515,27 @@ "spy", "stub" ], - "time": "2018-08-05T17:53:17+00:00" + "time": "2019-10-03T11:07:50+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "0.11.1", + "version": "0.11.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "0d339995c3c6acc56bc912959f436298c70d13ab" + "reference": "fbf2ad56c3b13189d29655e226c9b1da47c2fad9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/0d339995c3c6acc56bc912959f436298c70d13ab", - "reference": "0d339995c3c6acc56bc912959f436298c70d13ab", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/fbf2ad56c3b13189d29655e226c9b1da47c2fad9", + "reference": "fbf2ad56c3b13189d29655e226c9b1da47c2fad9", "shasum": "" }, "require": { "nikic/php-parser": "^4.0", "php": "~7.1", "phpstan/phpdoc-parser": "^0.3", - "phpstan/phpstan": "^0.11" + "phpstan/phpstan": "^0.11.4" }, "conflict": { "phpunit/phpunit": "<7.0" @@ -1783,7 +1557,8 @@ }, "phpstan": { "includes": [ - "extension.neon" + "extension.neon", + "rules.neon" ] } }, @@ -1797,20 +1572,20 @@ "MIT" ], "description": "PHPUnit extensions and rules for PHPStan", - "time": "2019-05-10T20:33:17+00:00" + "time": "2019-05-17T17:50:16+00:00" }, { "name": "phpstan/phpstan-shim", - "version": "0.11.6", + "version": "0.11.19", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-shim.git", - "reference": "f38e0658f497517aff0635f4a622858a5d20c37f" + "reference": "e3c06b1d63691dae644ae1e5b540905c8c021801" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-shim/zipball/f38e0658f497517aff0635f4a622858a5d20c37f", - "reference": "f38e0658f497517aff0635f4a622858a5d20c37f", + "url": "https://api.github.com/repos/phpstan/phpstan-shim/zipball/e3c06b1d63691dae644ae1e5b540905c8c021801", + "reference": "e3c06b1d63691dae644ae1e5b540905c8c021801", "shasum": "" }, "require": { @@ -1841,26 +1616,26 @@ "MIT" ], "description": "PHPStan Phar distribution", - "time": "2019-05-08T19:07:51+00:00" + "time": "2019-10-22T20:46:16+00:00" }, { "name": "phpstan/phpstan-strict-rules", - "version": "0.11", + "version": "0.11.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "747a742b26a35ef4e4ebef5ec4490ad74eebcbc0" + "reference": "a203a7afdda073d4ea405a6d9007a5b32de3be61" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/747a742b26a35ef4e4ebef5ec4490ad74eebcbc0", - "reference": "747a742b26a35ef4e4ebef5ec4490ad74eebcbc0", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/a203a7afdda073d4ea405a6d9007a5b32de3be61", + "reference": "a203a7afdda073d4ea405a6d9007a5b32de3be61", "shasum": "" }, "require": { "nikic/php-parser": "^4.0", "php": "~7.1", - "phpstan/phpstan": "^0.11" + "phpstan/phpstan": "^0.11.4" }, "require-dev": { "consistence/coding-standard": "^3.0.1", @@ -1871,10 +1646,15 @@ "phpunit/phpunit": "^7.0", "slevomat/coding-standard": "^4.5.2" }, - "type": "library", + "type": "phpstan-extension", "extra": { "branch-alias": { "dev-master": "0.11-dev" + }, + "phpstan": { + "includes": [ + "rules.neon" + ] } }, "autoload": { @@ -1887,20 +1667,20 @@ "MIT" ], "description": "Extra strict and opinionated rules for PHPStan", - "time": "2019-01-14T09:56:55+00:00" + "time": "2019-05-12T16:59:47+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "7.0.3", + "version": "7.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "0317a769a81845c390e19684d9ba25d7f6aa4707" + "reference": "aa0d179a13284c7420fc281fc32750e6cc7c9e2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/0317a769a81845c390e19684d9ba25d7f6aa4707", - "reference": "0317a769a81845c390e19684d9ba25d7f6aa4707", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa0d179a13284c7420fc281fc32750e6cc7c9e2f", + "reference": "aa0d179a13284c7420fc281fc32750e6cc7c9e2f", "shasum": "" }, "require": { @@ -1909,17 +1689,17 @@ "php": "^7.2", "phpunit/php-file-iterator": "^2.0.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^3.0.1", + "phpunit/php-token-stream": "^3.1.1", "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^4.1", + "sebastian/environment": "^4.2.2", "sebastian/version": "^2.0.1", - "theseer/tokenizer": "^1.1" + "theseer/tokenizer": "^1.1.3" }, "require-dev": { - "phpunit/phpunit": "^8.0" + "phpunit/phpunit": "^8.2.2" }, "suggest": { - "ext-xdebug": "^2.6.1" + "ext-xdebug": "^2.7.2" }, "type": "library", "extra": { @@ -1950,7 +1730,7 @@ "testing", "xunit" ], - "time": "2019-02-26T07:38:26+00:00" + "time": "2019-09-17T06:24:36+00:00" }, { "name": "phpunit/php-file-iterator", @@ -2045,16 +1825,16 @@ }, { "name": "phpunit/php-timer", - "version": "2.1.1", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "8b389aebe1b8b0578430bda0c7c95a829608e059" + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b389aebe1b8b0578430bda0c7c95a829608e059", - "reference": "8b389aebe1b8b0578430bda0c7c95a829608e059", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e", + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e", "shasum": "" }, "require": { @@ -2090,20 +1870,20 @@ "keywords": [ "timer" ], - "time": "2019-02-20T10:12:59+00:00" + "time": "2019-06-07T04:22:29+00:00" }, { "name": "phpunit/php-token-stream", - "version": "3.0.1", + "version": "3.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18" + "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/c99e3be9d3e85f60646f152f9002d46ed7770d18", - "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff", + "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff", "shasum": "" }, "require": { @@ -2116,7 +1896,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.1-dev" } }, "autoload": { @@ -2139,46 +1919,47 @@ "keywords": [ "tokenizer" ], - "time": "2018-10-30T05:52:18+00:00" + "time": "2019-09-17T06:23:10+00:00" }, { "name": "phpunit/phpunit", - "version": "8.1.5", + "version": "8.4.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "01392d4b5878aa617e8d9bc7a529e5febc8fe956" + "reference": "67f9e35bffc0dd52d55d565ddbe4230454fd6a4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/01392d4b5878aa617e8d9bc7a529e5febc8fe956", - "reference": "01392d4b5878aa617e8d9bc7a529e5febc8fe956", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/67f9e35bffc0dd52d55d565ddbe4230454fd6a4e", + "reference": "67f9e35bffc0dd52d55d565ddbe4230454fd6a4e", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.1", + "doctrine/instantiator": "^1.2.0", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.7", - "phar-io/manifest": "^1.0.2", - "phar-io/version": "^2.0", + "myclabs/deep-copy": "^1.9.1", + "phar-io/manifest": "^1.0.3", + "phar-io/version": "^2.0.1", "php": "^7.2", - "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^7.0", - "phpunit/php-file-iterator": "^2.0.1", + "phpspec/prophecy": "^1.8.1", + "phpunit/php-code-coverage": "^7.0.7", + "phpunit/php-file-iterator": "^2.0.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.1", - "sebastian/comparator": "^3.0", - "sebastian/diff": "^3.0", - "sebastian/environment": "^4.1", - "sebastian/exporter": "^3.1", - "sebastian/global-state": "^3.0", + "phpunit/php-timer": "^2.1.2", + "sebastian/comparator": "^3.0.2", + "sebastian/diff": "^3.0.2", + "sebastian/environment": "^4.2.2", + "sebastian/exporter": "^3.1.1", + "sebastian/global-state": "^3.0.0", "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^2.0", + "sebastian/resource-operations": "^2.0.1", + "sebastian/type": "^1.1.3", "sebastian/version": "^2.0.1" }, "require-dev": { @@ -2187,7 +1968,7 @@ "suggest": { "ext-soap": "*", "ext-xdebug": "*", - "phpunit/php-invoker": "^2.0" + "phpunit/php-invoker": "^2.0.0" }, "bin": [ "phpunit" @@ -2195,7 +1976,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "8.1-dev" + "dev-master": "8.4-dev" } }, "autoload": { @@ -2221,7 +2002,7 @@ "testing", "xunit" ], - "time": "2019-05-14T04:57:31+00:00" + "time": "2019-11-06T09:42:23+00:00" }, { "name": "psr/cache", @@ -2320,16 +2101,16 @@ }, { "name": "psr/log", - "version": "1.1.0", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" + "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", - "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801", + "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801", "shasum": "" }, "require": { @@ -2338,7 +2119,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { @@ -2363,7 +2144,7 @@ "psr", "psr-3" ], - "time": "2018-11-20T15:27:04+00:00" + "time": "2019-11-01T11:05:21+00:00" }, { "name": "psr/simple-cache", @@ -2633,16 +2414,16 @@ }, { "name": "sebastian/exporter", - "version": "3.1.0", + "version": "3.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", "shasum": "" }, "require": { @@ -2669,6 +2450,10 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, { "name": "Jeff Welch", "email": "whatthejeff@gmail.com" @@ -2677,17 +2462,13 @@ "name": "Volker Dusch", "email": "github@wallbash.com" }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, { "name": "Adam Harvey", "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" } ], "description": "Provides the functionality to export PHP variables for visualization", @@ -2696,7 +2477,7 @@ "export", "exporter" ], - "time": "2017-04-03T13:19:02+00:00" + "time": "2019-09-14T09:02:43+00:00" }, { "name": "sebastian/global-state", @@ -2939,6 +2720,52 @@ "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "time": "2018-10-04T04:07:39+00:00" }, + { + "name": "sebastian/type", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/3aaaa15fa71d27650d62a948be022fe3b48541a3", + "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3", + "shasum": "" + }, + "require": { + "php": "^7.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "time": "2019-07-02T08:10:15+00:00" + }, { "name": "sebastian/version", "version": "2.0.1", @@ -2984,16 +2811,16 @@ }, { "name": "seld/jsonlint", - "version": "1.7.1", + "version": "1.7.2", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "d15f59a67ff805a44c50ea0516d2341740f81a38" + "reference": "e2e5d290e4d2a4f0eb449f510071392e00e10d19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/d15f59a67ff805a44c50ea0516d2341740f81a38", - "reference": "d15f59a67ff805a44c50ea0516d2341740f81a38", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/e2e5d290e4d2a4f0eb449f510071392e00e10d19", + "reference": "e2e5d290e4d2a4f0eb449f510071392e00e10d19", "shasum": "" }, "require": { @@ -3029,54 +2856,7 @@ "parser", "validator" ], - "time": "2018-01-24T12:46:19+00:00" - }, - { - "name": "slam/php-cs-fixer-extensions", - "version": "v1.19.0", - "source": { - "type": "git", - "url": "https://github.com/Slamdunk/php-cs-fixer-extensions.git", - "reference": "1cc36ca952e49579bfa94964194de7008862b958" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Slamdunk/php-cs-fixer-extensions/zipball/1cc36ca952e49579bfa94964194de7008862b958", - "reference": "1cc36ca952e49579bfa94964194de7008862b958", - "shasum": "" - }, - "require": { - "friendsofphp/php-cs-fixer": "^2.15", - "php": "^7.2" - }, - "require-dev": { - "phpstan/phpstan": "^0.11", - "phpstan/phpstan-phpunit": "^0.11", - "phpunit/phpunit": "^7.5", - "roave/security-advisories": "dev-master", - "slam/php-debug-r": "^1.4", - "slam/phpstan-extensions": "^3.0", - "thecodingmachine/phpstan-strict-rules": "^0.11" - }, - "type": "library", - "autoload": { - "psr-4": { - "SlamCsFixer\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filippo Tessarotto", - "email": "zoeslam@gmail.com", - "role": "Developer" - } - ], - "description": "Slam extension of friendsofphp/php-cs-fixer", - "time": "2019-05-06T08:55:25+00:00" + "time": "2019-10-24T14:27:39+00:00" }, { "name": "slevomat/coding-standard", @@ -3120,16 +2900,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.4.2", + "version": "3.5.2", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8" + "reference": "65b12cdeaaa6cd276d4c3033a95b9b88b12701e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8", - "reference": "b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/65b12cdeaaa6cd276d4c3033a95b9b88b12701e7", + "reference": "65b12cdeaaa6cd276d4c3033a95b9b88b12701e7", "shasum": "" }, "require": { @@ -3167,28 +2947,28 @@ "phpcs", "standards" ], - "time": "2019-04-10T23:49:02+00:00" + "time": "2019-10-28T04:36:32+00:00" }, { "name": "symfony/cache", - "version": "v4.2.8", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "9e64db924324700e19ef4f21c2c279a35ff9bdff" + "reference": "30a51b2401ee15bfc7ea98bd7af0f9d80e26e649" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/9e64db924324700e19ef4f21c2c279a35ff9bdff", - "reference": "9e64db924324700e19ef4f21c2c279a35ff9bdff", + "url": "https://api.github.com/repos/symfony/cache/zipball/30a51b2401ee15bfc7ea98bd7af0f9d80e26e649", + "reference": "30a51b2401ee15bfc7ea98bd7af0f9d80e26e649", "shasum": "" }, "require": { "php": "^7.1.3", "psr/cache": "~1.0", "psr/log": "~1.0", - "psr/simple-cache": "^1.0", - "symfony/contracts": "^1.0", + "symfony/cache-contracts": "^1.1", + "symfony/service-contracts": "^1.1", "symfony/var-exporter": "^4.2" }, "conflict": { @@ -3199,13 +2979,14 @@ "provide": { "psr/cache-implementation": "1.0", "psr/simple-cache-implementation": "1.0", - "symfony/cache-contracts-implementation": "1.0" + "symfony/cache-implementation": "1.0" }, "require-dev": { "cache/integration-tests": "dev-master", "doctrine/cache": "~1.6", "doctrine/dbal": "~2.5", "predis/predis": "~1.1", + "psr/simple-cache": "^1.0", "symfony/config": "~4.2", "symfony/dependency-injection": "~3.4|~4.1", "symfony/var-dumper": "^4.1.1" @@ -3213,7 +2994,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3244,52 +3025,39 @@ "caching", "psr6" ], - "time": "2019-04-16T09:36:45+00:00" + "time": "2019-10-30T12:58:49+00:00" }, { - "name": "symfony/config", - "version": "v4.2.8", + "name": "symfony/cache-contracts", + "version": "v1.1.7", "source": { "type": "git", - "url": "https://github.com/symfony/config.git", - "reference": "0e745ead307d5dcd4e163e94a47ec04b1428943f" + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "af50d14ada9e4e82cfabfabdc502d144f89be0a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/0e745ead307d5dcd4e163e94a47ec04b1428943f", - "reference": "0e745ead307d5dcd4e163e94a47ec04b1428943f", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/af50d14ada9e4e82cfabfabdc502d144f89be0a1", + "reference": "af50d14ada9e4e82cfabfabdc502d144f89be0a1", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/filesystem": "~3.4|~4.0", - "symfony/polyfill-ctype": "~1.8" - }, - "conflict": { - "symfony/finder": "<3.4" - }, - "require-dev": { - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/event-dispatcher": "~3.4|~4.0", - "symfony/finder": "~3.4|~4.0", - "symfony/yaml": "~3.4|~4.0" + "psr/cache": "^1.0" }, "suggest": { - "symfony/yaml": "To use the yaml reference dumper" + "symfony/cache-implementation": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "1.1-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\Config\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Contracts\\Cache\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3297,67 +3065,67 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Config Component", + "description": "Generic abstractions related to caching", "homepage": "https://symfony.com", - "time": "2019-04-01T14:03:25+00:00" + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-10-04T21:43:27+00:00" }, { - "name": "symfony/console", - "version": "v4.2.8", + "name": "symfony/config", + "version": "v4.3.6", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "e2840bb38bddad7a0feaf85931e38fdcffdb2f81" + "url": "https://github.com/symfony/config.git", + "reference": "f4ee0ebb91b16ca1ac105aa39f9284f3cac19a15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/e2840bb38bddad7a0feaf85931e38fdcffdb2f81", - "reference": "e2840bb38bddad7a0feaf85931e38fdcffdb2f81", + "url": "https://api.github.com/repos/symfony/config/zipball/f4ee0ebb91b16ca1ac105aa39f9284f3cac19a15", + "reference": "f4ee0ebb91b16ca1ac105aa39f9284f3cac19a15", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/contracts": "^1.0", - "symfony/polyfill-mbstring": "~1.0" + "symfony/filesystem": "~3.4|~4.0", + "symfony/polyfill-ctype": "~1.8" }, "conflict": { - "symfony/dependency-injection": "<3.4", - "symfony/process": "<3.3" - }, - "provide": { - "psr/log-implementation": "1.0" + "symfony/finder": "<3.4" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0", "symfony/event-dispatcher": "~3.4|~4.0", - "symfony/lock": "~3.4|~4.0", - "symfony/process": "~3.4|~4.0" + "symfony/finder": "~3.4|~4.0", + "symfony/messenger": "~4.1", + "symfony/yaml": "~3.4|~4.0" }, "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" + "symfony/yaml": "To use the yaml reference dumper" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\Console\\": "" + "Symfony\\Component\\Config\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -3377,53 +3145,65 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Console Component", + "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2019-04-08T14:23:48+00:00" + "time": "2019-10-30T13:18:51+00:00" }, { - "name": "symfony/contracts", - "version": "v1.1.0", + "name": "symfony/console", + "version": "v4.3.6", "source": { "type": "git", - "url": "https://github.com/symfony/contracts.git", - "reference": "d3636025e8253c6144358ec0a62773cae588395b" + "url": "https://github.com/symfony/console.git", + "reference": "136c4bd62ea871d00843d1bc0316de4c4a84bb78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/contracts/zipball/d3636025e8253c6144358ec0a62773cae588395b", - "reference": "d3636025e8253c6144358ec0a62773cae588395b", + "url": "https://api.github.com/repos/symfony/console/zipball/136c4bd62ea871d00843d1bc0316de4c4a84bb78", + "reference": "136c4bd62ea871d00843d1bc0316de4c4a84bb78", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/service-contracts": "^1.1" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0" }, "require-dev": { - "psr/cache": "^1.0", - "psr/container": "^1.0", - "symfony/polyfill-intl-idn": "^1.10" + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "^4.3", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0", + "symfony/var-dumper": "^4.3" }, "suggest": { - "psr/cache": "When using the Cache contracts", - "psr/container": "When using the Service contracts", - "symfony/cache-contracts-implementation": "", - "symfony/event-dispatcher-implementation": "", - "symfony/http-client-contracts-implementation": "", - "symfony/service-contracts-implementation": "", - "symfony/translation-contracts-implementation": "" + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "4.3-dev" } }, "autoload": { "psr-4": { - "Symfony\\Contracts\\": "" + "Symfony\\Component\\Console\\": "" }, "exclude-from-classmap": [ - "**/Tests/" + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -3432,38 +3212,30 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "A set of abstractions extracted out of the Symfony components", + "description": "Symfony Console Component", "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "time": "2019-04-27T14:29:50+00:00" + "time": "2019-10-30T12:58:49+00:00" }, { "name": "symfony/debug", - "version": "v4.2.8", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "2d279b6bb1d582dd5740d4d3251ae8c18812ed37" + "reference": "5ea9c3e01989a86ceaa0283f21234b12deadf5e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/2d279b6bb1d582dd5740d4d3251ae8c18812ed37", - "reference": "2d279b6bb1d582dd5740d4d3251ae8c18812ed37", + "url": "https://api.github.com/repos/symfony/debug/zipball/5ea9c3e01989a86ceaa0283f21234b12deadf5e2", + "reference": "5ea9c3e01989a86ceaa0283f21234b12deadf5e2", "shasum": "" }, "require": { @@ -3479,7 +3251,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3506,39 +3278,39 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2019-04-11T11:27:41+00:00" + "time": "2019-10-28T17:07:32+00:00" }, { "name": "symfony/dependency-injection", - "version": "v4.2.8", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "d161c0c8bc77ad6fdb8f5083b9e34c3015d43eb1" + "reference": "fc036941dfafa037a7485714b62593c7eaf68edd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/d161c0c8bc77ad6fdb8f5083b9e34c3015d43eb1", - "reference": "d161c0c8bc77ad6fdb8f5083b9e34c3015d43eb1", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/fc036941dfafa037a7485714b62593c7eaf68edd", + "reference": "fc036941dfafa037a7485714b62593c7eaf68edd", "shasum": "" }, "require": { "php": "^7.1.3", "psr/container": "^1.0", - "symfony/contracts": "^1.0" + "symfony/service-contracts": "^1.1.6" }, "conflict": { - "symfony/config": "<4.2", + "symfony/config": "<4.3", "symfony/finder": "<3.4", "symfony/proxy-manager-bridge": "<3.4", "symfony/yaml": "<3.4" }, "provide": { "psr/container-implementation": "1.0", - "symfony/service-contracts-implementation": "1.0" + "symfony/service-implementation": "1.0" }, "require-dev": { - "symfony/config": "~4.2", + "symfony/config": "^4.3", "symfony/expression-language": "~3.4|~4.0", "symfony/yaml": "~3.4|~4.0" }, @@ -3552,7 +3324,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3579,34 +3351,40 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2019-04-27T11:48:17+00:00" + "time": "2019-10-28T17:07:32+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.2.8", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "fbce53cd74ac509cbe74b6f227622650ab759b02" + "reference": "6229f58993e5a157f6096fc7145c0717d0be8807" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/fbce53cd74ac509cbe74b6f227622650ab759b02", - "reference": "fbce53cd74ac509cbe74b6f227622650ab759b02", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/6229f58993e5a157f6096fc7145c0717d0be8807", + "reference": "6229f58993e5a157f6096fc7145c0717d0be8807", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/contracts": "^1.0" + "symfony/event-dispatcher-contracts": "^1.1" }, "conflict": { "symfony/dependency-injection": "<3.4" }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "1.1" + }, "require-dev": { "psr/log": "~1.0", "symfony/config": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0", "symfony/expression-language": "~3.4|~4.0", + "symfony/http-foundation": "^3.4|^4.0", + "symfony/service-contracts": "^1.1", "symfony/stopwatch": "~3.4|~4.0" }, "suggest": { @@ -3616,7 +3394,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3643,20 +3421,78 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2019-04-06T13:51:08+00:00" + "time": "2019-10-01T16:40:32+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v1.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c43ab685673fb6c8d84220c77897b1d6cdbe1d18", + "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "suggest": { + "psr/event-dispatcher": "", + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-09-17T09:54:03+00:00" }, { "name": "symfony/filesystem", - "version": "v4.2.8", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "e16b9e471703b2c60b95f14d31c1239f68f11601" + "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/e16b9e471703b2c60b95f14d31c1239f68f11601", - "reference": "e16b9e471703b2c60b95f14d31c1239f68f11601", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/9abbb7ef96a51f4d7e69627bc6f63307994e4263", + "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263", "shasum": "" }, "require": { @@ -3666,7 +3502,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3693,20 +3529,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2019-02-07T11:40:08+00:00" + "time": "2019-08-20T14:07:54+00:00" }, { "name": "symfony/finder", - "version": "v4.2.8", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "e45135658bd6c14b61850bf131c4f09a55133f69" + "reference": "72a068f77e317ae77c0a0495236ad292cfb5ce6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/e45135658bd6c14b61850bf131c4f09a55133f69", - "reference": "e45135658bd6c14b61850bf131c4f09a55133f69", + "url": "https://api.github.com/repos/symfony/finder/zipball/72a068f77e317ae77c0a0495236ad292cfb5ce6f", + "reference": "72a068f77e317ae77c0a0495236ad292cfb5ce6f", "shasum": "" }, "require": { @@ -3715,7 +3551,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3742,24 +3578,25 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2019-04-06T13:51:08+00:00" + "time": "2019-10-30T12:53:54+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.2.8", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "1ea878bd3af18f934dedb8c0de60656a9a31a718" + "reference": "38f63e471cda9d37ac06e76d14c5ea2ec5887051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/1ea878bd3af18f934dedb8c0de60656a9a31a718", - "reference": "1ea878bd3af18f934dedb8c0de60656a9a31a718", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/38f63e471cda9d37ac06e76d14c5ea2ec5887051", + "reference": "38f63e471cda9d37ac06e76d14c5ea2ec5887051", "shasum": "" }, "require": { "php": "^7.1.3", + "symfony/mime": "^4.3", "symfony/polyfill-mbstring": "~1.1" }, "require-dev": { @@ -3769,7 +3606,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3796,34 +3633,35 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2019-05-01T08:36:31+00:00" + "time": "2019-10-30T12:58:49+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.2.8", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "a7713bc522f1a1cdf0b39f809fa4542523fc3114" + "reference": "56acfda9e734e8715b3b0e6859cdb4f5b28757bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/a7713bc522f1a1cdf0b39f809fa4542523fc3114", - "reference": "a7713bc522f1a1cdf0b39f809fa4542523fc3114", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/56acfda9e734e8715b3b0e6859cdb4f5b28757bf", + "reference": "56acfda9e734e8715b3b0e6859cdb4f5b28757bf", "shasum": "" }, "require": { "php": "^7.1.3", "psr/log": "~1.0", - "symfony/contracts": "^1.0.2", "symfony/debug": "~3.4|~4.0", - "symfony/event-dispatcher": "~4.1", + "symfony/event-dispatcher": "^4.3", "symfony/http-foundation": "^4.1.1", - "symfony/polyfill-ctype": "~1.8" + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php73": "^1.9" }, "conflict": { + "symfony/browser-kit": "<4.3", "symfony/config": "<3.4", - "symfony/dependency-injection": "<4.2", + "symfony/dependency-injection": "<4.3", "symfony/translation": "<4.2", "symfony/var-dumper": "<4.1.1", "twig/twig": "<1.34|<2.4,>=2" @@ -3833,11 +3671,11 @@ }, "require-dev": { "psr/cache": "~1.0", - "symfony/browser-kit": "~3.4|~4.0", + "symfony/browser-kit": "^4.3", "symfony/config": "~3.4|~4.0", "symfony/console": "~3.4|~4.0", "symfony/css-selector": "~3.4|~4.0", - "symfony/dependency-injection": "^4.2", + "symfony/dependency-injection": "^4.3", "symfony/dom-crawler": "~3.4|~4.0", "symfony/expression-language": "~3.4|~4.0", "symfony/finder": "~3.4|~4.0", @@ -3846,7 +3684,9 @@ "symfony/stopwatch": "~3.4|~4.0", "symfony/templating": "~3.4|~4.0", "symfony/translation": "~4.2", - "symfony/var-dumper": "^4.1.1" + "symfony/translation-contracts": "^1.1", + "symfony/var-dumper": "^4.1.1", + "twig/twig": "^1.34|^2.4" }, "suggest": { "symfony/browser-kit": "", @@ -3858,7 +3698,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3885,29 +3725,88 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2019-05-01T13:31:08+00:00" + "time": "2019-11-01T10:00:03+00:00" }, { - "name": "symfony/options-resolver", - "version": "v4.2.8", + "name": "symfony/mime", + "version": "v4.3.6", "source": { "type": "git", - "url": "https://github.com/symfony/options-resolver.git", - "reference": "fd4a5f27b7cd085b489247b9890ebca9f3e10044" + "url": "https://github.com/symfony/mime.git", + "reference": "3c0e197529da6e59b217615ba8ee7604df88b551" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/fd4a5f27b7cd085b489247b9890ebca9f3e10044", - "reference": "fd4a5f27b7cd085b489247b9890ebca9f3e10044", + "url": "https://api.github.com/repos/symfony/mime/zipball/3c0e197529da6e59b217615ba8ee7604df88b551", + "reference": "3c0e197529da6e59b217615ba8ee7604df88b551", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.1.3", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10", + "symfony/dependency-injection": "~3.4|^4.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A library to manipulate MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "time": "2019-10-30T12:58:49+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v4.3.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "f46c7fc8e207bd8a2188f54f8738f232533765a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/f46c7fc8e207bd8a2188f54f8738f232533765a4", + "reference": "f46c7fc8e207bd8a2188f54f8738f232533765a4", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" } }, "autoload": { @@ -3939,20 +3838,20 @@ "configuration", "options" ], - "time": "2019-04-10T16:20:36+00:00" + "time": "2019-10-28T20:59:01+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.11.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "82ebae02209c21113908c229e9883c419720738a" + "reference": "550ebaac289296ce228a706d0867afc34687e3f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", - "reference": "82ebae02209c21113908c229e9883c419720738a", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4", "shasum": "" }, "require": { @@ -3964,7 +3863,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -3980,13 +3879,13 @@ "MIT" ], "authors": [ - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - }, { "name": "Gert de Pagter", "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill for ctype functions", @@ -3997,20 +3896,82 @@ "polyfill", "portable" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "6af626ae6fa37d396dc90a399c0ff08e5cfc45b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/6af626ae6fa37d396dc90a399c0ff08e5cfc45b2", + "reference": "6af626ae6fa37d396dc90a399c0ff08e5cfc45b2", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php72": "^1.9" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.11.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", - "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17", + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17", "shasum": "" }, "require": { @@ -4022,7 +3983,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -4056,20 +4017,20 @@ "portable", "shim" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/polyfill-php70", - "version": "v1.11.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "bc4858fb611bda58719124ca079baff854149c89" + "reference": "54b4c428a0054e254223797d2713c31e08610831" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/bc4858fb611bda58719124ca079baff854149c89", - "reference": "bc4858fb611bda58719124ca079baff854149c89", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/54b4c428a0054e254223797d2713c31e08610831", + "reference": "54b4c428a0054e254223797d2713c31e08610831", "shasum": "" }, "require": { @@ -4079,7 +4040,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -4115,20 +4076,20 @@ "portable", "shim" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.11.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c" + "reference": "04ce3335667451138df4307d6a9b61565560199e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/ab50dcf166d5f577978419edd37aa2bb8eabce0c", - "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/04ce3335667451138df4307d6a9b61565560199e", + "reference": "04ce3335667451138df4307d6a9b61565560199e", "shasum": "" }, "require": { @@ -4137,7 +4098,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -4170,37 +4131,40 @@ "portable", "shim" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { - "name": "symfony/process", - "version": "v4.2.8", + "name": "symfony/polyfill-php73", + "version": "v1.12.0", "source": { "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "8cf39fb4ccff793340c258ee7760fd40bfe745fe" + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/8cf39fb4ccff793340c258ee7760fd40bfe745fe", - "reference": "8cf39fb4ccff793340c258ee7760fd40bfe745fe", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/2ceb49eaccb9352bff54d22570276bb75ba4a188", + "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": ">=5.3.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "1.12-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\Process\\": "" + "Symfony\\Polyfill\\Php73\\": "" }, - "exclude-from-classmap": [ - "/Tests/" + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -4209,45 +4173,50 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Process Component", + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", "homepage": "https://symfony.com", - "time": "2019-04-10T16:20:36+00:00" + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" }, { - "name": "symfony/stopwatch", - "version": "v4.2.8", + "name": "symfony/process", + "version": "v4.3.6", "source": { "type": "git", - "url": "https://github.com/symfony/stopwatch.git", - "reference": "b1a5f646d56a3290230dbc8edf2a0d62cda23f67" + "url": "https://github.com/symfony/process.git", + "reference": "3b2e0cb029afbb0395034509291f21191d1a4db0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/b1a5f646d56a3290230dbc8edf2a0d62cda23f67", - "reference": "b1a5f646d56a3290230dbc8edf2a0d62cda23f67", + "url": "https://api.github.com/repos/symfony/process/zipball/3b2e0cb029afbb0395034509291f21191d1a4db0", + "reference": "3b2e0cb029afbb0395034509291f21191d1a4db0", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/contracts": "^1.0" + "php": "^7.1.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\Stopwatch\\": "" + "Symfony\\Component\\Process\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -4267,62 +4236,95 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Stopwatch Component", + "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2019-01-16T20:31:39+00:00" + "time": "2019-10-28T17:07:32+00:00" }, { - "name": "symfony/translation", - "version": "v4.2.8", + "name": "symfony/service-contracts", + "version": "v1.1.7", "source": { "type": "git", - "url": "https://github.com/symfony/translation.git", - "reference": "181a426dd129cb496f12d7e7555f6d0b37a7615b" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "ffcde9615dc5bb4825b9f6aed07716f1f57faae0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/181a426dd129cb496f12d7e7555f6d0b37a7615b", - "reference": "181a426dd129cb496f12d7e7555f6d0b37a7615b", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/ffcde9615dc5bb4825b9f6aed07716f1f57faae0", + "reference": "ffcde9615dc5bb4825b9f6aed07716f1f57faae0", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/contracts": "^1.0.2", - "symfony/polyfill-mbstring": "~1.0" + "psr/container": "^1.0" }, - "conflict": { - "symfony/config": "<3.4", - "symfony/dependency-injection": "<3.4", - "symfony/yaml": "<3.4" + "suggest": { + "symfony/service-implementation": "" }, - "provide": { - "symfony/translation-contracts-implementation": "1.0" + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } }, - "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~3.4|~4.0", - "symfony/console": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/finder": "~2.8|~3.0|~4.0", - "symfony/http-kernel": "~3.4|~4.0", - "symfony/intl": "~3.4|~4.0", - "symfony/var-dumper": "~3.4|~4.0", - "symfony/yaml": "~3.4|~4.0" + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } }, - "suggest": { - "psr/log-implementation": "To use logging capability in translator", - "symfony/config": "", - "symfony/yaml": "" + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-09-17T11:12:18+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v4.3.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "1e4ff456bd625be5032fac9be4294e60442e9b71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/1e4ff456bd625be5032fac9be4294e60442e9b71", + "reference": "1e4ff456bd625be5032fac9be4294e60442e9b71", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/service-contracts": "^1.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\Translation\\": "" + "Symfony\\Component\\Stopwatch\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -4342,22 +4344,22 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Translation Component", + "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2019-05-01T12:55:36+00:00" + "time": "2019-08-07T11:52:19+00:00" }, { "name": "symfony/var-exporter", - "version": "v4.2.8", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "57e00f3e0a3deee65b67cf971455b98afeacca46" + "reference": "d5b4e2d334c1d80e42876c7d489896cfd37562f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/57e00f3e0a3deee65b67cf971455b98afeacca46", - "reference": "57e00f3e0a3deee65b67cf971455b98afeacca46", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/d5b4e2d334c1d80e42876c7d489896cfd37562f2", + "reference": "d5b4e2d334c1d80e42876c7d489896cfd37562f2", "shasum": "" }, "require": { @@ -4369,7 +4371,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -4404,20 +4406,20 @@ "instantiate", "serialize" ], - "time": "2019-04-09T20:09:28+00:00" + "time": "2019-08-22T07:33:08+00:00" }, { "name": "symfony/yaml", - "version": "v4.2.8", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "6712daf03ee25b53abb14e7e8e0ede1a770efdb1" + "reference": "324cf4b19c345465fad14f3602050519e09e361d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/6712daf03ee25b53abb14e7e8e0ede1a770efdb1", - "reference": "6712daf03ee25b53abb14e7e8e0ede1a770efdb1", + "url": "https://api.github.com/repos/symfony/yaml/zipball/324cf4b19c345465fad14f3602050519e09e361d", + "reference": "324cf4b19c345465fad14f3602050519e09e361d", "shasum": "" }, "require": { @@ -4436,7 +4438,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -4463,84 +4465,41 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2019-03-30T15:58:42+00:00" - }, - { - "name": "symplify/better-phpdoc-parser", - "version": "v5.4.16", - "source": { - "type": "git", - "url": "https://github.com/Symplify/BetterPhpDocParser.git", - "reference": "a730f69c4b19c741f13b4d05116da7bb64e3db26" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Symplify/BetterPhpDocParser/zipball/a730f69c4b19c741f13b4d05116da7bb64e3db26", - "reference": "a730f69c4b19c741f13b4d05116da7bb64e3db26", - "shasum": "" - }, - "require": { - "nette/utils": "^2.5", - "php": "^7.1", - "phpstan/phpdoc-parser": "^0.3.1", - "symplify/package-builder": "^5.4.16" - }, - "require-dev": { - "phpunit/phpunit": "^7.5|^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.5-dev" - } - }, - "autoload": { - "psr-4": { - "Symplify\\BetterPhpDocParser\\": "src", - "Symplify\\BetterPhpDocParser\\Attributes\\": "packages/Attributes/src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slim wrapper around phpstan/phpdoc-parser with format preserving printer", - "time": "2019-03-05T23:15:04+00:00" + "time": "2019-10-30T12:58:49+00:00" }, { "name": "symplify/coding-standard", - "version": "v5.4.16", + "version": "v6.1.0", "source": { "type": "git", "url": "https://github.com/Symplify/CodingStandard.git", - "reference": "72a3b03f21be6c978a90ad567a29bd9261df0dfa" + "reference": "d692701e2c74edd8c0cc7c35f47b8421b8b4885c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/CodingStandard/zipball/72a3b03f21be6c978a90ad567a29bd9261df0dfa", - "reference": "72a3b03f21be6c978a90ad567a29bd9261df0dfa", + "url": "https://api.github.com/repos/Symplify/CodingStandard/zipball/d692701e2c74edd8c0cc7c35f47b8421b8b4885c", + "reference": "d692701e2c74edd8c0cc7c35f47b8421b8b4885c", "shasum": "" }, "require": { - "friendsofphp/php-cs-fixer": "^2.14", + "friendsofphp/php-cs-fixer": "^2.15", "nette/finder": "^2.4", - "nette/utils": "^2.5", + "nette/utils": "^2.5|^3.0", "php": "^7.1", - "slam/php-cs-fixer-extensions": "^1.17", + "phpstan/phpdoc-parser": "^0.3.4", "squizlabs/php_codesniffer": "^3.4", - "symplify/better-phpdoc-parser": "^5.4.16", - "symplify/package-builder": "^5.4.16" + "symplify/package-builder": "^6.1" }, "require-dev": { - "nette/application": "^2.4", + "nette/application": "^3.0", "phpunit/phpunit": "^7.5|^8.0", - "symplify/easy-coding-standard-tester": "^5.4.16", - "symplify/package-builder": "^5.4.16" + "symplify/easy-coding-standard-tester": "^6.1", + "symplify/package-builder": "^6.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.5-dev" + "dev-master": "6.1-dev" } }, "autoload": { @@ -4554,45 +4513,46 @@ "MIT" ], "description": "Set of Symplify rules for PHP_CodeSniffer and PHP CS Fixer.", - "time": "2019-03-05T23:15:04+00:00" + "time": "2019-09-18T08:01:34+00:00" }, { "name": "symplify/easy-coding-standard", - "version": "v5.4.16", + "version": "v6.1.0", "source": { "type": "git", "url": "https://github.com/Symplify/EasyCodingStandard.git", - "reference": "66ed360e0b81881336c7339989dce3b0c14509e9" + "reference": "94b8cf03af132d007d8a33c8dad5655cff6a74e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/EasyCodingStandard/zipball/66ed360e0b81881336c7339989dce3b0c14509e9", - "reference": "66ed360e0b81881336c7339989dce3b0c14509e9", + "url": "https://api.github.com/repos/Symplify/EasyCodingStandard/zipball/94b8cf03af132d007d8a33c8dad5655cff6a74e8", + "reference": "94b8cf03af132d007d8a33c8dad5655cff6a74e8", "shasum": "" }, "require": { "composer/xdebug-handler": "^1.3", - "friendsofphp/php-cs-fixer": "^2.14", + "friendsofphp/php-cs-fixer": "^2.15", "jean85/pretty-package-versions": "^1.2", "nette/robot-loader": "^3.1.0", - "nette/utils": "^2.5", + "nette/utils": "^2.5|^3.0", "ocramius/package-versions": "^1.3", "php": "^7.1", + "psr/simple-cache": "^1.0", "slevomat/coding-standard": "^5.0.1", "squizlabs/php_codesniffer": "^3.4", - "symfony/cache": "^3.4|^4.1", - "symfony/config": "^3.4|^4.1", - "symfony/console": "^3.4|^4.1", - "symfony/dependency-injection": "^3.4|^4.1", - "symfony/finder": "^3.4|^4.1", - "symfony/http-kernel": "^3.4|^4.1", - "symfony/yaml": "^3.4|^4.1", - "symplify/coding-standard": "^5.4.16", - "symplify/package-builder": "^5.4.16" + "symfony/cache": "^3.4|^4.3", + "symfony/config": "^3.4|^4.3", + "symfony/console": "^3.4|^4.3", + "symfony/dependency-injection": "^3.4.10|^4.2", + "symfony/finder": "^3.4|^4.3", + "symfony/http-kernel": "^3.4|^4.3", + "symfony/yaml": "^3.4|^4.3", + "symplify/coding-standard": "^6.1", + "symplify/package-builder": "^6.1" }, "require-dev": { "phpunit/phpunit": "^7.5|^8.0", - "symplify/easy-coding-standard-tester": "^5.4.16" + "symplify/easy-coding-standard-tester": "^6.1" }, "bin": [ "bin/ecs" @@ -4600,7 +4560,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.5-dev" + "dev-master": "6.1-dev" } }, "autoload": { @@ -4617,34 +4577,33 @@ "MIT" ], "description": "Use Coding Standard with 0-knowledge of PHP-CS-Fixer and PHP_CodeSniffer.", - "time": "2019-03-05T23:15:04+00:00" + "time": "2019-09-14T22:46:23+00:00" }, { "name": "symplify/package-builder", - "version": "v5.4.16", + "version": "v6.1.0", "source": { "type": "git", "url": "https://github.com/Symplify/PackageBuilder.git", - "reference": "20e04ad9cd15a53527807a62c8b244d8a114f779" + "reference": "fbdfe363a27070cfdfbc47d5f59e711ed08bb060" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/PackageBuilder/zipball/20e04ad9cd15a53527807a62c8b244d8a114f779", - "reference": "20e04ad9cd15a53527807a62c8b244d8a114f779", + "url": "https://api.github.com/repos/Symplify/PackageBuilder/zipball/fbdfe363a27070cfdfbc47d5f59e711ed08bb060", + "reference": "fbdfe363a27070cfdfbc47d5f59e711ed08bb060", "shasum": "" }, "require": { - "illuminate/support": "^5.7", "nette/finder": "^2.4", - "nette/utils": "^2.5", + "nette/utils": "^2.5|^3.0", "php": "^7.1", - "symfony/config": "^3.4|^4.1", - "symfony/console": "^3.4|^4.1", - "symfony/debug": "^3.4|^4.1", - "symfony/dependency-injection": "^3.4|^4.1", - "symfony/finder": "^3.4|^4.1", - "symfony/http-kernel": "^3.4|^4.1", - "symfony/yaml": "^3.4|^4.1" + "symfony/config": "^3.4|^4.3", + "symfony/console": "^3.4|^4.3", + "symfony/debug": "^3.4|^4.3", + "symfony/dependency-injection": "^3.4.10|^4.2", + "symfony/finder": "^3.4|^4.3", + "symfony/http-kernel": "^3.4|^4.3", + "symfony/yaml": "^3.4|^4.3" }, "require-dev": { "phpunit/phpunit": "^7.5|^8.0" @@ -4652,7 +4611,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.5-dev" + "dev-master": "6.1-dev" } }, "autoload": { @@ -4665,20 +4624,20 @@ "MIT" ], "description": "Dependency Injection, Console and Kernel toolkit for Symplify packages.", - "time": "2019-03-03T15:32:34+00:00" + "time": "2019-09-17T20:48:03+00:00" }, { "name": "theseer/tokenizer", - "version": "1.1.2", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "1c42705be2b6c1de5904f8afacef5895cab44bf8" + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/1c42705be2b6c1de5904f8afacef5895cab44bf8", - "reference": "1c42705be2b6c1de5904f8afacef5895cab44bf8", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", "shasum": "" }, "require": { @@ -4705,20 +4664,20 @@ } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2019-04-04T09:56:43+00:00" + "time": "2019-06-13T22:48:21+00:00" }, { "name": "webmozart/assert", - "version": "1.4.0", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9" + "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9", - "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9", + "url": "https://api.github.com/repos/webmozart/assert/zipball/88e6d84706d09a236046d686bbea96f07b3a34f4", + "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4", "shasum": "" }, "require": { @@ -4726,8 +4685,7 @@ "symfony/polyfill-ctype": "^1.8" }, "require-dev": { - "phpunit/phpunit": "^4.6", - "sebastian/version": "^1.0.1" + "phpunit/phpunit": "^4.8.36 || ^7.5.13" }, "type": "library", "extra": { @@ -4756,7 +4714,53 @@ "check", "validate" ], - "time": "2018-12-25T11:19:39+00:00" + "time": "2019-08-24T08:43:50+00:00" + }, + { + "name": "webmozart/path-util", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/path-util.git", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/path-util/zipball/d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "webmozart/assert": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\PathUtil\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.", + "time": "2015-12-17T08:42:14+00:00" } ], "aliases": [], diff --git a/ecs.yml b/ecs.yml index 21b30e9b..a6026959 100644 --- a/ecs.yml +++ b/ecs.yml @@ -1,8 +1,8 @@ imports: - - { resource: 'vendor/symplify/easy-coding-standard/config/psr2.yml' } - - { resource: 'vendor/symplify/easy-coding-standard/config/php71.yml' } - - { resource: 'vendor/symplify/easy-coding-standard/config/clean-code.yml' } - - { resource: 'vendor/symplify/easy-coding-standard/config/common.yml' } + - { resource: 'vendor/symplify/easy-coding-standard/config/set/psr2.yaml' } + - { resource: 'vendor/symplify/easy-coding-standard/config/set/php71.yaml' } + - { resource: 'vendor/symplify/easy-coding-standard/config/set/clean-code.yaml' } + - { resource: 'vendor/symplify/easy-coding-standard/config/set/common.yaml' } services: # spacing diff --git a/src/Math/Matrix.php b/src/Math/Matrix.php index db6be42b..fb2d7ac2 100644 --- a/src/Math/Matrix.php +++ b/src/Math/Matrix.php @@ -201,7 +201,7 @@ public function multiplyByScalar($value): self */ public function add(self $other): self { - return $this->_add($other); + return $this->sum($other); } /** @@ -209,7 +209,7 @@ public function add(self $other): self */ public function subtract(self $other): self { - return $this->_add($other, -1); + return $this->sum($other, -1); } public function inverse(): self @@ -297,7 +297,7 @@ public static function dot(array $array1, array $array2): array /** * Element-wise addition or substraction depending on the given sign parameter */ - private function _add(self $other, int $sign = 1): self + private function sum(self $other, int $sign = 1): self { $a1 = $this->toArray(); $a2 = $other->toArray(); diff --git a/src/ModelManager.php b/src/ModelManager.php index 349f8489..e0f5be57 100644 --- a/src/ModelManager.php +++ b/src/ModelManager.php @@ -32,8 +32,8 @@ public function restoreFromFile(string $filepath): Estimator throw new FileException(sprintf('File "%s" cannot be opened.', basename($filepath))); } - $object = unserialize((string) file_get_contents($filepath), [Estimator::class]); - if ($object === false) { + $object = unserialize((string) file_get_contents($filepath)); + if ($object === false || !$object instanceof Estimator) { throw new SerializeException(sprintf('"%s" cannot be unserialized.', basename($filepath))); } diff --git a/tests/Classification/Ensemble/BaggingTest.php b/tests/Classification/Ensemble/BaggingTest.php index 9738ce73..d879fba7 100644 --- a/tests/Classification/Ensemble/BaggingTest.php +++ b/tests/Classification/Ensemble/BaggingTest.php @@ -121,7 +121,9 @@ protected function getClassifier(int $numBaseClassifiers = 50): Classifier protected function getAvailableBaseClassifiers(): array { return [ - DecisionTree::class => ['depth' => 5], + DecisionTree::class => [ + 'depth' => 5, + ], NaiveBayes::class => [], ]; } diff --git a/tests/Classification/Ensemble/RandomForestTest.php b/tests/Classification/Ensemble/RandomForestTest.php index 2f21c5ca..abff9737 100644 --- a/tests/Classification/Ensemble/RandomForestTest.php +++ b/tests/Classification/Ensemble/RandomForestTest.php @@ -61,6 +61,10 @@ protected function getClassifier(int $numBaseClassifiers = 50): Classifier protected function getAvailableBaseClassifiers(): array { - return [DecisionTree::class => ['depth' => 5]]; + return [ + DecisionTree::class => [ + 'depth' => 5, + ], + ]; } } diff --git a/tests/NeuralNetwork/Node/Neuron/SynapseTest.php b/tests/NeuralNetwork/Node/Neuron/SynapseTest.php index 1374eadd..f3c68cd1 100644 --- a/tests/NeuralNetwork/Node/Neuron/SynapseTest.php +++ b/tests/NeuralNetwork/Node/Neuron/SynapseTest.php @@ -41,11 +41,9 @@ public function testSynapseWeightChange(): void } /** - * @param int|float $output - * * @return Neuron|MockObject */ - private function getNodeMock($output = 1) + private function getNodeMock(float $output = 1.) { $node = $this->getMockBuilder(Neuron::class)->getMock(); $node->method('getOutput')->willReturn($output); diff --git a/tests/NeuralNetwork/Node/NeuronTest.php b/tests/NeuralNetwork/Node/NeuronTest.php index 448c885b..376d78bb 100644 --- a/tests/NeuralNetwork/Node/NeuronTest.php +++ b/tests/NeuralNetwork/Node/NeuronTest.php @@ -56,7 +56,7 @@ public function testNeuronRefresh(): void /** * @return Synapse|MockObject */ - private function getSynapseMock(int $output = 2) + private function getSynapseMock(float $output = 2.) { $synapse = $this->getMockBuilder(Synapse::class)->disableOriginalConstructor()->getMock(); $synapse->method('getOutput')->willReturn($output); From 3b34550c9c3a465491392e562f696cb88b0673a5 Mon Sep 17 00:00:00 2001 From: Andrew Feeney Date: Thu, 14 Nov 2019 18:03:51 +1100 Subject: [PATCH 322/328] Make grammar a little more natural (#411) * Make grammar a little more natural * Now with added "no duplicate it" --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 3457f686..56d2359b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -44,7 +44,7 @@ To find out how to use PHP-ML follow [Documentation](http://php-ml.readthedocs.o ## Installation -Currently this library is in the process of developing, but You can install it with Composer: +This library is still in beta. However, it can be installed with Composer: ``` composer require php-ai/php-ml From 4075fa01b721bb49a18c9fffc63a000698d7ec89 Mon Sep 17 00:00:00 2001 From: Andrey Bolonin Date: Mon, 16 Dec 2019 09:13:38 +0200 Subject: [PATCH 323/328] Add php 7.4 for travis build (#393) * add php - os: linux * Update .travis.yml --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 92c643b3..30ee8232 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,9 @@ matrix: - os: linux php: '7.3' + - os: linux + php: '7.4' + cache: directories: - $HOME/.composer/cache From deefbb36f2df7d1b35da642a7d47bbda226b2ab4 Mon Sep 17 00:00:00 2001 From: Andrey Bolonin Date: Mon, 27 Jan 2020 08:25:02 +0200 Subject: [PATCH 324/328] Update phpstan to 0.12 (#419) * upd phpstan to 0.12 * add phpstan/phpstan to deps --- composer.json | 6 +- composer.lock | 919 ++++++++++++++++++++++++++++++-------------------- 2 files changed, 552 insertions(+), 373 deletions(-) diff --git a/composer.json b/composer.json index 19616b03..5d8b5114 100644 --- a/composer.json +++ b/composer.json @@ -24,9 +24,9 @@ }, "require-dev": { "phpbench/phpbench": "^0.16.0", - "phpstan/phpstan-phpunit": "^0.11", - "phpstan/phpstan-shim": "^0.11", - "phpstan/phpstan-strict-rules": "^0.11", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", "phpunit/phpunit": "^8.0", "symplify/easy-coding-standard": "^6.0" }, diff --git a/composer.lock b/composer.lock index 23641d3c..fb047316 100644 --- a/composer.lock +++ b/composer.lock @@ -4,21 +4,21 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8c178ab74a43d9b06449af4fcb823df1", + "content-hash": "6128d778392e0d3fbcf784537944df3a", "packages": [], "packages-dev": [ { "name": "beberlei/assert", - "version": "v3.2.6", + "version": "v3.2.7", "source": { "type": "git", "url": "https://github.com/beberlei/assert.git", - "reference": "99508be011753690fe108ded450f5caaae180cfa" + "reference": "d63a6943fc4fd1a2aedb65994e3548715105abcf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/beberlei/assert/zipball/99508be011753690fe108ded450f5caaae180cfa", - "reference": "99508be011753690fe108ded450f5caaae180cfa", + "url": "https://api.github.com/repos/beberlei/assert/zipball/d63a6943fc4fd1a2aedb65994e3548715105abcf", + "reference": "d63a6943fc4fd1a2aedb65994e3548715105abcf", "shasum": "" }, "require": { @@ -67,7 +67,7 @@ "assertion", "validation" ], - "time": "2019-10-10T10:33:57+00:00" + "time": "2019-12-19T17:51:41+00:00" }, { "name": "composer/semver", @@ -245,16 +245,16 @@ }, { "name": "doctrine/instantiator", - "version": "1.2.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "a2c590166b2133a4633738648b6b064edae0814a" + "reference": "ae466f726242e637cebdd526a7d991b9433bacf1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", - "reference": "a2c590166b2133a4633738648b6b064edae0814a", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/ae466f726242e637cebdd526a7d991b9433bacf1", + "reference": "ae466f726242e637cebdd526a7d991b9433bacf1", "shasum": "" }, "require": { @@ -297,20 +297,20 @@ "constructor", "instantiate" ], - "time": "2019-03-17T17:37:11+00:00" + "time": "2019-10-21T16:45:58+00:00" }, { "name": "doctrine/lexer", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "e17f069ede36f7534b95adec71910ed1b49c74ea" + "reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/e17f069ede36f7534b95adec71910ed1b49c74ea", - "reference": "e17f069ede36f7534b95adec71910ed1b49c74ea", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6", + "reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6", "shasum": "" }, "require": { @@ -324,7 +324,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { @@ -359,20 +359,20 @@ "parser", "php" ], - "time": "2019-07-30T19:33:28+00:00" + "time": "2019-10-30T14:39:59+00:00" }, { "name": "friendsofphp/php-cs-fixer", - "version": "v2.16.0", + "version": "v2.16.1", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "ceaff36bee1ed3f1bbbedca36d2528c0826c336d" + "reference": "c8afb599858876e95e8ebfcd97812d383fa23f02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/ceaff36bee1ed3f1bbbedca36d2528c0826c336d", - "reference": "ceaff36bee1ed3f1bbbedca36d2528c0826c336d", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/c8afb599858876e95e8ebfcd97812d383fa23f02", + "reference": "c8afb599858876e95e8ebfcd97812d383fa23f02", "shasum": "" }, "require": { @@ -383,15 +383,15 @@ "ext-tokenizer": "*", "php": "^5.6 || ^7.0", "php-cs-fixer/diff": "^1.3", - "symfony/console": "^3.4.17 || ^4.1.6", - "symfony/event-dispatcher": "^3.0 || ^4.0", - "symfony/filesystem": "^3.0 || ^4.0", - "symfony/finder": "^3.0 || ^4.0", - "symfony/options-resolver": "^3.0 || ^4.0", + "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0", + "symfony/event-dispatcher": "^3.0 || ^4.0 || ^5.0", + "symfony/filesystem": "^3.0 || ^4.0 || ^5.0", + "symfony/finder": "^3.0 || ^4.0 || ^5.0", + "symfony/options-resolver": "^3.0 || ^4.0 || ^5.0", "symfony/polyfill-php70": "^1.0", "symfony/polyfill-php72": "^1.4", - "symfony/process": "^3.0 || ^4.0", - "symfony/stopwatch": "^3.0 || ^4.0" + "symfony/process": "^3.0 || ^4.0 || ^5.0", + "symfony/stopwatch": "^3.0 || ^4.0 || ^5.0" }, "require-dev": { "johnkary/phpunit-speedtrap": "^1.1 || ^2.0 || ^3.0", @@ -404,8 +404,8 @@ "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.1", "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.1", "phpunitgoodpractices/traits": "^1.8", - "symfony/phpunit-bridge": "^4.3", - "symfony/yaml": "^3.0 || ^4.0" + "symfony/phpunit-bridge": "^4.3 || ^5.0", + "symfony/yaml": "^3.0 || ^4.0 || ^5.0" }, "suggest": { "ext-mbstring": "For handling non-UTF8 characters in cache signature.", @@ -448,7 +448,7 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2019-11-03T13:31:09+00:00" + "time": "2019-11-25T22:10:32+00:00" }, { "name": "jean85/pretty-package-versions", @@ -503,16 +503,16 @@ }, { "name": "lstrojny/functional-php", - "version": "1.10.0", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/lstrojny/functional-php.git", - "reference": "809947093034cb9db1ce69b8b4536f4bbb6fe93e" + "reference": "df0e516eb44cd0579eeaff57023ef41ffa11947f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lstrojny/functional-php/zipball/809947093034cb9db1ce69b8b4536f4bbb6fe93e", - "reference": "809947093034cb9db1ce69b8b4536f4bbb6fe93e", + "url": "https://api.github.com/repos/lstrojny/functional-php/zipball/df0e516eb44cd0579eeaff57023ef41ffa11947f", + "reference": "df0e516eb44cd0579eeaff57023ef41ffa11947f", "shasum": "" }, "require": { @@ -520,7 +520,7 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.14", - "phpunit/phpunit": "~6", + "phpunit/phpunit": "~7", "squizlabs/php_codesniffer": "~3.0" }, "type": "library", @@ -581,7 +581,9 @@ "src/Functional/Memoize.php", "src/Functional/Minimum.php", "src/Functional/None.php", + "src/Functional/Noop.php", "src/Functional/Not.php", + "src/Functional/OmitKeys.php", "src/Functional/PartialAny.php", "src/Functional/PartialLeft.php", "src/Functional/PartialMethod.php", @@ -639,20 +641,20 @@ "keywords": [ "functional" ], - "time": "2019-09-26T09:38:13+00:00" + "time": "2019-12-19T16:01:40+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.9.3", + "version": "1.9.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea" + "reference": "579bb7356d91f9456ccd505f24ca8b667966a0a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea", - "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/579bb7356d91f9456ccd505f24ca8b667966a0a7", + "reference": "579bb7356d91f9456ccd505f24ca8b667966a0a7", "shasum": "" }, "require": { @@ -687,24 +689,24 @@ "object", "object graph" ], - "time": "2019-08-09T12:45:53+00:00" + "time": "2019-12-15T19:12:40+00:00" }, { "name": "nette/finder", - "version": "v2.5.1", + "version": "v2.5.2", "source": { "type": "git", "url": "https://github.com/nette/finder.git", - "reference": "14164e1ddd69e9c5f627ff82a10874b3f5bba5fe" + "reference": "4ad2c298eb8c687dd0e74ae84206a4186eeaed50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/finder/zipball/14164e1ddd69e9c5f627ff82a10874b3f5bba5fe", - "reference": "14164e1ddd69e9c5f627ff82a10874b3f5bba5fe", + "url": "https://api.github.com/repos/nette/finder/zipball/4ad2c298eb8c687dd0e74ae84206a4186eeaed50", + "reference": "4ad2c298eb8c687dd0e74ae84206a4186eeaed50", "shasum": "" }, "require": { - "nette/utils": "^2.4 || ~3.0.0", + "nette/utils": "^2.4 || ^3.0", "php": ">=7.1" }, "conflict": { @@ -712,6 +714,7 @@ }, "require-dev": { "nette/tester": "^2.0", + "phpstan/phpstan": "^0.12", "tracy/tracy": "^2.3" }, "type": "library", @@ -749,30 +752,31 @@ "iterator", "nette" ], - "time": "2019-07-11T18:02:17+00:00" + "time": "2020-01-03T20:35:40+00:00" }, { "name": "nette/robot-loader", - "version": "v3.2.0", + "version": "v3.2.1", "source": { "type": "git", "url": "https://github.com/nette/robot-loader.git", - "reference": "0712a0e39ae7956d6a94c0ab6ad41aa842544b5c" + "reference": "d2a100e1f5cab390c78bc88709abbc91249c3993" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/robot-loader/zipball/0712a0e39ae7956d6a94c0ab6ad41aa842544b5c", - "reference": "0712a0e39ae7956d6a94c0ab6ad41aa842544b5c", + "url": "https://api.github.com/repos/nette/robot-loader/zipball/d2a100e1f5cab390c78bc88709abbc91249c3993", + "reference": "d2a100e1f5cab390c78bc88709abbc91249c3993", "shasum": "" }, "require": { "ext-tokenizer": "*", - "nette/finder": "^2.5", + "nette/finder": "^2.5 || ^3.0", "nette/utils": "^3.0", "php": ">=7.1" }, "require-dev": { "nette/tester": "^2.0", + "phpstan/phpstan": "^0.12", "tracy/tracy": "^2.3" }, "type": "library", @@ -802,7 +806,7 @@ "homepage": "https://nette.org/contributors" } ], - "description": "? Nette RobotLoader: high performance and comfortable autoloader that will search and autoload classes within your application.", + "description": "🍀 Nette RobotLoader: high performance and comfortable autoloader that will search and autoload classes within your application.", "homepage": "https://nette.org", "keywords": [ "autoload", @@ -811,20 +815,20 @@ "nette", "trait" ], - "time": "2019-03-08T21:57:24+00:00" + "time": "2019-12-26T22:32:02+00:00" }, { "name": "nette/utils", - "version": "v3.0.2", + "version": "v3.1.0", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "c133e18c922dcf3ad07673077d92d92cef25a148" + "reference": "d6cd63d77dd9a85c3a2fae707e1255e44c2bc182" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/c133e18c922dcf3ad07673077d92d92cef25a148", - "reference": "c133e18c922dcf3ad07673077d92d92cef25a148", + "url": "https://api.github.com/repos/nette/utils/zipball/d6cd63d77dd9a85c3a2fae707e1255e44c2bc182", + "reference": "d6cd63d77dd9a85c3a2fae707e1255e44c2bc182", "shasum": "" }, "require": { @@ -832,6 +836,7 @@ }, "require-dev": { "nette/tester": "~2.0", + "phpstan/phpstan": "^0.12", "tracy/tracy": "^2.3" }, "suggest": { @@ -846,7 +851,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.1-dev" } }, "autoload": { @@ -888,7 +893,7 @@ "utility", "validation" ], - "time": "2019-10-21T20:40:16+00:00" + "time": "2020-01-03T18:13:31+00:00" }, { "name": "ocramius/package-versions", @@ -1358,16 +1363,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "4.3.2", + "version": "4.3.4", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e" + "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/b83ff7cfcfee7827e1e78b637a5904fe6a96698e", - "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/da3fd972d6bafd628114f7e7e036f45944b62e9c", + "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c", "shasum": "" }, "require": { @@ -1379,6 +1384,7 @@ "require-dev": { "doctrine/instantiator": "^1.0.5", "mockery/mockery": "^1.0", + "phpdocumentor/type-resolver": "0.4.*", "phpunit/phpunit": "^6.4" }, "type": "library", @@ -1405,7 +1411,7 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2019-09-12T14:27:41+00:00" + "time": "2019-12-28T18:55:12+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -1456,33 +1462,33 @@ }, { "name": "phpspec/prophecy", - "version": "1.9.0", + "version": "1.10.1", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203" + "reference": "cbe1df668b3fe136bcc909126a0f529a78d4cbbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/f6811d96d97bdf400077a0cc100ae56aa32b9203", - "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/cbe1df668b3fe136bcc909126a0f529a78d4cbbc", + "reference": "cbe1df668b3fe136bcc909126a0f529a78d4cbbc", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", - "sebastian/comparator": "^1.1|^2.0|^3.0", + "sebastian/comparator": "^1.2.3|^2.0|^3.0", "sebastian/recursion-context": "^1.0|^2.0|^3.0" }, "require-dev": { - "phpspec/phpspec": "^2.5|^3.2", + "phpspec/phpspec": "^2.5 || ^3.2", "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8.x-dev" + "dev-master": "1.10.x-dev" } }, "autoload": { @@ -1515,86 +1521,71 @@ "spy", "stub" ], - "time": "2019-10-03T11:07:50+00:00" + "time": "2019-12-22T21:05:45+00:00" }, { - "name": "phpstan/phpstan-phpunit", - "version": "0.11.2", + "name": "phpstan/phpdoc-parser", + "version": "0.3.5", "source": { "type": "git", - "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "fbf2ad56c3b13189d29655e226c9b1da47c2fad9" + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "8c4ef2aefd9788238897b678a985e1d5c8df6db4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/fbf2ad56c3b13189d29655e226c9b1da47c2fad9", - "reference": "fbf2ad56c3b13189d29655e226c9b1da47c2fad9", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/8c4ef2aefd9788238897b678a985e1d5c8df6db4", + "reference": "8c4ef2aefd9788238897b678a985e1d5c8df6db4", "shasum": "" }, "require": { - "nikic/php-parser": "^4.0", - "php": "~7.1", - "phpstan/phpdoc-parser": "^0.3", - "phpstan/phpstan": "^0.11.4" - }, - "conflict": { - "phpunit/phpunit": "<7.0" + "php": "~7.1" }, "require-dev": { - "consistence/coding-standard": "^3.0.1", - "dealerdirect/phpcodesniffer-composer-installer": "^0.4.4", - "jakub-onderka/php-parallel-lint": "^1.0", + "consistence/coding-standard": "^3.5", + "jakub-onderka/php-parallel-lint": "^0.9.2", "phing/phing": "^2.16.0", - "phpstan/phpstan-strict-rules": "^0.11", - "phpunit/phpunit": "^7.0", - "satooshi/php-coveralls": "^1.0", - "slevomat/coding-standard": "^4.5.2" + "phpstan/phpstan": "^0.10", + "phpunit/phpunit": "^6.3", + "slevomat/coding-standard": "^4.7.2", + "squizlabs/php_codesniffer": "^3.3.2", + "symfony/process": "^3.4 || ^4.0" }, - "type": "phpstan-extension", + "type": "library", "extra": { "branch-alias": { - "dev-master": "0.11-dev" - }, - "phpstan": { - "includes": [ - "extension.neon", - "rules.neon" - ] + "dev-master": "0.3-dev" } }, "autoload": { "psr-4": { - "PHPStan\\": "src/" + "PHPStan\\PhpDocParser\\": [ + "src/" + ] } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "PHPUnit extensions and rules for PHPStan", - "time": "2019-05-17T17:50:16+00:00" + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "time": "2019-06-07T19:13:52+00:00" }, { - "name": "phpstan/phpstan-shim", - "version": "0.11.19", + "name": "phpstan/phpstan", + "version": "0.12.4", "source": { "type": "git", - "url": "https://github.com/phpstan/phpstan-shim.git", - "reference": "e3c06b1d63691dae644ae1e5b540905c8c021801" + "url": "https://github.com/phpstan/phpstan.git", + "reference": "100a25ba8561223f6bf5a5ff4204f951c0ec007c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-shim/zipball/e3c06b1d63691dae644ae1e5b540905c8c021801", - "reference": "e3c06b1d63691dae644ae1e5b540905c8c021801", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/100a25ba8561223f6bf5a5ff4204f951c0ec007c", + "reference": "100a25ba8561223f6bf5a5ff4204f951c0ec007c", "shasum": "" }, "require": { - "php": "~7.1" - }, - "replace": { - "nikic/php-parser": "^4.0.2", - "phpstan/phpdoc-parser": "^0.3.3", - "phpstan/phpstan": "self.version" + "php": "^7.1" }, "bin": [ "phpstan", @@ -1603,7 +1594,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "0.11-dev" + "dev-master": "0.12-dev" } }, "autoload": { @@ -1615,41 +1606,97 @@ "license": [ "MIT" ], - "description": "PHPStan Phar distribution", - "time": "2019-10-22T20:46:16+00:00" + "description": "PHPStan - PHP Static Analysis Tool", + "time": "2020-01-06T06:38:17+00:00" + }, + { + "name": "phpstan/phpstan-phpunit", + "version": "0.12.5", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-phpunit.git", + "reference": "fe49777a04d8dafcfb3958e3441d9c982a1e40ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/fe49777a04d8dafcfb3958e3441d9c982a1e40ae", + "reference": "fe49777a04d8dafcfb3958e3441d9c982a1e40ae", + "shasum": "" + }, + "require": { + "php": "~7.1", + "phpstan/phpstan": "^0.12.3" + }, + "conflict": { + "phpunit/phpunit": "<7.0" + }, + "require-dev": { + "consistence/coding-standard": "^3.5", + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.4", + "ergebnis/composer-normalize": "^2.0.2", + "jakub-onderka/php-parallel-lint": "^1.0", + "phing/phing": "^2.16.0", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^7.0", + "satooshi/php-coveralls": "^1.0", + "slevomat/coding-standard": "^4.7.2" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "0.12-dev" + }, + "phpstan": { + "includes": [ + "extension.neon", + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPUnit extensions and rules for PHPStan", + "time": "2020-01-03T10:04:21+00:00" }, { "name": "phpstan/phpstan-strict-rules", - "version": "0.11.1", + "version": "0.12.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "a203a7afdda073d4ea405a6d9007a5b32de3be61" + "reference": "08f2e51454153e707c6f4fa2c339a59811e83200" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/a203a7afdda073d4ea405a6d9007a5b32de3be61", - "reference": "a203a7afdda073d4ea405a6d9007a5b32de3be61", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/08f2e51454153e707c6f4fa2c339a59811e83200", + "reference": "08f2e51454153e707c6f4fa2c339a59811e83200", "shasum": "" }, "require": { - "nikic/php-parser": "^4.0", "php": "~7.1", - "phpstan/phpstan": "^0.11.4" + "phpstan/phpstan": "^0.12" }, "require-dev": { "consistence/coding-standard": "^3.0.1", "dealerdirect/phpcodesniffer-composer-installer": "^0.4.4", + "ergebnis/composer-normalize": "^2.0.2", "jakub-onderka/php-parallel-lint": "^1.0", "phing/phing": "^2.16.0", - "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-phpunit": "^0.12", "phpunit/phpunit": "^7.0", "slevomat/coding-standard": "^4.5.2" }, "type": "phpstan-extension", "extra": { "branch-alias": { - "dev-master": "0.11-dev" + "dev-master": "0.12-dev" }, "phpstan": { "includes": [ @@ -1667,20 +1714,20 @@ "MIT" ], "description": "Extra strict and opinionated rules for PHPStan", - "time": "2019-05-12T16:59:47+00:00" + "time": "2020-01-01T17:32:25+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "7.0.8", + "version": "7.0.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "aa0d179a13284c7420fc281fc32750e6cc7c9e2f" + "reference": "f1884187926fbb755a9aaf0b3836ad3165b478bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa0d179a13284c7420fc281fc32750e6cc7c9e2f", - "reference": "aa0d179a13284c7420fc281fc32750e6cc7c9e2f", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f1884187926fbb755a9aaf0b3836ad3165b478bf", + "reference": "f1884187926fbb755a9aaf0b3836ad3165b478bf", "shasum": "" }, "require": { @@ -1730,7 +1777,7 @@ "testing", "xunit" ], - "time": "2019-09-17T06:24:36+00:00" + "time": "2019-11-20T13:55:58+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1923,16 +1970,16 @@ }, { "name": "phpunit/phpunit", - "version": "8.4.3", + "version": "8.5.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "67f9e35bffc0dd52d55d565ddbe4230454fd6a4e" + "reference": "7870c78da3c5e4883eaef36ae47853ebb3cb86f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/67f9e35bffc0dd52d55d565ddbe4230454fd6a4e", - "reference": "67f9e35bffc0dd52d55d565ddbe4230454fd6a4e", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/7870c78da3c5e4883eaef36ae47853ebb3cb86f2", + "reference": "7870c78da3c5e4883eaef36ae47853ebb3cb86f2", "shasum": "" }, "require": { @@ -1976,7 +2023,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "8.4-dev" + "dev-master": "8.5-dev" } }, "autoload": { @@ -2002,7 +2049,7 @@ "testing", "xunit" ], - "time": "2019-11-06T09:42:23+00:00" + "time": "2019-12-25T14:49:39+00:00" }, { "name": "psr/cache", @@ -2361,16 +2408,16 @@ }, { "name": "sebastian/environment", - "version": "4.2.2", + "version": "4.2.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404" + "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/f2a2c8e1c97c11ace607a7a667d73d47c19fe404", - "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/464c90d7bdf5ad4e8a6aea15c091fec0603d4368", + "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368", "shasum": "" }, "require": { @@ -2410,7 +2457,7 @@ "environment", "hhvm" ], - "time": "2019-05-05T09:05:15+00:00" + "time": "2019-11-20T08:46:58+00:00" }, { "name": "sebastian/exporter", @@ -2900,16 +2947,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.5.2", + "version": "3.5.3", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "65b12cdeaaa6cd276d4c3033a95b9b88b12701e7" + "reference": "557a1fc7ac702c66b0bbfe16ab3d55839ef724cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/65b12cdeaaa6cd276d4c3033a95b9b88b12701e7", - "reference": "65b12cdeaaa6cd276d4c3033a95b9b88b12701e7", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/557a1fc7ac702c66b0bbfe16ab3d55839ef724cb", + "reference": "557a1fc7ac702c66b0bbfe16ab3d55839ef724cb", "shasum": "" }, "require": { @@ -2947,34 +2994,35 @@ "phpcs", "standards" ], - "time": "2019-10-28T04:36:32+00:00" + "time": "2019-12-04T04:46:47+00:00" }, { "name": "symfony/cache", - "version": "v4.3.6", + "version": "v4.4.2", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "30a51b2401ee15bfc7ea98bd7af0f9d80e26e649" + "reference": "6af64bab165e588300378a87bcd2df3c7c31c144" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/30a51b2401ee15bfc7ea98bd7af0f9d80e26e649", - "reference": "30a51b2401ee15bfc7ea98bd7af0f9d80e26e649", + "url": "https://api.github.com/repos/symfony/cache/zipball/6af64bab165e588300378a87bcd2df3c7c31c144", + "reference": "6af64bab165e588300378a87bcd2df3c7c31c144", "shasum": "" }, "require": { "php": "^7.1.3", "psr/cache": "~1.0", "psr/log": "~1.0", - "symfony/cache-contracts": "^1.1", - "symfony/service-contracts": "^1.1", - "symfony/var-exporter": "^4.2" + "symfony/cache-contracts": "^1.1.7|^2", + "symfony/service-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.2|^5.0" }, "conflict": { "doctrine/dbal": "<2.5", "symfony/dependency-injection": "<3.4", - "symfony/var-dumper": "<3.4" + "symfony/http-kernel": "<4.4", + "symfony/var-dumper": "<4.4" }, "provide": { "psr/cache-implementation": "1.0", @@ -2987,14 +3035,14 @@ "doctrine/dbal": "~2.5", "predis/predis": "~1.1", "psr/simple-cache": "^1.0", - "symfony/config": "~4.2", - "symfony/dependency-injection": "~3.4|~4.1", - "symfony/var-dumper": "^4.1.1" + "symfony/config": "^4.2|^5.0", + "symfony/dependency-injection": "^3.4|^4.1|^5.0", + "symfony/var-dumper": "^4.4|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -3025,24 +3073,24 @@ "caching", "psr6" ], - "time": "2019-10-30T12:58:49+00:00" + "time": "2019-12-16T10:45:21+00:00" }, { "name": "symfony/cache-contracts", - "version": "v1.1.7", + "version": "v2.0.1", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "af50d14ada9e4e82cfabfabdc502d144f89be0a1" + "reference": "23ed8bfc1a4115feca942cb5f1aacdf3dcdf3c16" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/af50d14ada9e4e82cfabfabdc502d144f89be0a1", - "reference": "af50d14ada9e4e82cfabfabdc502d144f89be0a1", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/23ed8bfc1a4115feca942cb5f1aacdf3dcdf3c16", + "reference": "23ed8bfc1a4115feca942cb5f1aacdf3dcdf3c16", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": "^7.2.5", "psr/cache": "^1.0" }, "suggest": { @@ -3051,7 +3099,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -3083,36 +3131,36 @@ "interoperability", "standards" ], - "time": "2019-10-04T21:43:27+00:00" + "time": "2019-11-18T17:27:11+00:00" }, { "name": "symfony/config", - "version": "v4.3.6", + "version": "v4.4.2", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "f4ee0ebb91b16ca1ac105aa39f9284f3cac19a15" + "reference": "6911d432edd5b50822986604fd5a5be3af856d30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/f4ee0ebb91b16ca1ac105aa39f9284f3cac19a15", - "reference": "f4ee0ebb91b16ca1ac105aa39f9284f3cac19a15", + "url": "https://api.github.com/repos/symfony/config/zipball/6911d432edd5b50822986604fd5a5be3af856d30", + "reference": "6911d432edd5b50822986604fd5a5be3af856d30", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/filesystem": "~3.4|~4.0", + "symfony/filesystem": "^3.4|^4.0|^5.0", "symfony/polyfill-ctype": "~1.8" }, "conflict": { "symfony/finder": "<3.4" }, "require-dev": { - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/event-dispatcher": "~3.4|~4.0", - "symfony/finder": "~3.4|~4.0", - "symfony/messenger": "~4.1", - "symfony/yaml": "~3.4|~4.0" + "symfony/event-dispatcher": "^3.4|^4.0|^5.0", + "symfony/finder": "^3.4|^4.0|^5.0", + "symfony/messenger": "^4.1|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/yaml": "^3.4|^4.0|^5.0" }, "suggest": { "symfony/yaml": "To use the yaml reference dumper" @@ -3120,7 +3168,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -3147,31 +3195,32 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2019-10-30T13:18:51+00:00" + "time": "2019-12-18T12:00:29+00:00" }, { "name": "symfony/console", - "version": "v4.3.6", + "version": "v4.4.2", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "136c4bd62ea871d00843d1bc0316de4c4a84bb78" + "reference": "82437719dab1e6bdd28726af14cb345c2ec816d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/136c4bd62ea871d00843d1bc0316de4c4a84bb78", - "reference": "136c4bd62ea871d00843d1bc0316de4c4a84bb78", + "url": "https://api.github.com/repos/symfony/console/zipball/82437719dab1e6bdd28726af14cb345c2ec816d0", + "reference": "82437719dab1e6bdd28726af14cb345c2ec816d0", "shasum": "" }, "require": { "php": "^7.1.3", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php73": "^1.8", - "symfony/service-contracts": "^1.1" + "symfony/service-contracts": "^1.1|^2" }, "conflict": { "symfony/dependency-injection": "<3.4", - "symfony/event-dispatcher": "<4.3", + "symfony/event-dispatcher": "<4.3|>=5", + "symfony/lock": "<4.4", "symfony/process": "<3.3" }, "provide": { @@ -3179,12 +3228,12 @@ }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", "symfony/event-dispatcher": "^4.3", - "symfony/lock": "~3.4|~4.0", - "symfony/process": "~3.4|~4.0", - "symfony/var-dumper": "^4.3" + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^3.4|^4.0|^5.0", + "symfony/var-dumper": "^4.3|^5.0" }, "suggest": { "psr/log": "For using the console logger", @@ -3195,7 +3244,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -3222,20 +3271,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2019-10-30T12:58:49+00:00" + "time": "2019-12-17T10:32:23+00:00" }, { "name": "symfony/debug", - "version": "v4.3.6", + "version": "v4.4.2", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "5ea9c3e01989a86ceaa0283f21234b12deadf5e2" + "reference": "5c4c1db977dc70bb3250e1308d3e8c6341aa38f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/5ea9c3e01989a86ceaa0283f21234b12deadf5e2", - "reference": "5ea9c3e01989a86ceaa0283f21234b12deadf5e2", + "url": "https://api.github.com/repos/symfony/debug/zipball/5c4c1db977dc70bb3250e1308d3e8c6341aa38f5", + "reference": "5c4c1db977dc70bb3250e1308d3e8c6341aa38f5", "shasum": "" }, "require": { @@ -3246,12 +3295,12 @@ "symfony/http-kernel": "<3.4" }, "require-dev": { - "symfony/http-kernel": "~3.4|~4.0" + "symfony/http-kernel": "^3.4|^4.0|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -3278,29 +3327,29 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2019-10-28T17:07:32+00:00" + "time": "2019-12-16T14:46:54+00:00" }, { "name": "symfony/dependency-injection", - "version": "v4.3.6", + "version": "v4.4.2", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "fc036941dfafa037a7485714b62593c7eaf68edd" + "reference": "79b0358207a3571cc3af02a57d0321927921f539" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/fc036941dfafa037a7485714b62593c7eaf68edd", - "reference": "fc036941dfafa037a7485714b62593c7eaf68edd", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/79b0358207a3571cc3af02a57d0321927921f539", + "reference": "79b0358207a3571cc3af02a57d0321927921f539", "shasum": "" }, "require": { "php": "^7.1.3", "psr/container": "^1.0", - "symfony/service-contracts": "^1.1.6" + "symfony/service-contracts": "^1.1.6|^2" }, "conflict": { - "symfony/config": "<4.3", + "symfony/config": "<4.3|>=5.0", "symfony/finder": "<3.4", "symfony/proxy-manager-bridge": "<3.4", "symfony/yaml": "<3.4" @@ -3311,8 +3360,8 @@ }, "require-dev": { "symfony/config": "^4.3", - "symfony/expression-language": "~3.4|~4.0", - "symfony/yaml": "~3.4|~4.0" + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/yaml": "^3.4|^4.0|^5.0" }, "suggest": { "symfony/config": "", @@ -3324,7 +3373,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -3351,20 +3400,76 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2019-10-28T17:07:32+00:00" + "time": "2019-12-19T16:00:02+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v4.4.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "6d7d7712a6ff5215ec26215672293b154f1db8c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/6d7d7712a6ff5215ec26215672293b154f1db8c1", + "reference": "6d7d7712a6ff5215ec26215672293b154f1db8c1", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "psr/log": "~1.0", + "symfony/debug": "^4.4", + "symfony/var-dumper": "^4.4|^5.0" + }, + "require-dev": { + "symfony/http-kernel": "^4.4|^5.0", + "symfony/serializer": "^4.4|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony ErrorHandler Component", + "homepage": "https://symfony.com", + "time": "2019-12-16T14:46:54+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.3.6", + "version": "v4.4.2", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "6229f58993e5a157f6096fc7145c0717d0be8807" + "reference": "b3c3068a72623287550fe20b84a2b01dcba2686f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/6229f58993e5a157f6096fc7145c0717d0be8807", - "reference": "6229f58993e5a157f6096fc7145c0717d0be8807", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b3c3068a72623287550fe20b84a2b01dcba2686f", + "reference": "b3c3068a72623287550fe20b84a2b01dcba2686f", "shasum": "" }, "require": { @@ -3380,12 +3485,12 @@ }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/expression-language": "~3.4|~4.0", - "symfony/http-foundation": "^3.4|^4.0", - "symfony/service-contracts": "^1.1", - "symfony/stopwatch": "~3.4|~4.0" + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^3.4|^4.0|^5.0" }, "suggest": { "symfony/dependency-injection": "", @@ -3394,7 +3499,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -3421,7 +3526,7 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2019-10-01T16:40:32+00:00" + "time": "2019-11-28T13:33:56+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -3483,16 +3588,16 @@ }, { "name": "symfony/filesystem", - "version": "v4.3.6", + "version": "v4.4.2", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263" + "reference": "40c2606131d56eff6f193b6e2ceb92414653b591" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/9abbb7ef96a51f4d7e69627bc6f63307994e4263", - "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/40c2606131d56eff6f193b6e2ceb92414653b591", + "reference": "40c2606131d56eff6f193b6e2ceb92414653b591", "shasum": "" }, "require": { @@ -3502,7 +3607,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -3529,20 +3634,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2019-08-20T14:07:54+00:00" + "time": "2019-11-26T23:16:41+00:00" }, { "name": "symfony/finder", - "version": "v4.3.6", + "version": "v4.4.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "72a068f77e317ae77c0a0495236ad292cfb5ce6f" + "reference": "ce8743441da64c41e2a667b8eb66070444ed911e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/72a068f77e317ae77c0a0495236ad292cfb5ce6f", - "reference": "72a068f77e317ae77c0a0495236ad292cfb5ce6f", + "url": "https://api.github.com/repos/symfony/finder/zipball/ce8743441da64c41e2a667b8eb66070444ed911e", + "reference": "ce8743441da64c41e2a667b8eb66070444ed911e", "shasum": "" }, "require": { @@ -3551,7 +3656,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -3578,35 +3683,35 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2019-10-30T12:53:54+00:00" + "time": "2019-11-17T21:56:56+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.3.6", + "version": "v5.0.2", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "38f63e471cda9d37ac06e76d14c5ea2ec5887051" + "reference": "5dd7f6be6e62d86ba6f3154cf40e78936367978b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/38f63e471cda9d37ac06e76d14c5ea2ec5887051", - "reference": "38f63e471cda9d37ac06e76d14c5ea2ec5887051", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/5dd7f6be6e62d86ba6f3154cf40e78936367978b", + "reference": "5dd7f6be6e62d86ba6f3154cf40e78936367978b", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/mime": "^4.3", + "php": "^7.2.5", + "symfony/mime": "^4.4|^5.0", "symfony/polyfill-mbstring": "~1.1" }, "require-dev": { "predis/predis": "~1.0", - "symfony/expression-language": "~3.4|~4.0" + "symfony/expression-language": "^4.4|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -3633,37 +3738,37 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2019-10-30T12:58:49+00:00" + "time": "2019-12-19T16:01:11+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.3.6", + "version": "v4.4.2", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "56acfda9e734e8715b3b0e6859cdb4f5b28757bf" + "reference": "fe310d2e95cd4c356836c8ecb0895a46d97fede2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/56acfda9e734e8715b3b0e6859cdb4f5b28757bf", - "reference": "56acfda9e734e8715b3b0e6859cdb4f5b28757bf", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/fe310d2e95cd4c356836c8ecb0895a46d97fede2", + "reference": "fe310d2e95cd4c356836c8ecb0895a46d97fede2", "shasum": "" }, "require": { "php": "^7.1.3", "psr/log": "~1.0", - "symfony/debug": "~3.4|~4.0", - "symfony/event-dispatcher": "^4.3", - "symfony/http-foundation": "^4.1.1", - "symfony/polyfill-ctype": "~1.8", + "symfony/error-handler": "^4.4", + "symfony/event-dispatcher": "^4.4", + "symfony/http-foundation": "^4.4|^5.0", + "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-php73": "^1.9" }, "conflict": { "symfony/browser-kit": "<4.3", "symfony/config": "<3.4", + "symfony/console": ">=5", "symfony/dependency-injection": "<4.3", "symfony/translation": "<4.2", - "symfony/var-dumper": "<4.1.1", "twig/twig": "<1.34|<2.4,>=2" }, "provide": { @@ -3671,34 +3776,32 @@ }, "require-dev": { "psr/cache": "~1.0", - "symfony/browser-kit": "^4.3", - "symfony/config": "~3.4|~4.0", - "symfony/console": "~3.4|~4.0", - "symfony/css-selector": "~3.4|~4.0", - "symfony/dependency-injection": "^4.3", - "symfony/dom-crawler": "~3.4|~4.0", - "symfony/expression-language": "~3.4|~4.0", - "symfony/finder": "~3.4|~4.0", - "symfony/process": "~3.4|~4.0", - "symfony/routing": "~3.4|~4.0", - "symfony/stopwatch": "~3.4|~4.0", - "symfony/templating": "~3.4|~4.0", - "symfony/translation": "~4.2", - "symfony/translation-contracts": "^1.1", - "symfony/var-dumper": "^4.1.1", - "twig/twig": "^1.34|^2.4" + "symfony/browser-kit": "^4.3|^5.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/console": "^3.4|^4.0", + "symfony/css-selector": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^4.3|^5.0", + "symfony/dom-crawler": "^3.4|^4.0|^5.0", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/finder": "^3.4|^4.0|^5.0", + "symfony/process": "^3.4|^4.0|^5.0", + "symfony/routing": "^3.4|^4.0|^5.0", + "symfony/stopwatch": "^3.4|^4.0|^5.0", + "symfony/templating": "^3.4|^4.0|^5.0", + "symfony/translation": "^4.2|^5.0", + "symfony/translation-contracts": "^1.1|^2", + "twig/twig": "^1.34|^2.4|^3.0" }, "suggest": { "symfony/browser-kit": "", "symfony/config": "", "symfony/console": "", - "symfony/dependency-injection": "", - "symfony/var-dumper": "" + "symfony/dependency-injection": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -3725,35 +3828,38 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2019-11-01T10:00:03+00:00" + "time": "2019-12-19T16:23:40+00:00" }, { "name": "symfony/mime", - "version": "v4.3.6", + "version": "v5.0.2", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "3c0e197529da6e59b217615ba8ee7604df88b551" + "reference": "0e6a4ced216e49d457eddcefb61132173a876d79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/3c0e197529da6e59b217615ba8ee7604df88b551", - "reference": "3c0e197529da6e59b217615ba8ee7604df88b551", + "url": "https://api.github.com/repos/symfony/mime/zipball/0e6a4ced216e49d457eddcefb61132173a876d79", + "reference": "0e6a4ced216e49d457eddcefb61132173a876d79", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": "^7.2.5", "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-mbstring": "^1.0" }, + "conflict": { + "symfony/mailer": "<4.4" + }, "require-dev": { "egulias/email-validator": "^2.1.10", - "symfony/dependency-injection": "~3.4|^4.1" + "symfony/dependency-injection": "^4.4|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -3784,20 +3890,20 @@ "mime", "mime-type" ], - "time": "2019-10-30T12:58:49+00:00" + "time": "2019-11-30T14:12:50+00:00" }, { "name": "symfony/options-resolver", - "version": "v4.3.6", + "version": "v4.4.2", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "f46c7fc8e207bd8a2188f54f8738f232533765a4" + "reference": "2be23e63f33de16b49294ea6581f462932a77e2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/f46c7fc8e207bd8a2188f54f8738f232533765a4", - "reference": "f46c7fc8e207bd8a2188f54f8738f232533765a4", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/2be23e63f33de16b49294ea6581f462932a77e2f", + "reference": "2be23e63f33de16b49294ea6581f462932a77e2f", "shasum": "" }, "require": { @@ -3806,7 +3912,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -3838,20 +3944,20 @@ "configuration", "options" ], - "time": "2019-10-28T20:59:01+00:00" + "time": "2019-10-28T21:57:16+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.12.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "550ebaac289296ce228a706d0867afc34687e3f4" + "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", - "reference": "550ebaac289296ce228a706d0867afc34687e3f4", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", + "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", "shasum": "" }, "require": { @@ -3863,7 +3969,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -3896,20 +4002,20 @@ "polyfill", "portable" ], - "time": "2019-08-06T08:03:45+00:00" + "time": "2019-11-27T13:56:44+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.12.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "6af626ae6fa37d396dc90a399c0ff08e5cfc45b2" + "reference": "6f9c239e61e1b0c9229a28ff89a812dc449c3d46" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/6af626ae6fa37d396dc90a399c0ff08e5cfc45b2", - "reference": "6af626ae6fa37d396dc90a399c0ff08e5cfc45b2", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/6f9c239e61e1b0c9229a28ff89a812dc449c3d46", + "reference": "6f9c239e61e1b0c9229a28ff89a812dc449c3d46", "shasum": "" }, "require": { @@ -3923,7 +4029,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -3958,20 +4064,20 @@ "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "time": "2019-11-27T13:56:44+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.12.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17" + "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17", - "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7b4aab9743c30be783b73de055d24a39cf4b954f", + "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f", "shasum": "" }, "require": { @@ -3983,7 +4089,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -4017,20 +4123,20 @@ "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "time": "2019-11-27T14:18:11+00:00" }, { "name": "symfony/polyfill-php70", - "version": "v1.12.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "54b4c428a0054e254223797d2713c31e08610831" + "reference": "af23c7bb26a73b850840823662dda371484926c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/54b4c428a0054e254223797d2713c31e08610831", - "reference": "54b4c428a0054e254223797d2713c31e08610831", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/af23c7bb26a73b850840823662dda371484926c4", + "reference": "af23c7bb26a73b850840823662dda371484926c4", "shasum": "" }, "require": { @@ -4040,7 +4146,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -4076,20 +4182,20 @@ "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "time": "2019-11-27T13:56:44+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.12.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "04ce3335667451138df4307d6a9b61565560199e" + "reference": "66fea50f6cb37a35eea048d75a7d99a45b586038" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/04ce3335667451138df4307d6a9b61565560199e", - "reference": "04ce3335667451138df4307d6a9b61565560199e", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/66fea50f6cb37a35eea048d75a7d99a45b586038", + "reference": "66fea50f6cb37a35eea048d75a7d99a45b586038", "shasum": "" }, "require": { @@ -4098,7 +4204,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -4131,20 +4237,20 @@ "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "time": "2019-11-27T13:56:44+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.12.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188" + "reference": "4b0e2222c55a25b4541305a053013d5647d3a25f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/2ceb49eaccb9352bff54d22570276bb75ba4a188", - "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/4b0e2222c55a25b4541305a053013d5647d3a25f", + "reference": "4b0e2222c55a25b4541305a053013d5647d3a25f", "shasum": "" }, "require": { @@ -4153,7 +4259,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -4189,20 +4295,20 @@ "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "time": "2019-11-27T16:25:15+00:00" }, { "name": "symfony/process", - "version": "v4.3.6", + "version": "v4.4.2", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "3b2e0cb029afbb0395034509291f21191d1a4db0" + "reference": "b84501ad50adb72a94fb460a5b5c91f693e99c9b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/3b2e0cb029afbb0395034509291f21191d1a4db0", - "reference": "3b2e0cb029afbb0395034509291f21191d1a4db0", + "url": "https://api.github.com/repos/symfony/process/zipball/b84501ad50adb72a94fb460a5b5c91f693e99c9b", + "reference": "b84501ad50adb72a94fb460a5b5c91f693e99c9b", "shasum": "" }, "require": { @@ -4211,7 +4317,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -4238,24 +4344,24 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2019-10-28T17:07:32+00:00" + "time": "2019-12-06T10:06:46+00:00" }, { "name": "symfony/service-contracts", - "version": "v1.1.7", + "version": "v2.0.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "ffcde9615dc5bb4825b9f6aed07716f1f57faae0" + "reference": "144c5e51266b281231e947b51223ba14acf1a749" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/ffcde9615dc5bb4825b9f6aed07716f1f57faae0", - "reference": "ffcde9615dc5bb4825b9f6aed07716f1f57faae0", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/144c5e51266b281231e947b51223ba14acf1a749", + "reference": "144c5e51266b281231e947b51223ba14acf1a749", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": "^7.2.5", "psr/container": "^1.0" }, "suggest": { @@ -4264,7 +4370,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -4296,30 +4402,30 @@ "interoperability", "standards" ], - "time": "2019-09-17T11:12:18+00:00" + "time": "2019-11-18T17:27:11+00:00" }, { "name": "symfony/stopwatch", - "version": "v4.3.6", + "version": "v5.0.2", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "1e4ff456bd625be5032fac9be4294e60442e9b71" + "reference": "d410282956706e0b08681a5527447a8e6b6f421e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/1e4ff456bd625be5032fac9be4294e60442e9b71", - "reference": "1e4ff456bd625be5032fac9be4294e60442e9b71", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/d410282956706e0b08681a5527447a8e6b6f421e", + "reference": "d410282956706e0b08681a5527447a8e6b6f421e", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/service-contracts": "^1.0" + "php": "^7.2.5", + "symfony/service-contracts": "^1.0|^2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -4346,32 +4452,107 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2019-08-07T11:52:19+00:00" + "time": "2019-11-18T17:27:11+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v5.0.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "d7bc61d5d335fa9b1b91e14bb16861e8ca50f53a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/d7bc61d5d335fa9b1b91e14bb16861e8ca50f53a", + "reference": "d7bc61d5d335fa9b1b91e14bb16861e8ca50f53a", + "shasum": "" + }, + "require": { + "php": "^7.2.5", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<4.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "twig/twig": "^2.4|^3.0" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony mechanism for exploring and dumping PHP variables", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "time": "2019-12-18T13:50:31+00:00" }, { "name": "symfony/var-exporter", - "version": "v4.3.6", + "version": "v5.0.2", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "d5b4e2d334c1d80e42876c7d489896cfd37562f2" + "reference": "1b9653e68d5b701bf6d9c91bdd3660078c9f4f28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/d5b4e2d334c1d80e42876c7d489896cfd37562f2", - "reference": "d5b4e2d334c1d80e42876c7d489896cfd37562f2", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/1b9653e68d5b701bf6d9c91bdd3660078c9f4f28", + "reference": "1b9653e68d5b701bf6d9c91bdd3660078c9f4f28", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.2.5" }, "require-dev": { - "symfony/var-dumper": "^4.1.1" + "symfony/var-dumper": "^4.4|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -4406,20 +4587,20 @@ "instantiate", "serialize" ], - "time": "2019-08-22T07:33:08+00:00" + "time": "2019-12-01T08:48:26+00:00" }, { "name": "symfony/yaml", - "version": "v4.3.6", + "version": "v4.4.2", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "324cf4b19c345465fad14f3602050519e09e361d" + "reference": "a08832b974dd5fafe3085a66d41fe4c84bb2628c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/324cf4b19c345465fad14f3602050519e09e361d", - "reference": "324cf4b19c345465fad14f3602050519e09e361d", + "url": "https://api.github.com/repos/symfony/yaml/zipball/a08832b974dd5fafe3085a66d41fe4c84bb2628c", + "reference": "a08832b974dd5fafe3085a66d41fe4c84bb2628c", "shasum": "" }, "require": { @@ -4430,7 +4611,7 @@ "symfony/console": "<3.4" }, "require-dev": { - "symfony/console": "~3.4|~4.0" + "symfony/console": "^3.4|^4.0|^5.0" }, "suggest": { "symfony/console": "For validating YAML files using the lint command" @@ -4438,7 +4619,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -4465,7 +4646,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2019-10-30T12:58:49+00:00" + "time": "2019-12-10T10:33:21+00:00" }, { "name": "symplify/coding-standard", @@ -4668,31 +4849,29 @@ }, { "name": "webmozart/assert", - "version": "1.5.0", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4" + "reference": "573381c0a64f155a0d9a23f4b0c797194805b925" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/88e6d84706d09a236046d686bbea96f07b3a34f4", - "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4", + "url": "https://api.github.com/repos/webmozart/assert/zipball/573381c0a64f155a0d9a23f4b0c797194805b925", + "reference": "573381c0a64f155a0d9a23f4b0c797194805b925", "shasum": "" }, "require": { "php": "^5.3.3 || ^7.0", "symfony/polyfill-ctype": "^1.8" }, + "conflict": { + "vimeo/psalm": "<3.6.0" + }, "require-dev": { "phpunit/phpunit": "^4.8.36 || ^7.5.13" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" - } - }, "autoload": { "psr-4": { "Webmozart\\Assert\\": "src/" @@ -4714,7 +4893,7 @@ "check", "validate" ], - "time": "2019-08-24T08:43:50+00:00" + "time": "2019-11-24T13:36:37+00:00" }, { "name": "webmozart/path-util", From 2ee0d373eb0c6d6b40a138b30ba8031352f6aa75 Mon Sep 17 00:00:00 2001 From: Marcin Michalski <57528542+marmichalski@users.noreply.github.com> Date: Tue, 3 Mar 2020 18:52:29 +0100 Subject: [PATCH 325/328] Fix static analysis errors from phpstan upgrade to 0.12 (#426) --- composer.lock | 449 +++++++++--------- phpstan.neon | 8 +- src/Association/Apriori.php | 14 +- src/Classification/Linear/Adaline.php | 2 +- .../Linear/LogisticRegression.php | 4 +- src/Classification/Linear/Perceptron.php | 2 +- src/Clustering/FuzzyCMeans.php | 2 +- src/Clustering/KMeans/Space.php | 2 +- src/Dataset/CsvDataset.php | 4 +- src/DimensionReduction/KernelPCA.php | 6 +- src/FeatureSelection/VarianceThreshold.php | 2 +- src/Helper/Optimizer/GD.php | 2 +- src/Math/Matrix.php | 2 +- src/Math/Statistic/ANOVA.php | 10 +- src/Math/Statistic/StandardDeviation.php | 2 +- src/Metric/ClassificationReport.php | 12 +- src/NeuralNetwork/Node/Neuron.php | 2 +- src/NeuralNetwork/Node/Neuron/Synapse.php | 2 +- src/Pipeline.php | 4 +- src/Regression/DecisionTreeRegressor.php | 2 +- src/Tree/Node/DecisionNode.php | 2 +- tests/DimensionReduction/KernelPCATest.php | 2 +- tests/DimensionReduction/PCATest.php | 4 +- .../Optimizer/ConjugateGradientTest.php | 6 +- tests/Helper/Optimizer/GDTest.php | 4 +- tests/Helper/Optimizer/StochasticGDTest.php | 4 +- tests/Preprocessing/NormalizerTest.php | 2 +- 27 files changed, 278 insertions(+), 279 deletions(-) diff --git a/composer.lock b/composer.lock index fb047316..3a8a25fb 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6128d778392e0d3fbcf784537944df3a", + "content-hash": "914a4eb72418c2cf0d2321cafd474ac2", "packages": [], "packages-dev": [ { @@ -71,24 +71,23 @@ }, { "name": "composer/semver", - "version": "1.5.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e" + "reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/46d9139568ccb8d9e7cdd4539cab7347568a5e2e", - "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e", + "url": "https://api.github.com/repos/composer/semver/zipball/c6bea70230ef4dd483e6bbcab6005f682ed3a8de", + "reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "^4.5 || ^5.0.5", - "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" + "phpunit/phpunit": "^4.5 || ^5.0.5" }, "type": "library", "extra": { @@ -129,20 +128,20 @@ "validation", "versioning" ], - "time": "2019-03-19T17:25:45+00:00" + "time": "2020-01-13T12:06:48+00:00" }, { "name": "composer/xdebug-handler", - "version": "1.4.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "cbe23383749496fe0f373345208b79568e4bc248" + "reference": "1ab9842d69e64fb3a01be6b656501032d1b78cb7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/cbe23383749496fe0f373345208b79568e4bc248", - "reference": "cbe23383749496fe0f373345208b79568e4bc248", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/1ab9842d69e64fb3a01be6b656501032d1b78cb7", + "reference": "1ab9842d69e64fb3a01be6b656501032d1b78cb7", "shasum": "" }, "require": { @@ -173,7 +172,7 @@ "Xdebug", "performance" ], - "time": "2019-11-06T16:40:04+00:00" + "time": "2020-03-01T12:26:26+00:00" }, { "name": "doctrine/annotations", @@ -645,16 +644,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.9.4", + "version": "1.9.5", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "579bb7356d91f9456ccd505f24ca8b667966a0a7" + "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/579bb7356d91f9456ccd505f24ca8b667966a0a7", - "reference": "579bb7356d91f9456ccd505f24ca8b667966a0a7", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/b2c28789e80a97badd14145fda39b545d83ca3ef", + "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef", "shasum": "" }, "require": { @@ -689,7 +688,7 @@ "object", "object graph" ], - "time": "2019-12-15T19:12:40+00:00" + "time": "2020-01-17T21:11:47+00:00" }, { "name": "nette/finder", @@ -756,16 +755,16 @@ }, { "name": "nette/robot-loader", - "version": "v3.2.1", + "version": "v3.2.2", "source": { "type": "git", "url": "https://github.com/nette/robot-loader.git", - "reference": "d2a100e1f5cab390c78bc88709abbc91249c3993" + "reference": "38e8a270567a4ad9fe716b40fcda5a6580afa3c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/robot-loader/zipball/d2a100e1f5cab390c78bc88709abbc91249c3993", - "reference": "d2a100e1f5cab390c78bc88709abbc91249c3993", + "url": "https://api.github.com/repos/nette/robot-loader/zipball/38e8a270567a4ad9fe716b40fcda5a6580afa3c0", + "reference": "38e8a270567a4ad9fe716b40fcda5a6580afa3c0", "shasum": "" }, "require": { @@ -793,8 +792,8 @@ "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause", - "GPL-2.0", - "GPL-3.0" + "GPL-2.0-only", + "GPL-3.0-only" ], "authors": [ { @@ -815,20 +814,20 @@ "nette", "trait" ], - "time": "2019-12-26T22:32:02+00:00" + "time": "2020-02-20T22:17:50+00:00" }, { "name": "nette/utils", - "version": "v3.1.0", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "d6cd63d77dd9a85c3a2fae707e1255e44c2bc182" + "reference": "2c17d16d8887579ae1c0898ff94a3668997fd3eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/d6cd63d77dd9a85c3a2fae707e1255e44c2bc182", - "reference": "d6cd63d77dd9a85c3a2fae707e1255e44c2bc182", + "url": "https://api.github.com/repos/nette/utils/zipball/2c17d16d8887579ae1c0898ff94a3668997fd3eb", + "reference": "2c17d16d8887579ae1c0898ff94a3668997fd3eb", "shasum": "" }, "require": { @@ -862,8 +861,8 @@ "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause", - "GPL-2.0", - "GPL-3.0" + "GPL-2.0-only", + "GPL-3.0-only" ], "authors": [ { @@ -893,7 +892,7 @@ "utility", "validation" ], - "time": "2020-01-03T18:13:31+00:00" + "time": "2020-02-09T14:10:55+00:00" }, { "name": "ocramius/package-versions", @@ -1363,41 +1362,38 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "4.3.4", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c" + "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/da3fd972d6bafd628114f7e7e036f45944b62e9c", - "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", + "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", "shasum": "" }, "require": { - "php": "^7.0", - "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0", - "phpdocumentor/type-resolver": "~0.4 || ^1.0.0", - "webmozart/assert": "^1.0" + "ext-filter": "^7.1", + "php": "^7.2", + "phpdocumentor/reflection-common": "^2.0", + "phpdocumentor/type-resolver": "^1.0", + "webmozart/assert": "^1" }, "require-dev": { - "doctrine/instantiator": "^1.0.5", - "mockery/mockery": "^1.0", - "phpdocumentor/type-resolver": "0.4.*", - "phpunit/phpunit": "^6.4" + "doctrine/instantiator": "^1", + "mockery/mockery": "^1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.x-dev" + "dev-master": "5.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1408,33 +1404,36 @@ { "name": "Mike van Riel", "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2019-12-28T18:55:12+00:00" + "time": "2020-02-22T12:28:44+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.0.1", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9" + "reference": "7462d5f123dfc080dfdf26897032a6513644fc95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", - "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/7462d5f123dfc080dfdf26897032a6513644fc95", + "reference": "7462d5f123dfc080dfdf26897032a6513644fc95", "shasum": "" }, "require": { - "php": "^7.1", + "php": "^7.2", "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "ext-tokenizer": "^7.1", - "mockery/mockery": "~1", - "phpunit/phpunit": "^7.0" + "ext-tokenizer": "^7.2", + "mockery/mockery": "~1" }, "type": "library", "extra": { @@ -1458,28 +1457,28 @@ } ], "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "time": "2019-08-22T18:11:29+00:00" + "time": "2020-02-18T18:59:58+00:00" }, { "name": "phpspec/prophecy", - "version": "1.10.1", + "version": "v1.10.2", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "cbe1df668b3fe136bcc909126a0f529a78d4cbbc" + "reference": "b4400efc9d206e83138e2bb97ed7f5b14b831cd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/cbe1df668b3fe136bcc909126a0f529a78d4cbbc", - "reference": "cbe1df668b3fe136bcc909126a0f529a78d4cbbc", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/b4400efc9d206e83138e2bb97ed7f5b14b831cd9", + "reference": "b4400efc9d206e83138e2bb97ed7f5b14b831cd9", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", - "sebastian/comparator": "^1.2.3|^2.0|^3.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0" + "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" }, "require-dev": { "phpspec/phpspec": "^2.5 || ^3.2", @@ -1521,7 +1520,7 @@ "spy", "stub" ], - "time": "2019-12-22T21:05:45+00:00" + "time": "2020-01-20T15:57:02+00:00" }, { "name": "phpstan/phpdoc-parser", @@ -1572,16 +1571,16 @@ }, { "name": "phpstan/phpstan", - "version": "0.12.4", + "version": "0.12.13", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "100a25ba8561223f6bf5a5ff4204f951c0ec007c" + "reference": "d74fb5ce1ab9f24a7128db90e99dec82440975c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/100a25ba8561223f6bf5a5ff4204f951c0ec007c", - "reference": "100a25ba8561223f6bf5a5ff4204f951c0ec007c", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d74fb5ce1ab9f24a7128db90e99dec82440975c3", + "reference": "d74fb5ce1ab9f24a7128db90e99dec82440975c3", "shasum": "" }, "require": { @@ -1607,25 +1606,25 @@ "MIT" ], "description": "PHPStan - PHP Static Analysis Tool", - "time": "2020-01-06T06:38:17+00:00" + "time": "2020-03-02T13:08:55+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "0.12.5", + "version": "0.12.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "fe49777a04d8dafcfb3958e3441d9c982a1e40ae" + "reference": "26394996368b6d033d012547d3197f4e07e23021" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/fe49777a04d8dafcfb3958e3441d9c982a1e40ae", - "reference": "fe49777a04d8dafcfb3958e3441d9c982a1e40ae", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/26394996368b6d033d012547d3197f4e07e23021", + "reference": "26394996368b6d033d012547d3197f4e07e23021", "shasum": "" }, "require": { "php": "~7.1", - "phpstan/phpstan": "^0.12.3" + "phpstan/phpstan": "^0.12.4" }, "conflict": { "phpunit/phpunit": "<7.0" @@ -1663,25 +1662,25 @@ "MIT" ], "description": "PHPUnit extensions and rules for PHPStan", - "time": "2020-01-03T10:04:21+00:00" + "time": "2020-01-10T12:07:21+00:00" }, { "name": "phpstan/phpstan-strict-rules", - "version": "0.12.1", + "version": "0.12.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "08f2e51454153e707c6f4fa2c339a59811e83200" + "reference": "a670a59aff7cf96f75d21b974860ada10e25b2ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/08f2e51454153e707c6f4fa2c339a59811e83200", - "reference": "08f2e51454153e707c6f4fa2c339a59811e83200", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/a670a59aff7cf96f75d21b974860ada10e25b2ee", + "reference": "a670a59aff7cf96f75d21b974860ada10e25b2ee", "shasum": "" }, "require": { "php": "~7.1", - "phpstan/phpstan": "^0.12" + "phpstan/phpstan": "^0.12.6" }, "require-dev": { "consistence/coding-standard": "^3.0.1", @@ -1714,7 +1713,7 @@ "MIT" ], "description": "Extra strict and opinionated rules for PHPStan", - "time": "2020-01-01T17:32:25+00:00" + "time": "2020-01-20T13:08:52+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1970,16 +1969,16 @@ }, { "name": "phpunit/phpunit", - "version": "8.5.1", + "version": "8.5.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "7870c78da3c5e4883eaef36ae47853ebb3cb86f2" + "reference": "018b6ac3c8ab20916db85fa91bf6465acb64d1e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/7870c78da3c5e4883eaef36ae47853ebb3cb86f2", - "reference": "7870c78da3c5e4883eaef36ae47853ebb3cb86f2", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/018b6ac3c8ab20916db85fa91bf6465acb64d1e0", + "reference": "018b6ac3c8ab20916db85fa91bf6465acb64d1e0", "shasum": "" }, "require": { @@ -2049,7 +2048,7 @@ "testing", "xunit" ], - "time": "2019-12-25T14:49:39+00:00" + "time": "2020-01-08T08:49:49+00:00" }, { "name": "psr/cache", @@ -2947,16 +2946,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.5.3", + "version": "3.5.4", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "557a1fc7ac702c66b0bbfe16ab3d55839ef724cb" + "reference": "dceec07328401de6211037abbb18bda423677e26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/557a1fc7ac702c66b0bbfe16ab3d55839ef724cb", - "reference": "557a1fc7ac702c66b0bbfe16ab3d55839ef724cb", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/dceec07328401de6211037abbb18bda423677e26", + "reference": "dceec07328401de6211037abbb18bda423677e26", "shasum": "" }, "require": { @@ -2994,20 +2993,20 @@ "phpcs", "standards" ], - "time": "2019-12-04T04:46:47+00:00" + "time": "2020-01-30T22:20:29+00:00" }, { "name": "symfony/cache", - "version": "v4.4.2", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "6af64bab165e588300378a87bcd2df3c7c31c144" + "reference": "28511cbd8c760a19f4b4b70961d2cd957733b3d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/6af64bab165e588300378a87bcd2df3c7c31c144", - "reference": "6af64bab165e588300378a87bcd2df3c7c31c144", + "url": "https://api.github.com/repos/symfony/cache/zipball/28511cbd8c760a19f4b4b70961d2cd957733b3d9", + "reference": "28511cbd8c760a19f4b4b70961d2cd957733b3d9", "shasum": "" }, "require": { @@ -3073,7 +3072,7 @@ "caching", "psr6" ], - "time": "2019-12-16T10:45:21+00:00" + "time": "2020-02-20T16:31:44+00:00" }, { "name": "symfony/cache-contracts", @@ -3135,16 +3134,16 @@ }, { "name": "symfony/config", - "version": "v4.4.2", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "6911d432edd5b50822986604fd5a5be3af856d30" + "reference": "cbfef5ae91ccd3b06621c18d58cd355c68c87ae9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/6911d432edd5b50822986604fd5a5be3af856d30", - "reference": "6911d432edd5b50822986604fd5a5be3af856d30", + "url": "https://api.github.com/repos/symfony/config/zipball/cbfef5ae91ccd3b06621c18d58cd355c68c87ae9", + "reference": "cbfef5ae91ccd3b06621c18d58cd355c68c87ae9", "shasum": "" }, "require": { @@ -3195,20 +3194,20 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2019-12-18T12:00:29+00:00" + "time": "2020-02-04T09:32:40+00:00" }, { "name": "symfony/console", - "version": "v4.4.2", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "82437719dab1e6bdd28726af14cb345c2ec816d0" + "reference": "4fa15ae7be74e53f6ec8c83ed403b97e23b665e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/82437719dab1e6bdd28726af14cb345c2ec816d0", - "reference": "82437719dab1e6bdd28726af14cb345c2ec816d0", + "url": "https://api.github.com/repos/symfony/console/zipball/4fa15ae7be74e53f6ec8c83ed403b97e23b665e9", + "reference": "4fa15ae7be74e53f6ec8c83ed403b97e23b665e9", "shasum": "" }, "require": { @@ -3271,20 +3270,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2019-12-17T10:32:23+00:00" + "time": "2020-02-24T13:10:00+00:00" }, { "name": "symfony/debug", - "version": "v4.4.2", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "5c4c1db977dc70bb3250e1308d3e8c6341aa38f5" + "reference": "a980d87a659648980d89193fd8b7a7ca89d97d21" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/5c4c1db977dc70bb3250e1308d3e8c6341aa38f5", - "reference": "5c4c1db977dc70bb3250e1308d3e8c6341aa38f5", + "url": "https://api.github.com/repos/symfony/debug/zipball/a980d87a659648980d89193fd8b7a7ca89d97d21", + "reference": "a980d87a659648980d89193fd8b7a7ca89d97d21", "shasum": "" }, "require": { @@ -3327,20 +3326,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2019-12-16T14:46:54+00:00" + "time": "2020-02-23T14:41:43+00:00" }, { "name": "symfony/dependency-injection", - "version": "v4.4.2", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "79b0358207a3571cc3af02a57d0321927921f539" + "reference": "ebb2e882e8c9e2eb990aa61ddcd389848466e342" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/79b0358207a3571cc3af02a57d0321927921f539", - "reference": "79b0358207a3571cc3af02a57d0321927921f539", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/ebb2e882e8c9e2eb990aa61ddcd389848466e342", + "reference": "ebb2e882e8c9e2eb990aa61ddcd389848466e342", "shasum": "" }, "require": { @@ -3400,26 +3399,26 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2019-12-19T16:00:02+00:00" + "time": "2020-02-29T09:50:10+00:00" }, { "name": "symfony/error-handler", - "version": "v4.4.2", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "6d7d7712a6ff5215ec26215672293b154f1db8c1" + "reference": "89aa4b9ac6f1f35171b8621b24f60477312085be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/6d7d7712a6ff5215ec26215672293b154f1db8c1", - "reference": "6d7d7712a6ff5215ec26215672293b154f1db8c1", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/89aa4b9ac6f1f35171b8621b24f60477312085be", + "reference": "89aa4b9ac6f1f35171b8621b24f60477312085be", "shasum": "" }, "require": { "php": "^7.1.3", "psr/log": "~1.0", - "symfony/debug": "^4.4", + "symfony/debug": "^4.4.5", "symfony/var-dumper": "^4.4|^5.0" }, "require-dev": { @@ -3456,20 +3455,20 @@ ], "description": "Symfony ErrorHandler Component", "homepage": "https://symfony.com", - "time": "2019-12-16T14:46:54+00:00" + "time": "2020-02-26T11:45:31+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.4.2", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "b3c3068a72623287550fe20b84a2b01dcba2686f" + "reference": "4ad8e149799d3128621a3a1f70e92b9897a8930d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b3c3068a72623287550fe20b84a2b01dcba2686f", - "reference": "b3c3068a72623287550fe20b84a2b01dcba2686f", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4ad8e149799d3128621a3a1f70e92b9897a8930d", + "reference": "4ad8e149799d3128621a3a1f70e92b9897a8930d", "shasum": "" }, "require": { @@ -3526,7 +3525,7 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2019-11-28T13:33:56+00:00" + "time": "2020-02-04T09:32:40+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -3588,16 +3587,16 @@ }, { "name": "symfony/filesystem", - "version": "v4.4.2", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "40c2606131d56eff6f193b6e2ceb92414653b591" + "reference": "266c9540b475f26122b61ef8b23dd9198f5d1cfd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/40c2606131d56eff6f193b6e2ceb92414653b591", - "reference": "40c2606131d56eff6f193b6e2ceb92414653b591", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/266c9540b475f26122b61ef8b23dd9198f5d1cfd", + "reference": "266c9540b475f26122b61ef8b23dd9198f5d1cfd", "shasum": "" }, "require": { @@ -3634,20 +3633,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2019-11-26T23:16:41+00:00" + "time": "2020-01-21T08:20:44+00:00" }, { "name": "symfony/finder", - "version": "v4.4.2", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ce8743441da64c41e2a667b8eb66070444ed911e" + "reference": "ea69c129aed9fdeca781d4b77eb20b62cf5d5357" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ce8743441da64c41e2a667b8eb66070444ed911e", - "reference": "ce8743441da64c41e2a667b8eb66070444ed911e", + "url": "https://api.github.com/repos/symfony/finder/zipball/ea69c129aed9fdeca781d4b77eb20b62cf5d5357", + "reference": "ea69c129aed9fdeca781d4b77eb20b62cf5d5357", "shasum": "" }, "require": { @@ -3683,20 +3682,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2019-11-17T21:56:56+00:00" + "time": "2020-02-14T07:42:58+00:00" }, { "name": "symfony/http-foundation", - "version": "v5.0.2", + "version": "v5.0.5", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "5dd7f6be6e62d86ba6f3154cf40e78936367978b" + "reference": "6f9c2ba72f4295d7ce6cf9f79dbb18036291d335" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/5dd7f6be6e62d86ba6f3154cf40e78936367978b", - "reference": "5dd7f6be6e62d86ba6f3154cf40e78936367978b", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6f9c2ba72f4295d7ce6cf9f79dbb18036291d335", + "reference": "6f9c2ba72f4295d7ce6cf9f79dbb18036291d335", "shasum": "" }, "require": { @@ -3738,20 +3737,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2019-12-19T16:01:11+00:00" + "time": "2020-02-14T07:43:07+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.4.2", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "fe310d2e95cd4c356836c8ecb0895a46d97fede2" + "reference": "8c8734486dada83a6041ab744709bdc1651a8462" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/fe310d2e95cd4c356836c8ecb0895a46d97fede2", - "reference": "fe310d2e95cd4c356836c8ecb0895a46d97fede2", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/8c8734486dada83a6041ab744709bdc1651a8462", + "reference": "8c8734486dada83a6041ab744709bdc1651a8462", "shasum": "" }, "require": { @@ -3828,20 +3827,20 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2019-12-19T16:23:40+00:00" + "time": "2020-02-29T10:31:38+00:00" }, { "name": "symfony/mime", - "version": "v5.0.2", + "version": "v5.0.5", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "0e6a4ced216e49d457eddcefb61132173a876d79" + "reference": "9b3e5b5e58c56bbd76628c952d2b78556d305f3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/0e6a4ced216e49d457eddcefb61132173a876d79", - "reference": "0e6a4ced216e49d457eddcefb61132173a876d79", + "url": "https://api.github.com/repos/symfony/mime/zipball/9b3e5b5e58c56bbd76628c952d2b78556d305f3c", + "reference": "9b3e5b5e58c56bbd76628c952d2b78556d305f3c", "shasum": "" }, "require": { @@ -3890,20 +3889,20 @@ "mime", "mime-type" ], - "time": "2019-11-30T14:12:50+00:00" + "time": "2020-02-04T09:41:09+00:00" }, { "name": "symfony/options-resolver", - "version": "v4.4.2", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "2be23e63f33de16b49294ea6581f462932a77e2f" + "reference": "9a02d6662660fe7bfadad63b5f0b0718d4c8b6b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/2be23e63f33de16b49294ea6581f462932a77e2f", - "reference": "2be23e63f33de16b49294ea6581f462932a77e2f", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/9a02d6662660fe7bfadad63b5f0b0718d4c8b6b0", + "reference": "9a02d6662660fe7bfadad63b5f0b0718d4c8b6b0", "shasum": "" }, "require": { @@ -3944,20 +3943,20 @@ "configuration", "options" ], - "time": "2019-10-28T21:57:16+00:00" + "time": "2020-01-04T13:00:46+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.13.1", + "version": "v1.14.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3" + "reference": "fbdeaec0df06cf3d51c93de80c7eb76e271f5a38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", - "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/fbdeaec0df06cf3d51c93de80c7eb76e271f5a38", + "reference": "fbdeaec0df06cf3d51c93de80c7eb76e271f5a38", "shasum": "" }, "require": { @@ -3969,7 +3968,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.13-dev" + "dev-master": "1.14-dev" } }, "autoload": { @@ -4002,26 +4001,26 @@ "polyfill", "portable" ], - "time": "2019-11-27T13:56:44+00:00" + "time": "2020-01-13T11:15:53+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.13.1", + "version": "v1.14.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "6f9c239e61e1b0c9229a28ff89a812dc449c3d46" + "reference": "6842f1a39cf7d580655688069a03dd7cd83d244a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/6f9c239e61e1b0c9229a28ff89a812dc449c3d46", - "reference": "6f9c239e61e1b0c9229a28ff89a812dc449c3d46", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/6842f1a39cf7d580655688069a03dd7cd83d244a", + "reference": "6842f1a39cf7d580655688069a03dd7cd83d244a", "shasum": "" }, "require": { "php": ">=5.3.3", "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php72": "^1.9" + "symfony/polyfill-php72": "^1.10" }, "suggest": { "ext-intl": "For best performance" @@ -4029,7 +4028,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.13-dev" + "dev-master": "1.14-dev" } }, "autoload": { @@ -4064,20 +4063,20 @@ "portable", "shim" ], - "time": "2019-11-27T13:56:44+00:00" + "time": "2020-01-17T12:01:36+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.13.1", + "version": "v1.14.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f" + "reference": "34094cfa9abe1f0f14f48f490772db7a775559f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7b4aab9743c30be783b73de055d24a39cf4b954f", - "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/34094cfa9abe1f0f14f48f490772db7a775559f2", + "reference": "34094cfa9abe1f0f14f48f490772db7a775559f2", "shasum": "" }, "require": { @@ -4089,7 +4088,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.13-dev" + "dev-master": "1.14-dev" } }, "autoload": { @@ -4123,20 +4122,20 @@ "portable", "shim" ], - "time": "2019-11-27T14:18:11+00:00" + "time": "2020-01-13T11:15:53+00:00" }, { "name": "symfony/polyfill-php70", - "version": "v1.13.1", + "version": "v1.14.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "af23c7bb26a73b850840823662dda371484926c4" + "reference": "419c4940024c30ccc033650373a1fe13890d3255" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/af23c7bb26a73b850840823662dda371484926c4", - "reference": "af23c7bb26a73b850840823662dda371484926c4", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/419c4940024c30ccc033650373a1fe13890d3255", + "reference": "419c4940024c30ccc033650373a1fe13890d3255", "shasum": "" }, "require": { @@ -4146,7 +4145,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.13-dev" + "dev-master": "1.14-dev" } }, "autoload": { @@ -4182,20 +4181,20 @@ "portable", "shim" ], - "time": "2019-11-27T13:56:44+00:00" + "time": "2020-01-13T11:15:53+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.13.1", + "version": "v1.14.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "66fea50f6cb37a35eea048d75a7d99a45b586038" + "reference": "46ecacf4751dd0dc81e4f6bf01dbf9da1dc1dadf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/66fea50f6cb37a35eea048d75a7d99a45b586038", - "reference": "66fea50f6cb37a35eea048d75a7d99a45b586038", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/46ecacf4751dd0dc81e4f6bf01dbf9da1dc1dadf", + "reference": "46ecacf4751dd0dc81e4f6bf01dbf9da1dc1dadf", "shasum": "" }, "require": { @@ -4204,7 +4203,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.13-dev" + "dev-master": "1.14-dev" } }, "autoload": { @@ -4237,20 +4236,20 @@ "portable", "shim" ], - "time": "2019-11-27T13:56:44+00:00" + "time": "2020-01-13T11:15:53+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.13.1", + "version": "v1.14.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "4b0e2222c55a25b4541305a053013d5647d3a25f" + "reference": "5e66a0fa1070bf46bec4bea7962d285108edd675" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/4b0e2222c55a25b4541305a053013d5647d3a25f", - "reference": "4b0e2222c55a25b4541305a053013d5647d3a25f", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/5e66a0fa1070bf46bec4bea7962d285108edd675", + "reference": "5e66a0fa1070bf46bec4bea7962d285108edd675", "shasum": "" }, "require": { @@ -4259,7 +4258,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.13-dev" + "dev-master": "1.14-dev" } }, "autoload": { @@ -4295,20 +4294,20 @@ "portable", "shim" ], - "time": "2019-11-27T16:25:15+00:00" + "time": "2020-01-13T11:15:53+00:00" }, { "name": "symfony/process", - "version": "v4.4.2", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "b84501ad50adb72a94fb460a5b5c91f693e99c9b" + "reference": "bf9166bac906c9e69fb7a11d94875e7ced97bcd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/b84501ad50adb72a94fb460a5b5c91f693e99c9b", - "reference": "b84501ad50adb72a94fb460a5b5c91f693e99c9b", + "url": "https://api.github.com/repos/symfony/process/zipball/bf9166bac906c9e69fb7a11d94875e7ced97bcd7", + "reference": "bf9166bac906c9e69fb7a11d94875e7ced97bcd7", "shasum": "" }, "require": { @@ -4344,7 +4343,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2019-12-06T10:06:46+00:00" + "time": "2020-02-07T20:06:44+00:00" }, { "name": "symfony/service-contracts", @@ -4406,16 +4405,16 @@ }, { "name": "symfony/stopwatch", - "version": "v5.0.2", + "version": "v5.0.5", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "d410282956706e0b08681a5527447a8e6b6f421e" + "reference": "5d9add8034135b9a5f7b101d1e42c797e7f053e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/d410282956706e0b08681a5527447a8e6b6f421e", - "reference": "d410282956706e0b08681a5527447a8e6b6f421e", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5d9add8034135b9a5f7b101d1e42c797e7f053e4", + "reference": "5d9add8034135b9a5f7b101d1e42c797e7f053e4", "shasum": "" }, "require": { @@ -4452,20 +4451,20 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2019-11-18T17:27:11+00:00" + "time": "2020-01-04T14:08:26+00:00" }, { "name": "symfony/var-dumper", - "version": "v5.0.2", + "version": "v5.0.5", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "d7bc61d5d335fa9b1b91e14bb16861e8ca50f53a" + "reference": "3a37aeb1132d1035536d3d6aa9cb06c2ff9355e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/d7bc61d5d335fa9b1b91e14bb16861e8ca50f53a", - "reference": "d7bc61d5d335fa9b1b91e14bb16861e8ca50f53a", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/3a37aeb1132d1035536d3d6aa9cb06c2ff9355e9", + "reference": "3a37aeb1132d1035536d3d6aa9cb06c2ff9355e9", "shasum": "" }, "require": { @@ -4527,20 +4526,20 @@ "debug", "dump" ], - "time": "2019-12-18T13:50:31+00:00" + "time": "2020-02-26T22:30:10+00:00" }, { "name": "symfony/var-exporter", - "version": "v5.0.2", + "version": "v5.0.5", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "1b9653e68d5b701bf6d9c91bdd3660078c9f4f28" + "reference": "30779a25c736b4290449eaedefe4196c1d060378" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/1b9653e68d5b701bf6d9c91bdd3660078c9f4f28", - "reference": "1b9653e68d5b701bf6d9c91bdd3660078c9f4f28", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/30779a25c736b4290449eaedefe4196c1d060378", + "reference": "30779a25c736b4290449eaedefe4196c1d060378", "shasum": "" }, "require": { @@ -4587,20 +4586,20 @@ "instantiate", "serialize" ], - "time": "2019-12-01T08:48:26+00:00" + "time": "2020-02-04T09:47:34+00:00" }, { "name": "symfony/yaml", - "version": "v4.4.2", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "a08832b974dd5fafe3085a66d41fe4c84bb2628c" + "reference": "94d005c176db2080e98825d98e01e8b311a97a88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/a08832b974dd5fafe3085a66d41fe4c84bb2628c", - "reference": "a08832b974dd5fafe3085a66d41fe4c84bb2628c", + "url": "https://api.github.com/repos/symfony/yaml/zipball/94d005c176db2080e98825d98e01e8b311a97a88", + "reference": "94d005c176db2080e98825d98e01e8b311a97a88", "shasum": "" }, "require": { @@ -4646,7 +4645,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2019-12-10T10:33:21+00:00" + "time": "2020-02-03T10:46:43+00:00" }, { "name": "symplify/coding-standard", @@ -4849,16 +4848,16 @@ }, { "name": "webmozart/assert", - "version": "1.6.0", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "573381c0a64f155a0d9a23f4b0c797194805b925" + "reference": "aed98a490f9a8f78468232db345ab9cf606cf598" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/573381c0a64f155a0d9a23f4b0c797194805b925", - "reference": "573381c0a64f155a0d9a23f4b0c797194805b925", + "url": "https://api.github.com/repos/webmozart/assert/zipball/aed98a490f9a8f78468232db345ab9cf606cf598", + "reference": "aed98a490f9a8f78468232db345ab9cf606cf598", "shasum": "" }, "require": { @@ -4893,7 +4892,7 @@ "check", "validate" ], - "time": "2019-11-24T13:36:37+00:00" + "time": "2020-02-14T12:15:55+00:00" }, { "name": "webmozart/path-util", diff --git a/phpstan.neon b/phpstan.neon index c2abfaf9..7af78fae 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -4,12 +4,18 @@ includes: - vendor/phpstan/phpstan-phpunit/rules.neon parameters: + checkGenericClassInNonGenericObjectType: false + checkMissingIterableValueType: false + ignoreErrors: - '#Property Phpml\\Clustering\\KMeans\\Cluster\:\:\$points \(iterable\\&SplObjectStorage\) does not accept SplObjectStorage#' - '#Phpml\\Dataset\\(.*)Dataset::__construct\(\) does not call parent constructor from Phpml\\Dataset\\ArrayDataset#' - '#Variable property access on .+#' - '#Variable method call on .+#' - + - message: '#ReflectionClass#' + paths: + - src/Classification/Ensemble/AdaBoost.php + - src/Classification/Ensemble/Bagging.php # probably known value - '#Method Phpml\\Classification\\DecisionTree::getBestSplit\(\) should return Phpml\\Classification\\DecisionTree\\DecisionTreeLeaf but returns Phpml\\Classification\\DecisionTree\\DecisionTreeLeaf\|null#' - '#Call to an undefined method Phpml\\Helper\\Optimizer\\Optimizer::getCostValues\(\)#' diff --git a/src/Association/Apriori.php b/src/Association/Apriori.php index 201bfbf0..1f736790 100644 --- a/src/Association/Apriori.php +++ b/src/Association/Apriori.php @@ -104,11 +104,11 @@ public function apriori(): array */ protected function predictSample(array $sample): array { - $predicts = array_values(array_filter($this->getRules(), function ($rule) use ($sample) { + $predicts = array_values(array_filter($this->getRules(), function ($rule) use ($sample): bool { return $this->equals($rule[self::ARRAY_KEY_ANTECEDENT], $sample); })); - return array_map(function ($rule) { + return array_map(static function ($rule) { return $rule[self::ARRAY_KEY_CONSEQUENT]; }, $predicts); } @@ -177,7 +177,7 @@ private function antecedents(array $sample): array $cardinality = count($sample); $antecedents = $this->powerSet($sample); - return array_filter($antecedents, function ($antecedent) use ($cardinality) { + return array_filter($antecedents, static function ($antecedent) use ($cardinality): bool { return (count($antecedent) != $cardinality) && ($antecedent != []); }); } @@ -199,7 +199,7 @@ private function items(): array } } - return array_map(function ($entry) { + return array_map(static function ($entry): array { return [$entry]; }, $items); } @@ -213,7 +213,7 @@ private function items(): array */ private function frequent(array $samples): array { - return array_values(array_filter($samples, function ($entry) { + return array_values(array_filter($samples, function ($entry): bool { return $this->support($entry) >= $this->support; })); } @@ -288,7 +288,7 @@ private function support(array $sample): float */ private function frequency(array $sample): int { - return count(array_filter($this->samples, function ($entry) use ($sample) { + return count(array_filter($this->samples, function ($entry) use ($sample): bool { return $this->subset($entry, $sample); })); } @@ -303,7 +303,7 @@ private function frequency(array $sample): int */ private function contains(array $system, array $set): bool { - return (bool) array_filter($system, function ($entry) use ($set) { + return (bool) array_filter($system, function ($entry) use ($set): bool { return $this->equals($entry, $set); }); } diff --git a/src/Classification/Linear/Adaline.php b/src/Classification/Linear/Adaline.php index 797cdc9c..e5bc9d9a 100644 --- a/src/Classification/Linear/Adaline.php +++ b/src/Classification/Linear/Adaline.php @@ -58,7 +58,7 @@ public function __construct( protected function runTraining(array $samples, array $targets): void { // The cost function is the sum of squares - $callback = function ($weights, $sample, $target) { + $callback = function ($weights, $sample, $target): array { $this->weights = $weights; $output = $this->output($sample); diff --git a/src/Classification/Linear/LogisticRegression.php b/src/Classification/Linear/LogisticRegression.php index 889ea981..4014fb07 100644 --- a/src/Classification/Linear/LogisticRegression.php +++ b/src/Classification/Linear/LogisticRegression.php @@ -188,7 +188,7 @@ protected function getCostFunction(): Closure * The gradient of the cost function to be used with gradient descent: * ∇J(x) = -(y - h(x)) = (h(x) - y) */ - return function ($weights, $sample, $y) use ($penalty) { + return function ($weights, $sample, $y) use ($penalty): array { $this->weights = $weights; $hX = $this->output($sample); @@ -220,7 +220,7 @@ protected function getCostFunction(): Closure * The gradient of the cost function: * ∇J(x) = -(h(x) - y) . h(x) . (1 - h(x)) */ - return function ($weights, $sample, $y) use ($penalty) { + return function ($weights, $sample, $y) use ($penalty): array { $this->weights = $weights; $hX = $this->output($sample); diff --git a/src/Classification/Linear/Perceptron.php b/src/Classification/Linear/Perceptron.php index 36cd4d1f..44220a60 100644 --- a/src/Classification/Linear/Perceptron.php +++ b/src/Classification/Linear/Perceptron.php @@ -154,7 +154,7 @@ protected function resetBinary(): void protected function runTraining(array $samples, array $targets): void { // The cost function is the sum of squares - $callback = function ($weights, $sample, $target) { + $callback = function ($weights, $sample, $target): array { $this->weights = $weights; $prediction = $this->outputClass($sample); diff --git a/src/Clustering/FuzzyCMeans.php b/src/Clustering/FuzzyCMeans.php index abc53f17..ce86f5f9 100644 --- a/src/Clustering/FuzzyCMeans.php +++ b/src/Clustering/FuzzyCMeans.php @@ -139,7 +139,7 @@ protected function generateRandomMembership(int $rows, int $cols): void $total += $val; } - $this->membership[] = array_map(function ($val) use ($total) { + $this->membership[] = array_map(static function ($val) use ($total): float { return $val / $total; }, $row); } diff --git a/src/Clustering/KMeans/Space.php b/src/Clustering/KMeans/Space.php index 33463159..e4207cca 100644 --- a/src/Clustering/KMeans/Space.php +++ b/src/Clustering/KMeans/Space.php @@ -88,7 +88,7 @@ public function getBoundaries() $min = $this->newPoint(array_fill(0, $this->dimension, null)); $max = $this->newPoint(array_fill(0, $this->dimension, null)); - /** @var self $point */ + /** @var Point $point */ foreach ($this as $point) { for ($n = 0; $n < $this->dimension; ++$n) { if ($min[$n] === null || $min[$n] > $point[$n]) { diff --git a/src/Dataset/CsvDataset.php b/src/Dataset/CsvDataset.php index cdd387fb..483664d8 100644 --- a/src/Dataset/CsvDataset.php +++ b/src/Dataset/CsvDataset.php @@ -35,8 +35,8 @@ public function __construct(string $filepath, int $features, bool $headingRow = } $samples = $targets = []; - while (($data = fgetcsv($handle, $maxLineLength, $delimiter)) !== false) { - $samples[] = array_slice((array) $data, 0, $features); + while ($data = fgetcsv($handle, $maxLineLength, $delimiter)) { + $samples[] = array_slice($data, 0, $features); $targets[] = $data[$features]; } diff --git a/src/DimensionReduction/KernelPCA.php b/src/DimensionReduction/KernelPCA.php index 41c7340f..beeaba41 100644 --- a/src/DimensionReduction/KernelPCA.php +++ b/src/DimensionReduction/KernelPCA.php @@ -179,13 +179,13 @@ protected function getKernel(): Closure // k(x,y)=exp(-γ.|x-y|) where |..| is Euclidean distance $dist = new Euclidean(); - return function ($x, $y) use ($dist) { + return function ($x, $y) use ($dist): float { return exp(-$this->gamma * $dist->sqDistance($x, $y)); }; case self::KERNEL_SIGMOID: // k(x,y)=tanh(γ.xT.y+c0) where c0=1 - return function ($x, $y) { + return function ($x, $y): float { $res = Matrix::dot($x, $y)[0] + 1.0; return tanh((float) $this->gamma * $res); @@ -195,7 +195,7 @@ protected function getKernel(): Closure // k(x,y)=exp(-γ.|x-y|) where |..| is Manhattan distance $dist = new Manhattan(); - return function ($x, $y) use ($dist) { + return function ($x, $y) use ($dist): float { return exp(-$this->gamma * $dist->distance($x, $y)); }; diff --git a/src/FeatureSelection/VarianceThreshold.php b/src/FeatureSelection/VarianceThreshold.php index 3bbc29da..0c3154e0 100644 --- a/src/FeatureSelection/VarianceThreshold.php +++ b/src/FeatureSelection/VarianceThreshold.php @@ -37,7 +37,7 @@ public function __construct(float $threshold = 0.0) public function fit(array $samples, ?array $targets = null): void { - $this->variances = array_map(function (array $column) { + $this->variances = array_map(static function (array $column): float { return Variance::population($column); }, Matrix::transposeArray($samples)); diff --git a/src/Helper/Optimizer/GD.php b/src/Helper/Optimizer/GD.php index 28320329..40c65c70 100644 --- a/src/Helper/Optimizer/GD.php +++ b/src/Helper/Optimizer/GD.php @@ -38,7 +38,7 @@ public function runOptimization(array $samples, array $targets, Closure $gradien $this->updateWeightsWithUpdates($updates, $totalPenalty); - $this->costValues[] = array_sum($errors) / $this->sampleCount; + $this->costValues[] = array_sum($errors) / (int) $this->sampleCount; if ($this->earlyStop($theta)) { break; diff --git a/src/Math/Matrix.php b/src/Math/Matrix.php index fb2d7ac2..6f07d5d5 100644 --- a/src/Math/Matrix.php +++ b/src/Math/Matrix.php @@ -126,7 +126,7 @@ public function isSquare(): bool public function transpose(): self { if ($this->rows === 1) { - $matrix = array_map(function ($el) { + $matrix = array_map(static function ($el): array { return [$el]; }, $this->matrix[0]); } else { diff --git a/src/Math/Statistic/ANOVA.php b/src/Math/Statistic/ANOVA.php index f89309ef..16291819 100644 --- a/src/Math/Statistic/ANOVA.php +++ b/src/Math/Statistic/ANOVA.php @@ -28,7 +28,7 @@ public static function oneWayF(array $samples): array throw new InvalidArgumentException('The array must have at least 2 elements'); } - $samplesPerClass = array_map(function (array $class): int { + $samplesPerClass = array_map(static function (array $class): int { return count($class); }, $samples); $allSamples = (int) array_sum($samplesPerClass); @@ -41,10 +41,10 @@ public static function oneWayF(array $samples): array $dfbn = $classes - 1; $dfwn = $allSamples - $classes; - $msb = array_map(function ($s) use ($dfbn) { + $msb = array_map(static function ($s) use ($dfbn) { return $s / $dfbn; }, $ssbn); - $msw = array_map(function ($s) use ($dfwn) { + $msw = array_map(static function ($s) use ($dfwn) { if ($dfwn === 0) { return 1; } @@ -76,7 +76,7 @@ private static function sumOfSquaresPerFeature(array $samples): array private static function sumOfFeaturesPerClass(array $samples): array { - return array_map(function (array $class) { + return array_map(static function (array $class): array { $sum = array_fill(0, count($class[0]), 0); foreach ($class as $sample) { foreach ($sample as $index => $feature) { @@ -97,7 +97,7 @@ private static function sumOfSquares(array $sums): array } } - return array_map(function ($sum) { + return array_map(static function ($sum) { return $sum ** 2; }, $squares); } diff --git a/src/Math/Statistic/StandardDeviation.php b/src/Math/Statistic/StandardDeviation.php index 50effab7..48d93021 100644 --- a/src/Math/Statistic/StandardDeviation.php +++ b/src/Math/Statistic/StandardDeviation.php @@ -50,7 +50,7 @@ public static function sumOfSquares(array $numbers): float $mean = Mean::arithmetic($numbers); return array_sum(array_map( - function ($val) use ($mean) { + static function ($val) use ($mean): float { return ($val - $mean) ** 2; }, $numbers diff --git a/src/Metric/ClassificationReport.php b/src/Metric/ClassificationReport.php index 6263a525..0abac769 100644 --- a/src/Metric/ClassificationReport.php +++ b/src/Metric/ClassificationReport.php @@ -148,7 +148,7 @@ private function computeMicroAverage(): void $precision = $this->computePrecision($truePositive, $falsePositive); $recall = $this->computeRecall($truePositive, $falseNegative); - $f1score = $this->computeF1Score((float) $precision, (float) $recall); + $f1score = $this->computeF1Score($precision, $recall); $this->average = compact('precision', 'recall', 'f1score'); } @@ -186,10 +186,7 @@ private function computeWeightedAverage(): void } } - /** - * @return float|string - */ - private function computePrecision(int $truePositive, int $falsePositive) + private function computePrecision(int $truePositive, int $falsePositive): float { $divider = $truePositive + $falsePositive; if ($divider == 0) { @@ -199,10 +196,7 @@ private function computePrecision(int $truePositive, int $falsePositive) return $truePositive / $divider; } - /** - * @return float|string - */ - private function computeRecall(int $truePositive, int $falseNegative) + private function computeRecall(int $truePositive, int $falseNegative): float { $divider = $truePositive + $falseNegative; if ($divider == 0) { diff --git a/src/NeuralNetwork/Node/Neuron.php b/src/NeuralNetwork/Node/Neuron.php index c5376069..6681e66b 100644 --- a/src/NeuralNetwork/Node/Neuron.php +++ b/src/NeuralNetwork/Node/Neuron.php @@ -33,7 +33,7 @@ class Neuron implements Node public function __construct(?ActivationFunction $activationFunction = null) { - $this->activationFunction = $activationFunction ?: new Sigmoid(); + $this->activationFunction = $activationFunction ?? new Sigmoid(); } public function addSynapse(Synapse $synapse): void diff --git a/src/NeuralNetwork/Node/Neuron/Synapse.php b/src/NeuralNetwork/Node/Neuron/Synapse.php index 0a6e0f8c..d749937f 100644 --- a/src/NeuralNetwork/Node/Neuron/Synapse.php +++ b/src/NeuralNetwork/Node/Neuron/Synapse.php @@ -24,7 +24,7 @@ class Synapse public function __construct(Node $node, ?float $weight = null) { $this->node = $node; - $this->weight = $weight ?: $this->generateRandomWeight(); + $this->weight = $weight ?? $this->generateRandomWeight(); } public function getOutput(): float diff --git a/src/Pipeline.php b/src/Pipeline.php index 421abb52..200173bd 100644 --- a/src/Pipeline.php +++ b/src/Pipeline.php @@ -61,12 +61,12 @@ public function train(array $samples, array $targets): void */ public function predict(array $samples) { + $this->transform($samples); + if ($this->estimator === null) { throw new InvalidOperationException('Pipeline without estimator can\'t use predict method'); } - $this->transform($samples); - return $this->estimator->predict($samples); } diff --git a/src/Regression/DecisionTreeRegressor.php b/src/Regression/DecisionTreeRegressor.php index e066009e..afc2805b 100644 --- a/src/Regression/DecisionTreeRegressor.php +++ b/src/Regression/DecisionTreeRegressor.php @@ -121,7 +121,7 @@ protected function terminate(array $targets): BinaryNode protected function splitImpurity(array $groups): float { - $samplesCount = (int) array_sum(array_map(static function (array $group) { + $samplesCount = (int) array_sum(array_map(static function (array $group): int { return count($group[0]); }, $groups)); diff --git a/src/Tree/Node/DecisionNode.php b/src/Tree/Node/DecisionNode.php index f621fed4..311e0e77 100644 --- a/src/Tree/Node/DecisionNode.php +++ b/src/Tree/Node/DecisionNode.php @@ -50,7 +50,7 @@ public function __construct(int $column, $value, array $groups, float $impurity) $this->value = $value; $this->groups = $groups; $this->impurity = $impurity; - $this->samplesCount = (int) array_sum(array_map(function (array $group) { + $this->samplesCount = (int) array_sum(array_map(static function (array $group): int { return count($group[0]); }, $groups)); } diff --git a/tests/DimensionReduction/KernelPCATest.php b/tests/DimensionReduction/KernelPCATest.php index 6e2ec2bb..da4e51b1 100644 --- a/tests/DimensionReduction/KernelPCATest.php +++ b/tests/DimensionReduction/KernelPCATest.php @@ -40,7 +40,7 @@ public function testKernelPCA(): void // during the calculation of eigenValues, we have to compare // absolute value of the values array_map(function ($val1, $val2) use ($epsilon): void { - self::assertEqualsWithDelta(abs($val1), abs($val2), $epsilon); + self::assertEqualsWithDelta(abs($val1[0]), abs($val2[0]), $epsilon); }, $transformed, $reducedData); // Fitted KernelPCA object can also transform an arbitrary sample of the diff --git a/tests/DimensionReduction/PCATest.php b/tests/DimensionReduction/PCATest.php index 5fbbc94b..3dbc5a6b 100644 --- a/tests/DimensionReduction/PCATest.php +++ b/tests/DimensionReduction/PCATest.php @@ -42,7 +42,7 @@ public function testPCA(): void // during the calculation of eigenValues, we have to compare // absolute value of the values array_map(function ($val1, $val2) use ($epsilon): void { - self::assertEqualsWithDelta(abs($val1), abs($val2), $epsilon); + self::assertEqualsWithDelta(abs($val1[0]), abs($val2[0]), $epsilon); }, $transformed, $reducedData); // Test fitted PCA object to transform an arbitrary sample of the @@ -52,7 +52,7 @@ public function testPCA(): void $newRow2 = $pca->transform($row); array_map(function ($val1, $val2) use ($epsilon): void { - self::assertEqualsWithDelta(abs($val1), abs($val2), $epsilon); + self::assertEqualsWithDelta(abs($val1[0][0]), abs($val2[0]), $epsilon); }, $newRow, $newRow2); } } diff --git a/tests/Helper/Optimizer/ConjugateGradientTest.php b/tests/Helper/Optimizer/ConjugateGradientTest.php index 86a2991b..fc85a600 100644 --- a/tests/Helper/Optimizer/ConjugateGradientTest.php +++ b/tests/Helper/Optimizer/ConjugateGradientTest.php @@ -21,7 +21,7 @@ public function testRunOptimization(): void $targets[] = -1 + 2 * $x; } - $callback = function ($theta, $sample, $target) { + $callback = static function ($theta, $sample, $target): array { $y = $theta[0] + $theta[1] * $sample[0]; $cost = (($y - $target) ** 2) / 2; $grad = $y - $target; @@ -47,7 +47,7 @@ public function testRunOptimizationWithCustomInitialTheta(): void $targets[] = -1 + 2 * $x; } - $callback = function ($theta, $sample, $target) { + $callback = static function ($theta, $sample, $target): array { $y = $theta[0] + $theta[1] * $sample[0]; $cost = (($y - $target) ** 2) / 2; $grad = $y - $target; @@ -76,7 +76,7 @@ public function testRunOptimization2Dim(): void $targets[] = -1 + 2 * $x0 - 3 * $x1; } - $callback = function ($theta, $sample, $target) { + $callback = static function ($theta, $sample, $target): array { $y = $theta[0] + $theta[1] * $sample[0] + $theta[2] * $sample[1]; $cost = (($y - $target) ** 2) / 2; $grad = $y - $target; diff --git a/tests/Helper/Optimizer/GDTest.php b/tests/Helper/Optimizer/GDTest.php index 96409889..a6b42776 100644 --- a/tests/Helper/Optimizer/GDTest.php +++ b/tests/Helper/Optimizer/GDTest.php @@ -20,7 +20,7 @@ public function testRunOptimization(): void $targets[] = -1 + 2 * $x; } - $callback = function ($theta, $sample, $target) { + $callback = static function ($theta, $sample, $target): array { $y = $theta[0] + $theta[1] * $sample[0]; $cost = (($y - $target) ** 2) / 2; $grad = $y - $target; @@ -47,7 +47,7 @@ public function testRunOptimization2Dim(): void $targets[] = -1 + 2 * $x0 - 3 * $x1; } - $callback = function ($theta, $sample, $target) { + $callback = static function ($theta, $sample, $target): array { $y = $theta[0] + $theta[1] * $sample[0] + $theta[2] * $sample[1]; $cost = (($y - $target) ** 2) / 2; $grad = $y - $target; diff --git a/tests/Helper/Optimizer/StochasticGDTest.php b/tests/Helper/Optimizer/StochasticGDTest.php index 07927af4..4f99f78a 100644 --- a/tests/Helper/Optimizer/StochasticGDTest.php +++ b/tests/Helper/Optimizer/StochasticGDTest.php @@ -20,7 +20,7 @@ public function testRunOptimization(): void $targets[] = -1 + 2 * $x; } - $callback = function ($theta, $sample, $target) { + $callback = static function ($theta, $sample, $target): array { $y = $theta[0] + $theta[1] * $sample[0]; $cost = (($y - $target) ** 2) / 2; $grad = $y - $target; @@ -47,7 +47,7 @@ public function testRunOptimization2Dim(): void $targets[] = -1 + 2 * $x0 - 3 * $x1; } - $callback = function ($theta, $sample, $target) { + $callback = static function ($theta, $sample, $target): array { $y = $theta[0] + $theta[1] * $sample[0] + $theta[2] * $sample[1]; $cost = (($y - $target) ** 2) / 2; $grad = $y - $target; diff --git a/tests/Preprocessing/NormalizerTest.php b/tests/Preprocessing/NormalizerTest.php index ed6b2c5a..0a8f76cf 100644 --- a/tests/Preprocessing/NormalizerTest.php +++ b/tests/Preprocessing/NormalizerTest.php @@ -126,7 +126,7 @@ public function testStandardNorm(): void foreach ($samples as $sample) { $errors = array_filter( $sample, - function ($element) { + function ($element): bool { return $element < -3 || $element > 3; } ); From df72b129d28c5793b74293e99e45d6aaf66808a6 Mon Sep 17 00:00:00 2001 From: Tima Date: Wed, 4 Mar 2020 11:01:54 +0300 Subject: [PATCH 326/328] Added Russian stopwords (#425) * Added Russian stopwords * Added Russian stopwords --- src/FeatureExtraction/StopWords/Russian.php | 30 +++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/FeatureExtraction/StopWords/Russian.php diff --git a/src/FeatureExtraction/StopWords/Russian.php b/src/FeatureExtraction/StopWords/Russian.php new file mode 100644 index 00000000..d26902da --- /dev/null +++ b/src/FeatureExtraction/StopWords/Russian.php @@ -0,0 +1,30 @@ +stopWords); + } +} From c31d260f2a7359f267d84d5f51fe62c840edd73d Mon Sep 17 00:00:00 2001 From: ctfblackflag <68704838+ctfblackflag@users.noreply.github.com> Date: Thu, 1 Oct 2020 23:48:05 +0530 Subject: [PATCH 327/328] new --- docs/index.md | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/docs/index.md b/docs/index.md index 56d2359b..bff00f2d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -108,18 +108,5 @@ Example scripts are available in a separate repository [php-ai/php-ml-examples]( * [Statistic](math/statistic.md) -## Contribute - -- Guide: [CONTRIBUTING.md](https://github.com/php-ai/php-ml/blob/master/CONTRIBUTING.md) -- Issue Tracker: [github.com/php-ai/php-ml/issues](https://github.com/php-ai/php-ml/issues) -- Source Code: [github.com/php-ai/php-ml](https://github.com/php-ai/php-ml) - -You can find more about contributing in [CONTRIBUTING.md](../CONTRIBUTING.md). - -## License - -PHP-ML is released under the MIT Licence. See the bundled LICENSE file for details. - -## Author - +## Arkadiusz Kondas (@ArkadiuszKondas) From fc21c0bbcfd8f676692621af609b8d3fca6f7715 Mon Sep 17 00:00:00 2001 From: bimsuru Date: Thu, 1 Oct 2020 23:56:15 +0530 Subject: [PATCH 328/328] last --- docs/math/statistic.md | 80 ------------------------------------------ 1 file changed, 80 deletions(-) delete mode 100644 docs/math/statistic.md diff --git a/docs/math/statistic.md b/docs/math/statistic.md deleted file mode 100644 index a677a58e..00000000 --- a/docs/math/statistic.md +++ /dev/null @@ -1,80 +0,0 @@ -# Statistic - -Selected statistical methods. - -## Correlation - -Correlation coefficients are used in statistics to measure how strong a relationship is between two variables. There are several types of correlation coefficient. - -### Pearson correlation - -Pearson’s correlation or Pearson correlation is a correlation coefficient commonly used in linear regression. - -Example: - -``` -use Phpml\Math\Statistic\Correlation; - -$x = [43, 21, 25, 42, 57, 59]; -$y = [99, 65, 79, 75, 87, 82]; - -Correlation::pearson($x, $y); -// return 0.549 -``` - -## Mean - -### Arithmetic - -Example: - -``` -use Phpml\Math\Statistic\Mean; - -Mean::arithmetic([2, 5]; -// return 3.5 - -Mean::arithmetic([0.5, 0.5, 1.5, 2.5, 3.5]; -// return 1.7 -``` - -## Median - -Example: - -``` -use Phpml\Math\Statistic\Mean; - -Mean::median([5, 2, 6, 1, 3, 4]); -// return 3.5 - -Mean::median([5, 2, 6, 1, 3]); -// return 3 -``` - -## Mode - -Example: - -``` -use Phpml\Math\Statistic\Mean; - -Mean::mode([5, 2, 6, 1, 3, 4, 6, 6, 5]); -// return 6 -``` - -## Standard Deviation - -Example: - -``` -use Phpml\Math\Statistic\StandardDeviation; - -$population = [5, 6, 8, 9]; -StandardDeviation::population($population) -// return 1.825 - -$population = [7100, 15500, 4400, 4400, 5900, 4600, 8800, 2000, 2750, 2550, 960, 1025]; -StandardDeviation::population($population) -// return 4079 -```