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 0000000..5279539 --- /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 5eb84e6..0000000 --- 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 2bc4043..4e04931 100644 --- a/src/Phpml/Dataset/Dataset.php +++ b/src/Phpml/Dataset/Dataset.php @@ -6,7 +6,6 @@ namespace Phpml\Dataset; 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 0000000..4e01fc8 --- /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 0000000..4dddef6 --- /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 0000000..49b03a0 --- /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 0000000..e06f715 --- /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 0000000..e69de29 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 4e01fc8..5b418e2 100644 --- a/src/Phpml/SupportVectorMachine/DataTransformer.php +++ b/src/Phpml/SupportVectorMachine/DataTransformer.php @@ -51,7 +51,7 @@ class DataTransformer { $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 6e2be20..6842777 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 @@ class SupportVectorMachine { return $this->model; } + + public function predict(array $samples) + { + } } diff --git a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php b/tests/Phpml/SupportVectorMachine/DataTransformerTest.php index 84fd001..1429511 100644 --- a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php +++ b/tests/Phpml/SupportVectorMachine/DataTransformerTest.php @@ -14,10 +14,10 @@ class DataTransformerTest extends \PHPUnit_Framework_TestCase $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 e06f715..e4a8857 100644 --- a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php @@ -24,8 +24,8 @@ rho 0 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 5b418e2..05599f1 100644 --- a/src/Phpml/SupportVectorMachine/DataTransformer.php +++ b/src/Phpml/SupportVectorMachine/DataTransformer.php @@ -23,6 +23,21 @@ class DataTransformer 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 6842777..325fcf3 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -83,5 +83,6 @@ class SupportVectorMachine public function predict(array $samples) { + $testSet = DataTransformer::testSet(); } } diff --git a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php b/tests/Phpml/SupportVectorMachine/DataTransformerTest.php index 1429511..ff2a7c7 100644 --- a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php +++ b/tests/Phpml/SupportVectorMachine/DataTransformerTest.php @@ -22,4 +22,19 @@ class DataTransformerTest extends \PHPUnit_Framework_TestCase $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 05599f1..1ce4bee 100644 --- a/src/Phpml/SupportVectorMachine/DataTransformer.php +++ b/src/Phpml/SupportVectorMachine/DataTransformer.php @@ -38,6 +38,23 @@ class DataTransformer 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 325fcf3..f14d534 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 @@ class SupportVectorMachine */ 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 @@ class SupportVectorMachine 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 ff2a7c7..c07948a 100644 --- a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php +++ b/tests/Phpml/SupportVectorMachine/DataTransformerTest.php @@ -36,5 +36,4 @@ class DataTransformerTest extends \PHPUnit_Framework_TestCase $this->assertEquals($testSet, DataTransformer::testSet($samples)); } - } diff --git a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php index e4a8857..330f7f0 100644 --- a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php @@ -33,4 +33,23 @@ SV $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 f14d534..a812065 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -69,7 +69,7 @@ class SupportVectorMachine 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 @@ class SupportVectorMachine 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 @@ class SupportVectorMachine 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 a812065..e9bd8c8 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -124,5 +124,4 @@ class SupportVectorMachine return ''; } - } diff --git a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php index 330f7f0..d14f777 100644 --- a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php @@ -34,7 +34,7 @@ SV $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 @@ SV $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 5279539..8dcb28f 100644 --- a/src/Phpml/Classification/SVC.php +++ b/src/Phpml/Classification/SVC.php @@ -4,50 +4,27 @@ declare (strict_types = 1); 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 + * @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 */ - 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 - */ - 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 1ce4bee..155f564 100644 --- a/src/Phpml/SupportVectorMachine/DataTransformer.php +++ b/src/Phpml/SupportVectorMachine/DataTransformer.php @@ -30,6 +30,10 @@ class DataTransformer */ 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 @@ class DataTransformer } /** - * @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 e9bd8c8..7a47db7 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 @@ class SupportVectorMachine 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 @@ class SupportVectorMachine { $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 @@ class SupportVectorMachine 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 0000000..1ddcdd0 --- /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 93991ae..f1a87cf 100644 --- a/src/Phpml/Classification/KNearestNeighbors.php +++ b/src/Phpml/Classification/KNearestNeighbors.php @@ -4,8 +4,8 @@ declare (strict_types = 1); 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 ae98e1d..9726b40 100644 --- a/src/Phpml/Classification/NaiveBayes.php +++ b/src/Phpml/Classification/NaiveBayes.php @@ -4,8 +4,8 @@ declare (strict_types = 1); 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 8dcb28f..2350d5d 100644 --- a/src/Phpml/Classification/SVC.php +++ b/src/Phpml/Classification/SVC.php @@ -4,6 +4,7 @@ declare (strict_types = 1); 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 804b54a..4bf2a2e 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 8fa97f2..36b8993 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 cd0251f..83a6a65 100644 --- a/src/Phpml/Regression/LeastSquares.php +++ b/src/Phpml/Regression/LeastSquares.php @@ -4,10 +4,12 @@ declare (strict_types = 1); namespace Phpml\Regression; +use Phpml\Helper\Predictable; use Phpml\Math\Matrix; class LeastSquares implements Regression { + use Predictable; /** * @var array */ @@ -45,7 +47,7 @@ class LeastSquares implements Regression * * @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 a7837d4..12d2f52 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 0000000..07b1459 --- /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 7a47db7..ef52d29 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -131,7 +131,7 @@ class SupportVectorMachine 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 @@ class SupportVectorMachine $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 0000000..d794062 --- /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 d794062..dd3518d 100644 --- a/tests/Phpml/Regression/SVRTest.php +++ b/tests/Phpml/Regression/SVRTest.php @@ -20,18 +20,6 @@ class SVRTest extends \PHPUnit_Framework_TestCase $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 @@ class SVRTest extends \PHPUnit_Framework_TestCase $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 0000000..c384c39 --- /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 4b0e6a8..20cb9ca 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 d3f65b7..20cb9ca 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 0000000..d502dac --- /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 0000000..83c6aaa --- /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 0000000..ed2d10f --- /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 a596d91..f833fc3 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 0000000..355ff14 --- /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 0000000..2cf1144 --- /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 2716b78..0988e01 100644 --- a/src/Phpml/Math/Statistic/Mean.php +++ b/src/Phpml/Math/Statistic/Mean.php @@ -4,15 +4,42 @@ declare (strict_types = 1); 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 b9ffdec..12a732b 100644 --- a/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php +++ b/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php @@ -9,6 +9,11 @@ use Phpml\Math\Statistic\Mean; 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 f0dca3b..d56f965 100644 --- a/tests/Phpml/Math/Statistic/MeanTest.php +++ b/tests/Phpml/Math/Statistic/MeanTest.php @@ -15,4 +15,27 @@ class MeanTest extends \PHPUnit_Framework_TestCase $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 0988e01..9d87a97 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 @@ class Mean * @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 @@ class Mean 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 d56f965..289f9a3 100644 --- a/tests/Phpml/Math/Statistic/MeanTest.php +++ b/tests/Phpml/Math/Statistic/MeanTest.php @@ -8,6 +8,15 @@ use Phpml\Math\Statistic\Mean; 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 @@ class MeanTest extends \PHPUnit_Framework_TestCase /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnEmptyArrayMedian() + public function testMedianThrowExceptionOnEmptyArray() { Mean::median([]); } @@ -38,4 +47,19 @@ class MeanTest extends \PHPUnit_Framework_TestCase $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 9d87a97..3804848 100644 --- a/src/Phpml/Math/Statistic/Mean.php +++ b/src/Phpml/Math/Statistic/Mean.php @@ -29,8 +29,8 @@ class Mean * * @throws InvalidArgumentException */ - public static function median(array $numbers) { - + public static function median(array $numbers) + { self::checkArrayLength($numbers); $count = count($numbers); @@ -72,5 +72,4 @@ class Mean 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 0000000..3746760 --- /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 630f4b1..9da9765 100644 --- a/tests/Phpml/Preprocessing/ImputerTest.php +++ b/tests/Phpml/Preprocessing/ImputerTest.php @@ -6,10 +6,12 @@ namespace tests\Preprocessing; 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 @@ class ImputerTest extends \PHPUnit_Framework_TestCase $this->assertEquals($imputeData, $data, '', $delta = 0.01); } - public function testCompletingMissingValuesWithMeanStrategyOnRowAxis() + public function testComplementsMissingValuesWithMeanStrategyOnRowAxis() { $data = [ [1, null, 3, 4], @@ -52,4 +54,96 @@ class ImputerTest extends \PHPUnit_Framework_TestCase $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 0000000..9f88f0c --- /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 0000000..b373944 --- /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 20cb9ca..dc65c09 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 20cb9ca..dc65c09 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 0000000..e69de29 diff --git a/docs/machine-learning/preprocessing/normalization.md b/docs/machine-learning/preprocessing/normalization.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/math/statistic.md b/docs/math/statistic.md new file mode 100644 index 0000000..89cc00e --- /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 dc65c09..da95839 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 dc65c09..da95839 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 0000000..1ad84d3 --- /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 5972f1b..8baf731 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 0000000..5b3f999 --- /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 e69de29..db64d8d 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 e69de29..a0dbc80 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 db64d8d..186f424 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 a0dbc80..61b1a8d 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 89cc00e..626828e 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 da95839..db3c32b 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 da95839..db3c32b 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 f833fc3..68e8b97 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 186f424..5bbefec 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 aa68b22..1e71fcf 100644 --- a/tests/Phpml/Metric/AccuracyTest.php +++ b/tests/Phpml/Metric/AccuracyTest.php @@ -4,7 +4,11 @@ declare (strict_types = 1); 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 @@ class AccuracyTest extends \PHPUnit_Framework_TestCase $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 14fc69c..cde5278 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 @@ class TokenCountVectorizer implements Vectorizer */ 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 @@ class TokenCountVectorizer implements Vectorizer } /** - * @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 @@ class TokenCountVectorizer implements Vectorizer ++$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) + private function getTokenIndex(string $token): int + { + return $this->vocabulary[$token]; + } + + /** + * @param 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 @@ class TokenCountVectorizer implements Vectorizer 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 @@ class TokenCountVectorizer implements Vectorizer * * @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 64ac569..5166575 100644 --- a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php +++ b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php @@ -17,16 +17,28 @@ class TokenCountVectorizerTest extends \PHPUnit_Framework_TestCase '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 @@ class TokenCountVectorizerTest extends \PHPUnit_Framework_TestCase '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 1e71fcf..6f28d94 100644 --- a/tests/Phpml/Metric/AccuracyTest.php +++ b/tests/Phpml/Metric/AccuracyTest.php @@ -52,5 +52,4 @@ class AccuracyTest extends \PHPUnit_Framework_TestCase $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 45d532e..798532d 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -65,4 +65,12 @@ class InvalidArgumentException extends \Exception { 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 0000000..cb4ccc8 --- /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 0000000..841ef37 --- /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 0000000..f591384 --- /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 0000000..dfb5877 --- /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 cde5278..95ecc33 100644 --- a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php +++ b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php @@ -6,7 +6,7 @@ namespace Phpml\FeatureExtraction; 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 04a8bea..13eb955 100644 --- a/src/Phpml/FeatureExtraction/Vectorizer.php +++ b/src/Phpml/FeatureExtraction/Transformer.php @@ -4,7 +4,7 @@ declare (strict_types = 1); 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 db3c32b..c10cb7b 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 0000000..152919e --- /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 0000000..59d96c0 --- /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 00e6779..1477904 100644 --- a/src/Phpml/Classification/Classifier.php +++ b/src/Phpml/Classification/Classifier.php @@ -4,18 +4,8 @@ declare (strict_types = 1); 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 0000000..0631679 --- /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 12d2f52..27a1b83 100644 --- a/src/Phpml/Regression/Regression.php +++ b/src/Phpml/Regression/Regression.php @@ -4,18 +4,8 @@ declare (strict_types = 1); 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 13eb955..bdc809b 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 f1a87cf..95ebeaf 100644 --- a/src/Phpml/Classification/KNearestNeighbors.php +++ b/src/Phpml/Classification/KNearestNeighbors.php @@ -35,7 +35,7 @@ class KNearestNeighbors implements Classifier $this->k = $k; $this->samples = []; - $this->labels = []; + $this->targets = []; $this->distanceMetric = $distanceMetric; } @@ -48,10 +48,10 @@ class KNearestNeighbors implements Classifier { $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 9726b40..3e7d819 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 36b8993..fda27d5 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 2ac0ed0..f0017fe 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) { - $this->stages = $stages; + foreach ($transformers as $transformer) { + $this->addTransformer($transformer); + } + + $this->estimator = $estimator; } /** - * @param mixed $stage + * @param Transformer $transformer */ - public function addStage($stage) + public function addTransformer(Transformer $transformer) { - $this->stages[] = $stage; + $this->transformers[] = $transformer; } + + /** + * @param Estimator $estimator + */ + public function setEstimator(Estimator $estimator) + { + $this->estimator = $estimator; + } + + /** + * @return array|Transformer[] + */ + public function getTransformers() + { + 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 0000000..270fb89 --- /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 fade5b4..783bd46 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 @@ class TfIdfTransformer implements Transformer $feature = $feature * $this->idf[$index]; } } - - return $samples; } /** diff --git a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php index 5273f6b..f823778 100644 --- a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php +++ b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php @@ -48,10 +48,8 @@ class TokenCountVectorizer implements Transformer /** * @param array $samples - * - * @return array */ - public function transform(array $samples): array + public function transform(array &$samples) { $this->buildVocabulary($samples); @@ -60,8 +58,6 @@ class TokenCountVectorizer implements Transformer } $samples = $this->checkDocumentFrequency($samples); - - return $samples; } /** diff --git a/src/Phpml/Pipeline.php b/src/Phpml/Pipeline.php index f0017fe..3f98f29 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 @@ class Pipeline implements Estimator /** * @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 bdc809b..47c2ce3 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 59d96c0..ca5db36 100644 --- a/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php +++ b/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php @@ -23,7 +23,8 @@ class TfIdfTransformerTest extends \PHPUnit_Framework_TestCase ]; $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 5166575..80b7723 100644 --- a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php +++ b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php @@ -37,8 +37,9 @@ class TokenCountVectorizerTest extends \PHPUnit_Framework_TestCase ]; $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 @@ class TokenCountVectorizerTest extends \PHPUnit_Framework_TestCase ]; $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 @@ class TokenCountVectorizerTest extends \PHPUnit_Framework_TestCase ]; $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 270fb89..b00a60a 100644 --- a/tests/Phpml/PipelineTest.php +++ b/tests/Phpml/PipelineTest.php @@ -10,11 +10,10 @@ use Phpml\Pipeline; class PipelineTest extends \PHPUnit_Framework_TestCase { - public function testPipelineConstruction() { $transformers = [ - new TfIdfTransformer() + new TfIdfTransformer(), ]; $estimator = new SVC(); @@ -23,5 +22,4 @@ class PipelineTest extends \PHPUnit_Framework_TestCase $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 355ff14..586e3f3 100644 --- a/src/Phpml/Preprocessing/Imputer.php +++ b/src/Phpml/Preprocessing/Imputer.php @@ -41,7 +41,7 @@ class Imputer implements Preprocessor /** * @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 4f01785..832d044 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -33,7 +33,7 @@ class Normalizer implements Preprocessor /** * @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 cfbaf06..ff5530e 100644 --- a/src/Phpml/Preprocessing/Preprocessor.php +++ b/src/Phpml/Preprocessing/Preprocessor.php @@ -4,10 +4,9 @@ declare (strict_types = 1); 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 9da9765..a7e36f7 100644 --- a/tests/Phpml/Preprocessing/ImputerTest.php +++ b/tests/Phpml/Preprocessing/ImputerTest.php @@ -28,7 +28,7 @@ class ImputerTest extends \PHPUnit_Framework_TestCase ]; $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 @@ class ImputerTest extends \PHPUnit_Framework_TestCase ]; $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 @@ class ImputerTest extends \PHPUnit_Framework_TestCase ]; $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 @@ class ImputerTest extends \PHPUnit_Framework_TestCase ]; $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 @@ class ImputerTest extends \PHPUnit_Framework_TestCase ]; $imputer = new Imputer(null, new MostFrequentStrategy(), Imputer::AXIS_COLUMN); - $imputer->preprocess($data); + $imputer->transform($data); $this->assertEquals($imputeData, $data); } @@ -142,7 +142,7 @@ class ImputerTest extends \PHPUnit_Framework_TestCase ]; $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 b373944..cd007fb 100644 --- a/tests/Phpml/Preprocessing/NormalizerTest.php +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -31,7 +31,7 @@ class NormalizerTest extends \PHPUnit_Framework_TestCase ]; $normalizer = new Normalizer(); - $normalizer->preprocess($samples); + $normalizer->transform($samples); $this->assertEquals($normalized, $samples, '', $delta = 0.01); } @@ -51,7 +51,7 @@ class NormalizerTest extends \PHPUnit_Framework_TestCase ]; $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 3f98f29..2c2d984 100644 --- a/src/Phpml/Pipeline.php +++ b/src/Phpml/Pipeline.php @@ -68,7 +68,7 @@ class Pipeline implements Estimator 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 b00a60a..b1d5bf7 100644 --- a/tests/Phpml/PipelineTest.php +++ b/tests/Phpml/PipelineTest.php @@ -7,6 +7,9 @@ namespace tests; 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 @@ class PipelineTest extends \PHPUnit_Framework_TestCase $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 2c2d984..f230db3 100644 --- a/src/Phpml/Pipeline.php +++ b/src/Phpml/Pipeline.php @@ -67,10 +67,7 @@ class Pipeline implements Estimator */ 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 @@ class Pipeline implements Estimator */ 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 ff5530e..ae70941 100644 --- a/src/Phpml/Preprocessing/Preprocessor.php +++ b/src/Phpml/Preprocessing/Preprocessor.php @@ -8,5 +8,4 @@ use Phpml\Transformer; interface Preprocessor extends Transformer { - } diff --git a/tests/Phpml/PipelineTest.php b/tests/Phpml/PipelineTest.php index b1d5bf7..4e5815b 100644 --- a/tests/Phpml/PipelineTest.php +++ b/tests/Phpml/PipelineTest.php @@ -43,7 +43,7 @@ class PipelineTest extends \PHPUnit_Framework_TestCase $targets = [ 4, 1, - 4 + 4, ]; $pipeline = new Pipeline($transformers, $estimator); @@ -53,5 +53,4 @@ class PipelineTest extends \PHPUnit_Framework_TestCase $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 c5a24bd..92de976 100644 --- a/src/Phpml/CrossValidation/RandomSplit.php +++ b/src/Phpml/CrossValidation/RandomSplit.php @@ -44,7 +44,7 @@ class RandomSplit $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 7c5c2b5..fd471e1 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 @@ class ArrayDataset implements Dataset /** * @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 7d1f91e..23ac35c 100644 --- a/src/Phpml/Dataset/CsvDataset.php +++ b/src/Phpml/Dataset/CsvDataset.php @@ -36,7 +36,7 @@ class CsvDataset extends ArrayDataset 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 4e04931..791dcb4 100644 --- a/src/Phpml/Dataset/Dataset.php +++ b/src/Phpml/Dataset/Dataset.php @@ -14,5 +14,5 @@ interface Dataset /** * @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 7244b3e..d1ede5f 100644 --- a/tests/Phpml/Dataset/ArrayDatasetTest.php +++ b/tests/Phpml/Dataset/ArrayDatasetTest.php @@ -24,6 +24,6 @@ class ArrayDatasetTest extends \PHPUnit_Framework_TestCase ); $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 2994504..a2629a6 100644 --- a/tests/Phpml/Dataset/CsvDatasetTest.php +++ b/tests/Phpml/Dataset/CsvDatasetTest.php @@ -23,7 +23,7 @@ class CsvDatasetTest extends \PHPUnit_Framework_TestCase $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 @@ class CsvDatasetTest extends \PHPUnit_Framework_TestCase $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 6f6e177..d4f1313 100644 --- a/tests/Phpml/Dataset/Demo/GlassTest.php +++ b/tests/Phpml/Dataset/Demo/GlassTest.php @@ -14,7 +14,7 @@ class GlassTest extends \PHPUnit_Framework_TestCase // 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 1f0da90..354329e 100644 --- a/tests/Phpml/Dataset/Demo/IrisTest.php +++ b/tests/Phpml/Dataset/Demo/IrisTest.php @@ -14,7 +14,7 @@ class IrisTest extends \PHPUnit_Framework_TestCase // 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 de16483..34c93fa 100644 --- a/tests/Phpml/Dataset/Demo/WineTest.php +++ b/tests/Phpml/Dataset/Demo/WineTest.php @@ -14,7 +14,7 @@ class WineTest extends \PHPUnit_Framework_TestCase // 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 0000000..15e3975 --- /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 @@ class TfIdfTransformer implements Transformer 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 f823778..c4551bb 100644 --- a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php +++ b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php @@ -46,6 +46,14 @@ class TokenCountVectorizer implements Transformer $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 586e3f3..fdbfaf6 100644 --- a/src/Phpml/Preprocessing/Imputer.php +++ b/src/Phpml/Preprocessing/Imputer.php @@ -38,6 +38,14 @@ class Imputer implements Preprocessor $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 832d044..11a0218 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -5,6 +5,7 @@ declare (strict_types = 1); namespace Phpml\Preprocessing; use Phpml\Exception\NormalizerException; +use Phpml\Exception\PreprocessorException; class Normalizer implements Preprocessor { @@ -30,6 +31,16 @@ class Normalizer implements Preprocessor $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 47c2ce3..1accdda 100644 --- a/src/Phpml/Transformer.php +++ b/src/Phpml/Transformer.php @@ -6,6 +6,12 @@ namespace Phpml; 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 ca5db36..eceaeb3 100644 --- a/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php +++ b/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php @@ -22,7 +22,7 @@ class TfIdfTransformerTest extends \PHPUnit_Framework_TestCase [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 9f88f0c..5c7ced1 100644 --- a/src/Phpml/Exception/NormalizerException.php +++ b/src/Phpml/Exception/NormalizerException.php @@ -13,4 +13,13 @@ class NormalizerException extends \Exception { 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 15e3975..0000000 --- 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 @@ class Pipeline implements Estimator 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 fdbfaf6..424efa4 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 @@ class Imputer implements Preprocessor */ public function fit(array $samples) { - // TODO: Implement fit() method. + $this->samples = $samples; } /** @@ -52,19 +59,18 @@ class Imputer implements Preprocessor 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 @@ class Imputer implements Preprocessor /** * @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 11a0218..7647997 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -5,7 +5,6 @@ declare (strict_types = 1); namespace Phpml\Preprocessing; use Phpml\Exception\NormalizerException; -use Phpml\Exception\PreprocessorException; class Normalizer implements Preprocessor { @@ -33,12 +32,10 @@ class Normalizer implements Preprocessor /** * @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 a7e36f7..9aa3ea3 100644 --- a/tests/Phpml/Preprocessing/ImputerTest.php +++ b/tests/Phpml/Preprocessing/ImputerTest.php @@ -27,7 +27,7 @@ class ImputerTest extends \PHPUnit_Framework_TestCase [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 @@ class ImputerTest extends \PHPUnit_Framework_TestCase [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 @@ class ImputerTest extends \PHPUnit_Framework_TestCase [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 @@ class ImputerTest extends \PHPUnit_Framework_TestCase [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 @@ class ImputerTest extends \PHPUnit_Framework_TestCase [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 @@ class ImputerTest extends \PHPUnit_Framework_TestCase [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 9aa3ea3..fce195b 100644 --- a/tests/Phpml/Preprocessing/ImputerTest.php +++ b/tests/Phpml/Preprocessing/ImputerTest.php @@ -146,4 +146,31 @@ class ImputerTest extends \PHPUnit_Framework_TestCase $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 cd007fb..d01e62c 100644 --- a/tests/Phpml/Preprocessing/NormalizerTest.php +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -55,4 +55,29 @@ class NormalizerTest extends \PHPUnit_Framework_TestCase $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 c4551bb..501263d 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 @@ class TokenCountVectorizer implements Transformer */ public function fit(array $samples) { - // TODO: Implement fit() method. + $this->buildVocabulary($samples); } /** @@ -59,13 +54,11 @@ class TokenCountVectorizer implements Transformer */ 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 @@ class TokenCountVectorizer implements Transformer 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 @@ class TokenCountVectorizer implements Transformer } } - 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 @@ class TokenCountVectorizer implements Transformer * * @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 80b7723..cf22b5c 100644 --- a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php +++ b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php @@ -37,10 +37,12 @@ class TokenCountVectorizerTest extends \PHPUnit_Framework_TestCase ]; $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 @@ class TokenCountVectorizerTest extends \PHPUnit_Framework_TestCase ]; $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 @@ class TokenCountVectorizerTest extends \PHPUnit_Framework_TestCase ]; $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 5c7ced1..31abfeb 100644 --- a/src/Phpml/Exception/NormalizerException.php +++ b/src/Phpml/Exception/NormalizerException.php @@ -21,5 +21,4 @@ class NormalizerException extends \Exception { 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 17f5b8b..1e222d2 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 501263d..8d4a5ab 100644 --- a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php +++ b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php @@ -92,7 +92,7 @@ class TokenCountVectorizer implements Transformer 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 424efa4..012bb79 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 1accdda..f7ddd81 100644 --- a/src/Phpml/Transformer.php +++ b/src/Phpml/Transformer.php @@ -6,7 +6,6 @@ namespace Phpml; interface Transformer { - /** * @param array $samples */ diff --git a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php index cf22b5c..3a5f7fe 100644 --- a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php +++ b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php @@ -78,7 +78,6 @@ class TokenCountVectorizerTest extends \PHPUnit_Framework_TestCase $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 fce195b..c607510 100644 --- a/tests/Phpml/Preprocessing/ImputerTest.php +++ b/tests/Phpml/Preprocessing/ImputerTest.php @@ -172,5 +172,4 @@ class ImputerTest extends \PHPUnit_Framework_TestCase $this->assertEquals($imputeData, $data, '', $delta = 0.01); } - } diff --git a/tests/Phpml/Preprocessing/NormalizerTest.php b/tests/Phpml/Preprocessing/NormalizerTest.php index d01e62c..f0e21c9 100644 --- a/tests/Phpml/Preprocessing/NormalizerTest.php +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -79,5 +79,4 @@ class NormalizerTest extends \PHPUnit_Framework_TestCase $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 247e222..2337e48 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 2337e48..cc176a6 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 cc176a6..9f59935 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 9f59935..3d63462 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 95ebeaf..594438b 100644 --- a/src/Phpml/Classification/KNearestNeighbors.php +++ b/src/Phpml/Classification/KNearestNeighbors.php @@ -67,7 +67,7 @@ class KNearestNeighbors implements Classifier * * @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 3d63462..6ddffd8 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 6ddffd8..5281d26 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 5281d26..383d3de 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 383d3de..6b59901 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 0000000..3e501e0 --- /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 0000000..93303ee --- /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 6b59901..ea7c1c8 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 zPjtjHS+%3MudjSx;u)zFn)^)WGo#`)xmiCw0U?}EI~wKe@;1kAeYwvpe9mKzSmZHA zJm)hC7s(OkH@%#=RZ{gvZUMmi<=?p9dZF?fy(3$s1($y)Je1dfG)Y?`TcmKc7z{Jx zXH<*x86i_Jev3n${Z)nJ0wHckgzV;pMk^P7G|FLWqnXeKqgBGC%^t@wR1;@vOJv^| zqv#o*`zTHfk(V(hsJ?6wA;4q{O5!>o2t0Ek2y{HgPp^K%dq>ENMuD)=mRvX_**}~2GnV4EhUNV1|3jl#(evthgY=2^o#)I{&5?Gw z1mSsEUaB+gFWbw@^!+g&>p;85y(99e@*}Lh-eqDO-3l_Qzddp16{@eD&%~WyC3M>GBECdgIW~FtXS;Vgo>gqrx0mfM?)X=# z^~6UrOorLRB$Y^gPuQ+^eml31jOF!~5S9NM`=-}8id`IO#|pvr|KY+XPmc2$kKr{C zcvBtR#r*8(V#~PWYqCp+vNsT{qtx%H1R51^Z461${Pxn|Wcj=yQc?`V6kKz1{A0P_; zLh>+@PtiZm(?8GBKRl{3<5 zc+TXE%wx8(R_3_jtNCaFQ_rTvH_*OBu}FN?3e+bCsDSztmc;plWo~dtnz3K?-mV-7 zbeUSZbK{VZkU5+yfaLZC5t8K_kiYT!2zkv1pK%&(-qM8E(JtCrXt#Yg5&!y}*maXq zeHdkHMDuJV>f=TR{y>iFn;`LW{slt_biCg2x)33J6%ribe?GS_xo|k(VgjxX;Ll;z zg#vlX`I;Hp5x&=JG?dESIf4!S`YJ1+6!si%7kB$F6i|BTN8CqnJ5Y+abm?~PB;dMZ zby?SZeA2($^XjWTZ-zr~F-*bYoMhmn#jC7oe8yq!g3y-y0igcS77#~uLw$>^p=^DABj!(%U>H4@Ln)4&t%dJK$M;jciTe(EwGfUygNgTG8 zJD@EBMK-1e<;(i9z(-_%;@{-8>08Pw&9_4KVw_$4ofJ*o956o7Vn3v9)@|Co^a&ep zaU>m5J@Hd=Ki1UeR+74MYeZ!(t#L)9TXjO z9B(@lMo`2A*}|fUB+!8bB4Jgr37rm!h9uJq1Y~IqT+_6T?TicV8Mn*0=FW^`bQBWV z0w_eCQBbeMy<^mgIHNc!`F_8udV5Q}{q*}*omx-r=RN0CRhzzrL@jB0 zX0mLu@gwaF6{#_tLCbZ!NaRH+QxGc~;h^q656r0Kfr&l?j-Hp;@JYQoED;n|CV8_r ze1Mg*u;IDPMh1p#NrRHew`}A(3e8?3SL~?HtOQNlm=|2q>pzSob9Y3a(B{C!ubeoEDKC=XWh9Fq6w%QzD`PYT8Zjp8XlbMqnPJ#L`8SRbcXkm5OOdxS_omX-* zYXh-5W(LjQu?V(gAb?LftaH#^MHEWvGRz}jYlUsGv~|F?$UFl4fRs5H6Yk+*>qJso z)J)7c?K!j}7&($j-hFPA{sGe=Wg>!yznBmWe3K4Lel(5=P2u-D_`T9Zo-ML(BBv49 zxOioIk^dbaI+CLUT^UENa}#y%L3vGMF^Y@23aks6_Oj|goHuLI97AAhcQQPo`q|r_ zpM!%ac0iEB=2AracjYbRC_D~!*^RQZ37-Uxs0SRY^HKN#*zvUh?$CKWE}$TUR>5-0 zu4CC!1d<7*PT7MtL?Jk&hTsGqehd|~@-R*|Q=p&;6L=~T+^V)@;h@nF7DAlT#}U{r zC!3G3A~uJ|Gh-a7Fw<}T$dH=i{!xx%%a$W-p@6&q(fkgWPOYftloy#Zhj6DYV1fGY zJ2ro+;>T0XkW67ohk4J(Y?q3F89FN4u+xY*CsoC?r9OiKVe`PZNuHYc`3bAvK^gsB znR8t9p@(J$hQ13eJS-pc4ys)G2{!K#vSGM+?N}8+>gBxX-DZsMVhHL@#W`VozoESc z4@R*p!FPZR_!9Xe-vxdje)$4!Ksex;h(x{z6^$T!D!5$>Q}(iB;<7^5f8qQapT#M*zYG>0LMG9y537HZ^U&5L@*26DlDf?hsygI z5oeB+NZm$U$v$ORayTVRLXxd7obcWT1?_^Iuw~Q7U~=*5RLhooLGH-ARz5;^fCc>u zoeul^;a(7&kK2&at|1&TW3ZFxdmLE~Y=j8ICNB+ze`ToC+JPNA)&7xq@VW_e&Jp|3j3-i4gY|+KA@}>*R zVnU1L-6fPaNTI_bYI0g0GGYpW9dLu~2g+`vZ>;`!@?3FhfH>1)=?|zoJ zS#Ed{lQlNdBuO0c!O64nulxt=xhRm{0oSX>zDz$ei^!R02yBEQ?_11M@jqp~zz+Sp z5xfv*(LZJ5xYl5p`p3(-*1|QYXmWA8bX$2P_G}~eDMuj@<&Ba4L%d^Iudk4( z4#M*j?B(vju?Nr-y9(mjA*aIaB-o)_#=HJ@M!GI$v=iR_#W_gnJKAC zX!j{F&w5)$NzwcIq5_Jb>GszkxiCPuE*So z=7;i7rVL69C*_QS3x!>oFuA1&yP2?3`7AdcLdVDp&Ifa}_=J?yQLQXjDwAb@DIF5) zll=i#ew;vW9Bsy?<2|dAaq$HDI6gSRxy04h-Z%NB=8cX&^WH+oK^guKgChugRX!@+ z@tQ4YGfA)4{rguX`K16YA19+Zj^Q&wdVJ+S2oH`uL>Y$DnhcM(_+5GL^ zn~NP^vR2MStu)E;&-xghWz@$@tPjcG2%kYm9xs#uOJQJR=2lb%rp@!r&0td2$B=@o zfzd|&8vM!95=r=x#~9xe&V)9`_jQCr7wV1xDnNFG6U-AGp_3iq2KcfgbRz#$ePnuw z`IVN~aANKz+~k4M@IJAj;nRLf>?emync`k_Cm4*SZa+%EH!v{%yl-1TZV3lR(ev}{ zeGMBb#riXi@&$%A`iE}eCz@knC9aQH`qW^mrI55cnO9`Mg>!NGT_;caf~x{(g(99$nEz5#Jp*!Gzf z(jZ$~uT|3fPhP+R7JvZ%a{c(?fU^$WxKR$kNPQIs#^Q2WVEb|S9AJ%1lkp8Dr!Ra* zk$zv-jB^LD-@#hgcH(Rf`o%~U;uR2{+aPYp1ooh`dXp?SHo#UG2sY6nh>;Xrcp|$s zDj+#Ozwx*kJ2iWhpadKmtf%OS$>ux2p1Hd}`N%&a~GSzV01v8>nZXI)VZ(h>@$O7TWsg- z0Zn7ZauWXZeAcXF&5-)os+oZ72DnWYZ0P9tMRoV?w*_b5^QFfU2 z4}r6u#SM)0_&PyXy48og&>KOA3_Zckxib(RA7tbP6g$4j_scUBpgHO_~r(Sm8sh-4?tQm~GBl z!%iz!6-ib+BiUnrMD66^uz5(dlDS*6xDgv{yI_W}Po*p0|2YoRQ{vue{UgbtUtp9C z{fcRV*$$aicQ;4?O%GthI!L)Cc_Y6pG=VfJ-a0cl6iLc!>X|Ar)c9K#&sFGK7GEBN zJ)EP+TNdNk6XetIg2X)!XGwd}C>Yha&paKRGmKj*g+c*#bo)x|i!R1kp6}~5-`t(9 z*N@`XUh{R_zQ^5+#m&_9p8Gi<`STRTzz#r~Iy9cSH`ADl@#bRU-Q z?QoTcwiK{A4x0vSis4nNoV}+2)fIXyBYGFz& zP;C5Ydn`2CgVd|hn=FeL#rc>q2{5h6`LRA&3;VA zSfKK8E&j1P(}Ah*RNy*_9V!4xAIAg9`nJJ$ZT# z>_{MqBISo14f4LX^CnN(3R-AGA6n=rX`!lUeL(`_D|Eb;H#t4T;Gl)_0`q*3=bo^E z63Uy5i`e#W03DPU@c2LxJ_={SBzZ9cW?^7c44HG?E&?%>4!*DA*JF8S;+N2eZ^HZ>J_yDexSibK zxN#3==0e8+EO49g$&C3CihbiIfEvkemD*Jd>X?o-V(^_&|ICQLAO27-HiMGzp25pR z5&QIr?={*NoJOpstA2_(C}VmCf}8*e(TuP0zlCi^0_{l*z zJ(z4f^zs{9TelT5)eeL?fG`qItPbH5!-0Qr+%Cfn^m(^*nz|iM+qv9G`h{wE-@*f3 zgjW_E@)x&9c%UFWfGCc^Uz=@nECCqJ8g8Uxul&FLB3vWg`xI3XR z=dHDG?*K0k4fR?0S~@oDV4GlAe@j`fX9>ut$wVUsjuAAx2uWcdcIYCXiO451e;Sl5 zZuOHTWNv36xa990^>#*b8!U6vj7rZ6p-QnM<$;xq_1dLP=+AWTEP65oi;f2 z3M9wtJ|g%aB?w!gM6je;uTRNwed|PcH1m^Re+RvgF%-1V4vg)D+0syN#(4bMX-Lvb z{r*u4aBnJk^}%u9fPf1B+fSpu0RvXy77s19Vu|cg7n?2smoIyT2 z2i&Gs&%hR}4!zy!bowitzB+tHD?X)SFRF`Of8ysyrJrI8{OqAIY~-Kn`Q^_Tk>MsJ z1{DOfGH%YWY#amDzq(RsA3U)1%TUa6D)|YUN+UsLi;}2Fj4=Ae~E6OV63d-t( z!5g;g#k=qOzng5im><}4e1Cd2tNI-jGRPf9YJzoT=Sc9vp( zT)Gq-*F$Hfi6OD^_DJaW==_#;z9-`R3Y~Xr=LaIryXgFccK%(&IgQS@Yv(5;&R=0v zhke>vZ7(4vAJVx>JGWujV!?#-U+FwoJ6~otoOjWAvU1*ZT?$)VDnb-F+?61Zo)Y+l zyZ?=?3o#5n@B>Y7fyfj@imXl~{W;RW{XgL*1f!;xW#ez)i%+*dBOB<8fKE~5N{Df7 zrZ-by*g!3iZxgO5)Gmn0Q zl;3&U?@Ici+GM=HfzyR}!?a&6{W!NV-mA(lRr_Vqk42#0)9~xshmaMac>EqYTjKWy z`yKH68$b-fYmmo5?erF$G;W?!xVLHdU36D?c_zdCaqa#X-4#xrU%>qy?S2d0H9ngb zAb=qc*Zq5ux$8_|Y1HoJB$5KRatwu25Vh+&$OsM3>B^I%Djfb9JUe4NM=H;W^gKY% zoiU!t$}@|ePtfz>7|#zupCC_;#qiDa^uyCo{Sf*1+i>7?IkSn3hr%c_foYXLr~;=dJTX)okvvdA?Ipz^It#h5_C5!{ ztxxS8!S=`Kcx3`HbvA_fJxexSe4QQax?e_Fj6eu*WKr%*sKA5S{ zoY^0mV-PU1en#`k=aNISk!Hp0Y$i3N3@O!cR>$xAwL9!Lu3 zi=MtxdQ7Ohk*N)P7dVRULv+k1R{*QCWc-N;@E-`kG=owJ3as+dKH@%jX2R|T?w{IoaxEueH^cw4!*)iW%QIz zI5Jc#+#up6$8-h=u|~`@nD3{y!CUH~AT$s7jT?_RRtRJk)6awA%BEJ8-%6xJpbfHS z4i9{4aardKMWtx0+aHGWiBsSzto-+I{t3?N_2YXAO^Lm_ZDA{dONv3O8jNLV|8%G` z?BPksZQp`@_&(04`zU-Se5gbXmGc3F(*Ub8L>s;~c~8+8bYE&%pz$k7jbPyk5FI5% z31^onGy}8iZT|ctsZ8gEVJL&VX@y~Oj^J=l|MWWaPojlYWXt$upyb#MhCRz=Gl5U~ zkvSY-G!6xsbc19On=D&)ffhkrmK6MW6pl7qFb|ZGq;~k_GJF{3Ky-zC#ZE*$6)07R zbCDDJSE1u2d1S6ZHJMYv)REO2C_jDE-t)=SP4=}c=WGTPKxDR_} zK-4T_RWg|5cX+%-$qx}HqdFtsWBt_U1VjUaJQEcvzma|lm=%h{P<4fFVOD8IR;b|( zbolx-nH|{qsL)Tn5B+Z{)UxRS>AAqvYS2O9aY&I9Pclr@B?i@|-(oSK)5k+2PFv)9 zB+1UbzHR6ps9ltMPnQ=S;Rg^fW<8eQKMP8^=^`+7ATkMz`x+!Sx03%3A#rpFX1CgC zf5%k*4*a+4ZWm4kDxA5BY>siQ@UUUG$hfCz!8CgWDR4`U|7VYDmsjZmC5b7?fpIs( zZ6q#{JQlu$u1Ms5q~0Jy9)`S(LN3L>lKAB{Y-eGFXBaMytQPBs;L^)FVh6p8iQzMm z1RQYmEGAK04j(xcIACdaOs}-{w%d4L z6)teD3%9|&aPRawW(|k>aIEWQ%aG1?I+w$_ z;o4MTY1|AHjR00l!wnJ~J#B+Opi;_mufZ{VXd71=*sGo~ z2ee#?h6T~yhHnAD$LuoH_;xLwp6P9H4u=6hYsKd0AL5^T5j1x&s$!=cgZQp zBD*!$ddy}ifF?6`aW6$Es1{i^Erg{YaB;qWo!-v1Z3tS^ziUS>${oH7K)(M3_P1{t z$bi|s^3op&wQ%99+N$++!KOE#MOd+ZSfS%U_AA(YnBMm+T5JRvA-e0IFXt;X>veShA~oAj5-s67aSo z2W-o_aj&@%*kFm+8Cdrsq$7DbUs*O?jTsWStS>8e>{Kde*}V29>hokt^KJ@Yy=n=`@2Wk1)nxWAr;A zw~fHU5Fipeh5wDwt$ra$fUfZ1}AE?AZAWRc}j0H5cNw2+XIpj6fY?K+pR` zulMOCQizW*bF%u)bWVJxCsl-gKz=36?=t2m&oGb4@43K?QP|cr4gT%$o->i1$SpWWpcd=h+>KdfQZc3tUvh8h?ZxA z6>vIKSdJmf^si*CW}p^dVV%uJm3~ejklj6q{La9yRDGhx$K)LAw>}rKe2ZB=++c_L zEK&6d2IRBwIBh1df*({#7em==Vag-Kva5VB<|Cxv7eC#|``E}a^}P>`{6DR4$UWoQ z_c>K@i)Kt*(sjE;K;|zs#Z~_T|1~!i3oc|#=v$=WP+9q=RQ6sUIClnPDfm5#bN!P3>+5=jkhVd4si<3MjN|7k{-*CHj8Vo zC0(Z=dBu;E{$0NWfgXh;>k)KtaI500>U_k!z<9Y`0vqub&LItyavXCmyLKUNc*ik` z{sQbug~oX=j$PY}nW`7(t@VZM?kPGq(0YW{0^&To9%Nkii?Q%3Iv2sk^iJx2E@pa}enb1$`QADV61o_>bq$^DqoF!{Gdisgk2AL`BB z;P_)C!Df^a>mrMZ_M!UC;&vJF@-}Rl8)Cj2WzJc$%X@FPnK{b(-Zd;n*ct6lP|7Jm zAB@b;vOM!jv2#b31N%?bX4=BbPy}2n%0oc0%%W?@_h`EJ-`DAro4a{z(&n?$8qZ3~ zZ5*3MCoBT-DG)c=CVlF6K`GR3!snpBp%F;Xb$xyk8w^*u9u0p8D^+OTO~n|I`eGV? zY>~wv_yW|E8UzDz)_bmM&&Lp_IIzkFF107CI9M?Zi~_5yUr>V?;qQ=?EpSCD1bE)h zK*VN*yWzr2Rx{tgh7y&G6A*gOB9KFcekQLwPq7LmJI`RFSX~p~Y5JMJ+YcJAAENMaHU1__ zgJkt^eMy$>X!y_o3|Gleikn{}^~>>^=YJjgn`*S?a)&Bz*S{W2 z$bQCGfS>Vlkr{{)mCF$|Ix?b84$1NKIHJanr@JIa^Q-Vk<`dXpQ{(A0Kp0P{!xCEe zJA^h`H#(j^g6sE;rvcy(Cd_B^^XBop4{b@cV}8R0E<){OfR`3N7rYe=sSbXd%c2a( z51e1%ugO&-E5z(^JG}$yolw=mk!W&Yy^LO_SHlY3z&B zg_a`RxMs&nc$}jOk@#R8TyLy{opK%A$yL;5QO6Va%H*flL&>+$QYeom{zN+MMZ6Mu zs4Zvu`1NzYinNHD!K<_1gs3x0Z$Lq$1m~~)n+YHNx!fF&ud0$gU|8Wpf06&Jt$Bgj zxuzr7P>V|^;DoWe9%J{b{>JX~dO3EB9W}X^2Gh?+9K!8bhl+B#*WOlO`V`zub?z?l z)8)!L%t`!Qg(8@>IGbGA>+<$aUn;9lssl$>Y;}N1Fy&peR-u0Y5Yn?QcdG)@a{$F} zOzi-t)@XVAtk)Q3P@=VALBk-Y7Q&)@>^qiAUS6OmQ%?ye!qcWfKtqR|_H~zT+PD5< zY!E=A-QE2Xet*K|ffwUyf68_95y474@ob8TSsvo%5|_`SO0KMIV>o+bMsudBEoM(;L$V%W2)so9Hs4ER+lhzA1#0>_O6Rg0bVD!lj?8nt!BW z%l8`jUMt`0<=ZFU*UR@t`TlqLZj|rM@_nOxZ;|hieE(X$Z;|iYhzMqiqX8HcTeE(6t|0Lfn^8KuQKZkcw@Mi`1sLU(v z9X<|bs1g+>x<16TNe;H?dW*JS()J2%<7sQBt&q0uv@N6UkF?d&_6Tk3X=|b_KpT!% z>aDz;wi{`Cn6~R_dy2Mt+IG=aLmN9^S2=B+v|UbH4{ekORGy%1I&JA_@vbSfok1Ip zJngLk_n8)Al78-<8+W_6cp9Y2%%#m3Pp_6Pqf3 zPaE&NtmLULUAt&INZYfty-6EyYpLv}?P1!;KJ2=iHfptW{hGEjXyeT?l_O|dPum3A zJhV-x?P}UeY2#U`m1K8zEud{JZAGx524a4Ta~05Y$&imPxbBYIe>GLXotJ2L9lmFS zf<>z`Gjlbp&7K{fSH9Qy)baN(zF^u}-S3V+^O8S5|N4=mYv0uA4b8PZ`P`kyUSD(Z zfip|?_xAqnl52-PG2@*z9-WU)O=RU6Hf{P06ZnhR2lXT61%}CRv@;A=Ac{c+$Okb1 z59kH`>oLk@m^+k5kKAiaJJa9~&D$Gm8ueqC^v7?5eA8r{gh$5bi$752eSbYh{+Gb7 zb^I)+tHxO^#+TMsuXMUZjxz1D=ZguK6_4ItY%S3keoY@8TX+?RZrGVf# z7e?%Fp{urLxwWdM&g-#O)Vfxbd93(XSJgPJ!^_h|iK}+mXzPmFa%VNYtsbY_V=XW9 zlufdF%3RBx9_z}oYOm8e)mrCrmRC7E&T{Q?zIDy0v^A5gMP+r>We%scyvhy4mU%r@ zwKZ1fRUO~#?sB@_$Yf-iDAfTRwJw*lx=d#6sC8CUR5_}g zHJ<*W1A{8g=PT6RTqVYP}v=<%VZWG+o<-|cT2t$>y1jLE zwJs0pyLMUGva0H;)mF4g6&lWoJj8r=*>dM3t9#{&3v`QEuTU7em&n$$S1@6jhq5Y6 zq&-cCoRoIKvS6xQw!9SA zvg|{(HO>oUZCXng6kBU+s#haumD?zpvplwcs?zI1yT30hbSrkSvSroi4plVl_3C<5a_I=3J%zqmlwPr1`mR#ojD9g~ErE;%NUw$)wdbf7D$vaWJgEwA*j0@*z( zYP~hcd|s`mtlC;rxQWU8iv2q<*acy(*!z{-Ltw* z_2g^h{1b)CIq1}cvAk@>iZYDEG{J$EC2ch>yD9*y#Y4>O?v=8RTv*O!sRX-oo_t>_ z5Z=8~lrBgU!$)V2Uv3pylTc_eynNE|(Gvl>mWkmLv)mD!gjWwmAZaC%sJa*wBi_o* zTH&p6aL6K*LL4aziMJSDU7_$M4ie`GzQ8AvkjGh%3a>HdMGUA()+>g)m!w5P7??yH zbl${H&C>a^bJf*Wudb)gsRO&K*_X>y>! zda*TI)X8++m9DCqYuM+`K{RVw4f;&16T%tOhi7#(OoC&!h##u4-g^FunUT;NS35l- z+Bbm_Iwkv1nY#)j^Z(6aB70!%va2}|Dn?n+~ex(*TaljC3~oyxnj6s%s51iR~l$H-F-FwaCIq9m*C=F<`U5!hXGX~xb)Umm7`-g z9kna4HmP~I_pUdu7s;=XToUIQv}D^yvYRZNxbT&fxruYZ$B)zyz4h1U3UW&+z#iVh{XP0==-2 zYL~x3_}s9LkAMdNv6eo5$U=Vs;8*ndBLLz?{)PAwz^jJOK70?#J?C%` z*TI`u-vrZirv|nW_=5)82)w9)Hwb*7fiDOs9DDET5XrNdU)3y@e*Y*ND^7pUP-if617?#y5f3)n!^B1P(z>7)0Lf`?sp!lGCo(3Rm z0uI3Ozb^x0LwqL5|Z?1A}5m`jhq4m0-+g!v55+eiZ@za`Q|j_8q? z2$2OGI$6$va5zaM`OqA#X~L9&mZ-A`b5;g=@+l&@?o?qa872nio*_)F*@!nrBsGr{ zqG`M^b>twQNg}BgUq>aE0In7(o!1Lf9T4?}gvocONb0#0Y2776$6bJ*LOM?ibKTRz zwDeiz_pC7Y!1O&YQbW%Jm+c}Yv;+CIq8zOvxu;bOu(pZhjy9CJO{C=RL>@a~-zkzx zc8LMLT_V}K8#wKT-(F#AYDXIT#DJ`QvbX@EzucEq8`O0uQ)shOyDxo3xgY*QnLcff z_`41tP3i~P#&)WB7i-g|&AHmVQkxaptkdQiZEn=&7Hw|T=Kb1yOq+kwW~(;$Yx6JK zd|R7gZ64F+x7r-=j>0oTn`de>Q=6l;IZ>OJYO_e23$(dZn^$Yoqs{f&Y}DqjwRyKT zAJ%5GHlNkzE^Uf`@jLiE;X=pyZ?`7h&aIi+e|UU2KgN~`dETVW+qBuF&BwH9p!Ek0 zKda5%+T5qjm$hkx$FPz=&wDDLI^E9<|4xl}r^d5Kn>-7bc(u+{X6_7S8tJXo=^5!{ z>2wp*X`ZFxb<9;}$sA=G@r?8~>huhJ4F6nhnLuRl>p0YZ{F@T+Y1Q~N{Y8ae`d4Mv zX_Kp%OcL{$PFkjK;Imbym)NcudVH1~R`EKu*<-*uJeDi-P zvsIf}m{(;wm{()dd;ELSlZE*4^hWXV-5UR13%Wlk2UaP4g6RG|5s{Y z$j?(3OOLqZEha&A)VR-h#Zk)CHH$od!3WfaN3c8Io(a=PQBGTyKrDsMb*i z&hTX}5rT)^?J09yV|P?uV_y!@Ub*nuv~xLRe04^cax$L*l&`KXM-=Ze`?9JUgb<-q zkjUlp7R=7hvfCZ?^^lLP%+8(wJB4>rUSX%qqd@q2%u6$x8cj?-F~S%`c31TiXDj65 z@@Hz=YC`1JB#GYT+9`xPM5p3rmVUOQo5B{!q?tfNULr%wdK|@V-cUD;9D;0mkY8nw#8Dh>1oHQ9g4d)V#uYd9|;3 zphRF^p%!QRsP3VQCtHpl6lwdKbW;{aB;dYqv?^rS(uBl&l2qcQM&jEN5~qhue5+2p zG@3Z|D`F5)U7$6RV`5wXuRDM-h+Xv@IrD2)p{LvDI^EtCPVg&TJ`LGEfa5-U?lqv3072l-IWY{Sc1?Ra1-j35GCb=!V;>3+zM9$1MX3<*CX>k zZa^}oFNv8#gaoQT;Zr&P2YA6x*_cX*bxQob!8q1fNQeH)8jBbq6O-dpjVYWOuz-X90T@9 zz!}N!IALf@I9A9i_9ZK@T2)k|1|fsOjE5%Hu%}igwlxCE3TTRevIe#h^pw@e=4d9c z(j7&yC0S0OrJfv=VntOAbP857i%#h-C!%G}FQ&Nh>PRv78PFoa~j(GEU=*E*Doy zKE63yTxXh~BbHzucTX=YDk#meA0%O zQ;Afo>sE@VOvxg29vIiHeDy$*lvF16ny6eMo&*|IHR26Zy&ZC2w4qCUH5kDsLO*ES zG|FJb4N2q0Q^_%!HU|boW>xWWnlMij+miCd3rX2o;*F$<=v8)#`wOZ{N-BU)vPc?J zfMM2>luhILB&0qCc=)CQk1+)+MF%`qioe3WXDX3|%p|GW<+Y11lH9Ax>QKCLXFVs% zW+VliogQaF$?Q`5c-EQSQy_kuTvg+Cx;*i&4<{p|OORkQUmbk)6@>30Lh$+8qw&7EaT(P}yupNb2kyQwi`FgjeJhxqN!jB41dM>w;{!$V z_oMJ10~1mB!@xc$JTuTNnr~-5o%hI`@0IU|kY}pYP7ybxIz%v)y;d}*>H#NSPUReH z7rm)&@%Bm8wdiP4Z%72w$|2r%)vi{3L)?*ul((@2u3FGpPzkF-=jYIWB(>5Bimp!F zhaf#qVN@qaG%F6ILd)aPleDJSizk_dkILXlcJW54_&QZ2#fa0z7pYRw%qWB2&Kj?d?t&Txvv#$wRntSBC^?uqrACT_{fkbkt zcz@6W@qT)txOuQh885b`$IN{#>Ba~X+tWcLiCyXOwAJDC$Q1Kfx}IWwpKca^-@{^l zE8iP&EV$Wx6AuSZqN?I6>7=#ZPB)vkN}v1W`*G<%UVIgsfA`=>@`1rR`J;nTi|?`i z#*5v9W03uPa0JW3txZWy$)V(> zxw*N%lvY#cNj;{}`8_FhzJWca<^eE5$-XN>qkIEGr)Btt*5zj8mV_)#X}&X?Oif8y zJt?`?thx?UlgVew%E-;g$Ot93nleJBtY%Y2XHv+NQJ2*O*xZ?vQIe&y7wF^SWQ^RU z7ewVPgeeDyA*O5pp4%0?R>L6(^y&W_4I6T*X255gd?KXfY7`k0Zqo4k5=QzOrltY? z4R|0DJsXc9mpfC!z6gAth7EaEj)p0(hgDpyVM7kavksWPArHGn!-ib!Nev4v$Kr+b z44%-@isrJ!FCA48sI)iCuG82>^I=W6(3z$ZgbfjSTLzf8l_eIQ(} zVd_8-UZr8`LJ;1pVd_K>zDL8#A4_&FLj zaf5+6gJK3Btroc&r2 zcg|Mv@074l82TuG(y*bI@~Van{gh)GHuO|ZNo9VRFZBF0o-q2KAqOwiupw`suVF*3 zT}BxF+mNHzXxNbR<4_ryo}riE*Zzh+!5xInK4HkuAJecQpKsByArIdrVd%5Hr^^4b zh7Em%w{>_!U*Hq%zfp((mN4+``CP?637en^Z`0%PbPe}t_?LuD8R&^B{3OB{uZ#3} zohkjH2XL11FVX%b+J6b*!RTLlyp?OXQN!H8E&Jb#D*hG?*J(Y7M>Pz+CpjM4G+d&? z|3$+B3lYalkA|}jC^$7urC*}`hiTaNkn%rI!@1gjiiSfPo^QaXt-0yzfZ$;Eh_wz8gAC*JE-BM^OXM)!lX0=zl__DML3k6*!&^zKf8n-ju6li+_S!T-$!_|pWKI+(HL zACv%}o&cYf0B0t^=Ow^AY$G=P2?_8e3Gj>rcyk1{w4uV##o7MuR#g$ zsR{5I3GgoxVA8uA@!WvNk7qNUEqHFi6T)*do?qkn4W3)@+=}NmJRq0FRy=p$!Bi!n zqABjia}S<-@!W@}3D0lw+>hr0JP+c*v>_hG^B;J&;rSh&NANs~=P^8w<9PxPrW;Oi zVjUhIp6l>jkEa38COp^TDaM0kQOvd5taa+8}S73{3jm#a{qrVvsFOY zQ8bnko|0LPH4;)REXm}sLo2>yR(7`JY2jPE+PMN?Jc|qb7N(Isfq|C0YQ1$z4&-zv z#;hs<@2m78`3Dn>u_j{UP&q{-5mu#2EGLd@;Py~{1q9*A#KA})pWPl|xiV5zB9p+5 zqF?mmHlIv0I_a%|5jZ9YIcl*%3|L7$A~6&Q9w4}Nu~pQEc?XV~!cfH+N2bwbogTq7&h-Z%tPqQfm4I}dUjpuNe*G08EeS1Q;9_UDU^E zTuvXS@qI&$AKxd`gsi?!eIrhoz@{q79?QYUol*)Bk!H&=_`C%u z7@11a9@iETZ!&6Rc4N4&hQroQ2@9RZ68e{(*?nXTh@0JyGy=~33Nhe5TZtF4v&if3 zkFB2=ravZsY?yfFb<`(OA`$hOCM6OvuoP58;b?D_l8}6(FGpp{5k~00ly!$O*jQ@XM#Av{HB-EN3v!dydtd$UmVnmdd$mfpp?8_LB z^X`WM9_Rl(yl{j`)H{$)F*AsvWgy*kpOW)Pp|J&D`b7B55{g!b44R;Qz#xX=fnnD( z(!A2xEJ|auh$f=(Hr|4`kbNbtnEv7@HjZtsN=piO4%*9s5W=lxL!}NV*!r94U4b(n*S;`U-lYe(?ey=}rnniAqL8qK(o(FhfOd-0V}hH~6oI85 zT_(6j^j2U`P*{m`QQBhC2?_+z6jN5IC#F z(PoU|2)dC}fq}{wk&rqfc6?Y!WfVgzgr+1`U`jm@I#2;bnZQKC)k5idM3p@HsLb($ 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 0453d4c..65b8ef6 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -211,7 +211,7 @@ class SupportVectorMachine */ 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 @@ class SupportVectorMachine $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 8d4a5ab..3ec6af1 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 @@ class TokenCountVectorizer implements Transformer */ private function getTokenIndex(string $token) { + if ($this->isStopWord($token)) { + return false; + } + return isset($this->vocabulary[$token]) ? $this->vocabulary[$token] : false; } @@ -126,11 +138,25 @@ class TokenCountVectorizer implements Transformer */ 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 3a5f7fe..b18db60 100644 --- a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php +++ b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php @@ -4,12 +4,13 @@ declare (strict_types = 1); 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 @@ class TokenCountVectorizerTest extends \PHPUnit_Framework_TestCase $this->assertEquals($tokensCounts, $samples); } - public function testMinimumDocumentTokenCountFrequency() + public function testTransformationWithMinimumDocumentTokenCountFrequency() { // word at least in half samples $samples = [ @@ -70,7 +71,7 @@ class TokenCountVectorizerTest extends \PHPUnit_Framework_TestCase [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 @@ class TokenCountVectorizerTest extends \PHPUnit_Framework_TestCase [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 0000000..6aeaa87 --- /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 0000000..d133ae2 --- /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 1e222d2..55629bd 100644 --- a/src/Phpml/FeatureExtraction/TfIdfTransformer.php +++ b/src/Phpml/FeatureExtraction/TfIdfTransformer.php @@ -32,7 +32,7 @@ class TfIdfTransformer implements Transformer $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 7647997..fa62dde 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -78,7 +78,7 @@ class Normalizer implements Preprocessor 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 ea7c1c8..96ac55f 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 594438b..42d8374 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 96ac55f..0219f4b 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 42d8374..594438b 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 0219f4b..535a3c2 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 535a3c2..93d162b 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 93d162b..6e665ef 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 6e665ef..925d8e4 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 925d8e4..e3b92fc 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 e3b92fc..ea7c1c8 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 92de976..c1e709e 100644 --- a/src/Phpml/CrossValidation/RandomSplit.php +++ b/src/Phpml/CrossValidation/RandomSplit.php @@ -5,101 +5,26 @@ declare (strict_types = 1); 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 0000000..3478f54 --- /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 0000000..10af303 --- /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 0000000..14802de --- /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 c10cb7b..1f7d97c 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 db3c32b..7943c38 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 464f0db..edfdded 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 0000000..d3f53be --- /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 68e8b97..dcf5071 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 0000000..5c80050 --- /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 7943c38..dd444a3 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 1f7d97c..24c1d53 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 dd444a3..e1cde6e 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 0000000..34465eb --- /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 dcf5071..4c9fbf6 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 5c80050..81ca897 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 24c1d53..9602e4d 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 e1cde6e..2d5589f 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 0000000..b07443a --- /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 4c9fbf6..71ad898 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 81ca897..bc66065 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 9602e4d..61d215a 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 2d5589f..c3088e3 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 0000000..c592b8d --- /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 71ad898..2634101 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 eceaeb3..116b608 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 3ec6af1..56d63e2 100644 --- a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php +++ b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php @@ -116,6 +116,8 @@ class TokenCountVectorizer implements Transformer } } + ksort($counts); + $sample = $counts; } diff --git a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php index b18db60..22ff1a9 100644 --- a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php +++ b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php @@ -40,10 +40,10 @@ class TokenCountVectorizerTest extends \PHPUnit_Framework_TestCase $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 @@ class TokenCountVectorizerTest extends \PHPUnit_Framework_TestCase $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 @@ class TokenCountVectorizerTest extends \PHPUnit_Framework_TestCase $vectorizer->fit($samples); $vectorizer->transform($samples); - $this->assertEquals($tokensCounts, $samples); + $this->assertSame($tokensCounts, $samples); } public function testTransformationWithStopWords() @@ -131,9 +131,9 @@ class TokenCountVectorizerTest extends \PHPUnit_Framework_TestCase $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 bc66065..6e70a3e 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 23ac35c..b552d47 100644 --- a/src/Phpml/Dataset/CsvDataset.php +++ b/src/Phpml/Dataset/CsvDataset.php @@ -8,11 +8,6 @@ use Phpml\Exception\DatasetException; 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 0000000..f28e09b --- /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 7b99e64..813166b 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 0000000..5a5e7e9 --- /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 0000000..f4e2242 --- /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 0000000..0aa9c6f --- /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 0000000..dd69655 --- /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 0000000..f03a2c1 --- /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 0000000..ac7bf0b --- /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 0000000..fa78492 --- /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 0000000..4147eb0 --- /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 0000000..6657036 --- /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 0000000..7345c8d --- /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 0000000..078b6e9 --- /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 0000000..aa8cee0 --- /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 0000000..b79825f --- /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 0000000..92bb95a --- /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 0000000..8a4b657 --- /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 0000000..e7bc04e --- /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 0000000..03dd264 --- /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 0000000..64efbe1 --- /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 0000000..c1dcf1b --- /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 0000000..7862fc2 --- /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 0000000..037c155 --- /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 0000000..285893a --- /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 0000000..5468695 --- /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 0000000..d8e1bce --- /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 0000000..e192dc5 --- /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 0000000..17748d8 --- /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 0000000..9dc8640 --- /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 0000000..e17e192 --- /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 0000000..8da6426 --- /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 0000000..ef07bba --- /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 0000000..8bcedaa --- /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 0000000..0233bf6 --- /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 0000000..0102893 --- /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 0000000..9dcc752 --- /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 0000000..59195b6 --- /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 0000000..f3d9dd4 --- /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 0000000..98d645f --- /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 0000000..1047180 --- /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 0000000..b2b47ae --- /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 0000000..5123235 --- /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 0000000..b9fd654 --- /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 0000000..acb7e7f --- /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 0000000..4c5decd --- /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 0000000..dbab557 --- /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 0000000..950dd0e --- /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 0000000..bd1caf7 --- /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 0000000..4a3d70e --- /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 0000000..1c9b89b --- /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 0000000..31359e3 --- /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 0000000..3af3f25 --- /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 0000000..3e4bd43 --- /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 61d215a..5b14f0b 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 c3088e3..938d73f 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 0000000..969610c --- /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 2634101..f06a08b 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 f28e09b..6897ba1 100644 --- a/src/Phpml/Dataset/FilesDataset.php +++ b/src/Phpml/Dataset/FilesDataset.php @@ -1,5 +1,6 @@ scanDir($dir); } } @@ -38,10 +39,9 @@ class FilesDataset extends ArrayDataset { $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 5a5e7e9..5461c2f 100644 --- a/tests/Phpml/Dataset/FilesDatasetTest.php +++ b/tests/Phpml/Dataset/FilesDatasetTest.php @@ -31,8 +31,13 @@ class FilesDatasetTest extends \PHPUnit_Framework_TestCase $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 6e70a3e..6a9a928 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 5b14f0b..a3f01ce 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 938d73f..d58f03e 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 0000000..69209fd --- /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 0000000..58520be --- /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 69209fd..31ee0b7 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 @@ class ClassificationReport 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 @@ class ClassificationReport */ 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 @@ class ClassificationReport return $labels; } - } diff --git a/tests/Phpml/Metric/ClassificationReportTest.php b/tests/Phpml/Metric/ClassificationReportTest.php index 58520be..2f7bc1a 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 2f7bc1a..f0f1cd3 100644 --- a/tests/Phpml/Metric/ClassificationReportTest.php +++ b/tests/Phpml/Metric/ClassificationReportTest.php @@ -27,4 +27,24 @@ class ClassificationReportTest extends \PHPUnit_Framework_TestCase $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 6a9a928..7402100 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 a3f01ce..29394a6 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 d58f03e..757f278 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 0000000..53490b2 --- /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 f06a08b..057a1a1 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 29394a6..0ac39b1 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 757f278..5aaf8ea 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 0ac39b1..e34a69c 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 5aaf8ea..38eca65 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 73854f2..8a409f4 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 f0e21c9..d33881f 100644 --- a/tests/Phpml/Preprocessing/NormalizerTest.php +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -79,4 +79,24 @@ class NormalizerTest extends \PHPUnit_Framework_TestCase $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 0000000..a24c0e8 --- /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 7402100..bef3451 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 31abfeb..d1b689a 100644 --- a/src/Phpml/Exception/NormalizerException.php +++ b/src/Phpml/Exception/NormalizerException.php @@ -13,12 +13,5 @@ class NormalizerException extends \Exception { 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 4e5815b..108a24c 100644 --- a/tests/Phpml/PipelineTest.php +++ b/tests/Phpml/PipelineTest.php @@ -10,6 +10,7 @@ use Phpml\Pipeline; 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 @@ class PipelineTest extends \PHPUnit_Framework_TestCase $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 d1b689a..9f88f0c 100644 --- a/src/Phpml/Exception/NormalizerException.php +++ b/src/Phpml/Exception/NormalizerException.php @@ -13,5 +13,4 @@ class NormalizerException extends \Exception { return new self('Unknown norm supplied.'); } - } diff --git a/tests/Phpml/Clustering/KMeansTest.php b/tests/Phpml/Clustering/KMeansTest.php index 5a85b38..79b5ce7 100644 --- a/tests/Phpml/Clustering/KMeansTest.php +++ b/tests/Phpml/Clustering/KMeansTest.php @@ -48,4 +48,12 @@ class KMeansTest extends \PHPUnit_Framework_TestCase $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 aba0ff2..05ca5b5 100644 --- a/tests/Phpml/Math/ProductTest.php +++ b/tests/Phpml/Math/ProductTest.php @@ -13,5 +13,8 @@ class ProductTest extends \PHPUnit_Framework_TestCase $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 70accb9..678dd71 100644 --- a/src/Phpml/Math/Product.php +++ b/src/Phpml/Math/Product.php @@ -16,7 +16,9 @@ class Product { $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 0000000..8084dc8 --- /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 e34a69c..07ce099 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 38eca65..6b0ce8c 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 0000000..9b7d984 --- /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 0000000..cdbe4ae --- /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 0000000..ee7b7be --- /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 0000000..52b38e7 --- /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 0000000..c074955 --- /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 0000000..4780a53 --- /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 0000000..92f4b97 --- /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 0000000..c84a20b --- /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 2904e2f..89a0d09 100644 --- a/src/Phpml/Clustering/KMeans/Space.php +++ b/src/Phpml/Clustering/KMeans/Space.php @@ -61,7 +61,7 @@ class Space extends SplObjectStorage */ public function addPoint(array $coordinates, $data = null) { - return $this->attach($this->newPoint($coordinates), $data); + $this->attach($this->newPoint($coordinates), $data); } /** @@ -74,7 +74,7 @@ class Space extends SplObjectStorage throw new InvalidArgumentException('can only attach points to spaces'); } - return parent::attach($point, $data); + parent::attach($point, $data); } /** @@ -230,8 +230,8 @@ class Space extends SplObjectStorage 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 208b10d..808472c 100644 --- a/src/Phpml/Math/Matrix.php +++ b/src/Phpml/Math/Matrix.php @@ -193,7 +193,8 @@ class 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 0000000..77e0c00 --- /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 0000000..923c4ff --- /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 0000000..526041b --- /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 0000000..41fc937 --- /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 798532d..d280296 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -73,4 +73,12 @@ class InvalidArgumentException extends \Exception { 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 0000000..6700164 --- /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 0000000..f19dcb6 --- /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 8d2fb4a..677831e 100644 --- a/src/Phpml/NeuralNetwork/Node/Neuron.php +++ b/src/Phpml/NeuralNetwork/Node/Neuron.php @@ -5,6 +5,7 @@ declare (strict_types = 1); 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 923c4ff..3813d71 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 0000000..5706ab4 --- /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 0000000..c0ece3f --- /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 0000000..b0abdcc --- /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 41fc937..9ad733d 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 526041b..c416ffd 100644 --- a/tests/Phpml/NeuralNetwork/Node/NeuronTest.php +++ b/tests/Phpml/NeuralNetwork/Node/NeuronTest.php @@ -6,7 +6,7 @@ namespace tests\Phpml\NeuralNetwork\Node; 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 @@ class NeuronTest extends \PHPUnit_Framework_TestCase /** * @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 0000000..be59b34 --- /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 | 49 +++++++++++++++---- .../Network/MultilayerPerceptron.php | 1 - src/Phpml/NeuralNetwork/Training.php | 2 +- .../Training/Backpropagation.php | 5 +- 5 files changed, 52 insertions(+), 19 deletions(-) diff --git a/src/Phpml/NeuralNetwork/Network.php b/src/Phpml/NeuralNetwork/Network.php index be59b34..269351f 100644 --- a/src/Phpml/NeuralNetwork/Network.php +++ b/src/Phpml/NeuralNetwork/Network.php @@ -4,9 +4,8 @@ declare (strict_types = 1); namespace Phpml\NeuralNetwork; -interface Network extends Node +interface Network { - /** * @param mixed $input */ @@ -15,6 +14,15 @@ interface Network extends Node /** * @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 a46b267..699c4d4 100644 --- a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php +++ b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php @@ -4,25 +4,51 @@ declare (strict_types = 1); namespace Phpml\NeuralNetwork\Network; +use Phpml\NeuralNetwork\Layer; use Phpml\NeuralNetwork\Network; abstract class LayeredNetwork implements Network { + /** + * @var Layer[] + */ + protected $layers; + + /** + * @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 array */ - public function getLayers(): array - { - - } - - /** - * @return float - */ - public function getOutput(): float + public function getOutput(): array { + $result = []; + foreach ($this->getOutputLayer()->getNodes() as $neuron) { + $result[] = $neuron->getOutput(); + } + return $result; } /** @@ -30,7 +56,10 @@ abstract class LayeredNetwork implements Network */ 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 ce5a615..c0f7df3 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -6,5 +6,4 @@ namespace Phpml\NeuralNetwork\Network; class MultilayerPerceptron extends LayeredNetwork { - } diff --git a/src/Phpml/NeuralNetwork/Training.php b/src/Phpml/NeuralNetwork/Training.php index 9870c11..932f189 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 17ca44c..ce9f5e6 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -8,16 +8,13 @@ use Phpml\NeuralNetwork\Training; 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 0000000..11fe914 --- /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 d280296..86cfd86 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -81,4 +81,12 @@ class InvalidArgumentException extends \Exception { 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 c0f7df3..4079822 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -4,6 +4,89 @@ declare (strict_types = 1); 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 0000000..1ac1621 --- /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 269351f..a03b8b6 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 699c4d4..4413403 100644 --- a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php +++ b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php @@ -6,6 +6,8 @@ namespace Phpml\NeuralNetwork\Network; 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 @@ abstract class LayeredNetwork implements Network /** * @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 ce9f5e6..e6691e2 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -4,10 +4,33 @@ declare (strict_types = 1); 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 0000000..8ce397b --- /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 0000000..a44c1d5 --- /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 e6691e2..3f80bf5 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 @@ class Backpropagation implements Training 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 @@ class Backpropagation implements Training } /** - * @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 8ce397b..8520354 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php @@ -43,4 +43,22 @@ class Sigma { 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 a44c1d5..265d936 100644 --- a/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php +++ b/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php @@ -18,7 +18,7 @@ class BackpropagationTest extends \PHPUnit_Framework_TestCase [[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 6700164..b94da21 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 4079822..e97e045 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -5,6 +5,7 @@ declare (strict_types = 1); 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 @@ use Phpml\NeuralNetwork\Node\Neuron\Synapse; 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 @@ class MultilayerPerceptron extends LayeredNetwork } /** - * @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 0000000..b00b4d1 --- /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 b00b4d1..9a84214 100644 --- a/src/Phpml/Regression/MLPRegressor.php +++ b/src/Phpml/Regression/MLPRegressor.php @@ -4,7 +4,6 @@ declare (strict_types = 1); 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 @@ class MLPRegressor implements Regression $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 @@ class MLPRegressor implements Regression { 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 bef3451..3b636d2 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 07ce099..ea1ff4f 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 041f818..eeccc53 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 6b0ce8c..1a38642 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 3d5aa27..2de597c 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 0000000..8c9b560 --- /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 0000000..c1c0eef --- /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 057a1a1..4fa6c21 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 ea1ff4f..93574fc 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 265d936..32deb64 100644 --- a/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php +++ b/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php @@ -17,7 +17,7 @@ class BackpropagationTest extends \PHPUnit_Framework_TestCase $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 8a409f4..e85e1fd 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 0000000..c5986f4 --- /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 0000000..bf52c27 --- /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 0000000..c339b5e --- /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 e85e1fd..8a409f4 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 c5986f4..544406e 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 bf52c27..4855691 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 @@ class Apriori implements Associator $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 @@ class Apriori implements Associator * * @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 @@ class Apriori implements Associator * * @return mixed[][] */ - private function powerSet(array $sample) + private function powerSet(array $sample) : array { $results = [[]]; foreach ($sample as $item) { @@ -154,12 +174,12 @@ class Apriori implements Associator * * @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 @@ class Apriori implements Associator * * @return mixed[][] */ - private function items() + private function items() : array { $items = []; @@ -181,7 +201,7 @@ class Apriori implements Associator } } - return array_map(function($entry) { + return array_map(function ($entry) { return [$entry]; }, $items); } @@ -193,9 +213,9 @@ class Apriori implements Associator * * @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 @@ class Apriori implements Associator * * @return mixed[][] */ - private function candidates(array $samples) + private function candidates(array $samples) : array { $candidates = []; @@ -223,7 +243,7 @@ class Apriori implements Associator 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 @@ class Apriori implements Associator * * @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 @@ class Apriori implements Associator * * @return float */ - private function support(array $sample) + private function support(array $sample) : float { return $this->frequency($sample) / count($this->samples); } @@ -273,9 +293,9 @@ class Apriori implements Associator * * @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 @@ class Apriori implements Associator * * @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 @@ class Apriori implements Associator * * @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 @@ class Apriori implements Associator * * @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 9cc595d..b249ff6 100644 --- a/tests/Phpml/Association/AprioriTest.php +++ b/tests/Phpml/Association/AprioriTest.php @@ -71,12 +71,12 @@ class AprioriTest extends \PHPUnit_Framework_TestCase $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 0000000..fa016ed --- /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 0000000..20fc780 --- /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 0000000..cdf4460 --- /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 93574fc..31fdb60 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 1a38642..423877f 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 | 342 +++++++++++++----- .../NeuralNetwork/Node/Neuron/SynapseTest.php | 4 +- tests/Phpml/NeuralNetwork/Node/NeuronTest.php | 4 +- 4 files changed, 257 insertions(+), 95 deletions(-) diff --git a/composer.json b/composer.json index eeccc53..9784154 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 bf1fd1f..27f1829 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,90 @@ "object", "object graph" ], - "time": "2015-11-20 12:04:31" + "time": "2016-09-16 13:37:59" }, { - "name": "phpdocumentor/reflection-docblock", - "version": "2.0.4", + "name": "phpdocumentor/reflection-common", + "version": "1.0", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", - "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=5.5" }, "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "suggest": { - "dflydev/markdown": "~1.0", - "erusev/parsedown": "~1.0" + "phpunit/phpunit": "^4.6" }, "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" + ] + } + }, + "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": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "9270140b940ff02e58ec577c237274e92cd40cdd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9270140b940ff02e58ec577c237274e92cd40cdd", + "reference": "9270140b940ff02e58ec577c237274e92cd40cdd", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0@dev", + "phpdocumentor/type-resolver": "^0.2.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ "src/" ] } @@ -148,39 +197,87 @@ "authors": [ { "name": "Mike van Riel", - "email": "mike.vanriel@naenius.com" + "email": "me@mikevanriel.com" } ], - "time": "2015-02-03 12:10:50" + "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": "phpspec/prophecy", - "version": "v1.6.0", + "name": "phpdocumentor/type-resolver", + "version": "0.2", "source": { "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972" + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3c91bdf81797d725b14cb62906f9a4ce44235972", - "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972", + "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": "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": "2016-06-10 07:14:17" + }, + { + "name": "phpspec/prophecy", + "version": "v1.6.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "58a8137754bc24b25740d4281399a4a3596058e0" + }, + "dist": { + "type": "zip", + "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 9ad733d..82560f1 100644 --- a/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php +++ b/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php @@ -44,8 +44,8 @@ class SynapseTest extends \PHPUnit_Framework_TestCase */ 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 c416ffd..13bf6c5 100644 --- a/tests/Phpml/NeuralNetwork/Node/NeuronTest.php +++ b/tests/Phpml/NeuralNetwork/Node/NeuronTest.php @@ -20,7 +20,7 @@ class NeuronTest extends \PHPUnit_Framework_TestCase 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 @@ class NeuronTest extends \PHPUnit_Framework_TestCase */ 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 4855691..bec1510 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 31ee0b7..b1b135f 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 @@ class ClassificationReport 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 @@ class ClassificationReport } } + /** + * @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 @@ class ClassificationReport } /** - * @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 f0f1cd3..6ccc21d 100644 --- a/tests/Phpml/Metric/ClassificationReportTest.php +++ b/tests/Phpml/Metric/ClassificationReportTest.php @@ -47,4 +47,24 @@ class ClassificationReportTest extends \PHPUnit_Framework_TestCase $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 2a3d7e2..aa591f3 100644 --- a/src/Phpml/Dataset/Demo/Glass.php +++ b/src/Phpml/Dataset/Demo/GlassDataset.php @@ -18,7 +18,7 @@ use Phpml\Dataset\CsvDataset; * 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 923f0ba..509d9f4 100644 --- a/src/Phpml/Dataset/Demo/Iris.php +++ b/src/Phpml/Dataset/Demo/IrisDataset.php @@ -12,7 +12,7 @@ use Phpml\Dataset\CsvDataset; * 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 3bc71a9..90ddfe4 100644 --- a/src/Phpml/Dataset/Demo/Wine.php +++ b/src/Phpml/Dataset/Demo/WineDataset.php @@ -12,7 +12,7 @@ use Phpml\Dataset\CsvDataset; * 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 d4f1313..7ce3d37 100644 --- a/tests/Phpml/Dataset/Demo/GlassTest.php +++ b/tests/Phpml/Dataset/Demo/GlassDatasetTest.php @@ -4,13 +4,13 @@ declare (strict_types = 1); 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 354329e..8e27db0 100644 --- a/tests/Phpml/Dataset/Demo/IrisTest.php +++ b/tests/Phpml/Dataset/Demo/IrisDatasetTest.php @@ -4,13 +4,13 @@ declare (strict_types = 1); 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 34c93fa..cf6c53d 100644 --- a/tests/Phpml/Dataset/Demo/WineTest.php +++ b/tests/Phpml/Dataset/Demo/WineDatasetTest.php @@ -4,13 +4,13 @@ declare (strict_types = 1); 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 6f28d94..436cb31 100644 --- a/tests/Phpml/Metric/AccuracyTest.php +++ b/tests/Phpml/Metric/AccuracyTest.php @@ -6,7 +6,7 @@ namespace tests\Phpml\Metric; 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 @@ class AccuracyTest extends \PHPUnit_Framework_TestCase 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 31fdb60..bf33f90 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 3b636d2..5c8f2ae 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 5081ed8..de49556 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 b1b135f..f56fb2a 100644 --- a/src/Phpml/Metric/ClassificationReport.php +++ b/src/Phpml/Metric/ClassificationReport.php @@ -113,6 +113,10 @@ class ClassificationReport { 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 6ccc21d..515f97c 100644 --- a/tests/Phpml/Metric/ClassificationReportTest.php +++ b/tests/Phpml/Metric/ClassificationReportTest.php @@ -67,4 +67,19 @@ class ClassificationReportTest extends \PHPUnit_Framework_TestCase $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 5c8f2ae..781d5fd 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 bec1510..4855691 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 89a0d09..40b1f1d 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 6aeaa87..664e355 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 77e0c00..65d5cdc 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 012bb79..805d3f6 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 @@ class Imputer implements Preprocessor /** * @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 2cf1144..9125e06 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 d133ae2..1b2782b 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 30f633f..dac5066 100644 --- a/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php +++ b/tests/Phpml/NeuralNetwork/Training/BackpropagationTest.php @@ -18,7 +18,7 @@ class BackpropagationTest extends \PHPUnit_Framework_TestCase [[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 377f84c..ab9a2b7 100644 --- a/src/Phpml/Dataset/CsvDataset.php +++ b/src/Phpml/Dataset/CsvDataset.php @@ -21,7 +21,7 @@ class CsvDataset extends ArrayDataset 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 cb13a7c..6ed0660 100644 --- a/src/Phpml/Math/Distance/Minkowski.php +++ b/src/Phpml/Math/Distance/Minkowski.php @@ -43,6 +43,6 @@ class Minkowski implements Distance $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 2bef22f..cf5ce3b 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -132,7 +132,7 @@ class SupportVectorMachine { $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 @@ class SupportVectorMachine 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 2360560..da1f25f 100644 --- a/src/Phpml/FeatureExtraction/TfIdfTransformer.php +++ b/src/Phpml/FeatureExtraction/TfIdfTransformer.php @@ -32,7 +32,7 @@ class TfIdfTransformer implements Transformer $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 591ca72..22ba555 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -78,7 +78,7 @@ class Normalizer implements Preprocessor 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 4371a7d..993af85 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 6ed0660..0788193 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 07c43fe..581a122 100644 --- a/src/Phpml/Math/Statistic/Mean.php +++ b/src/Phpml/Math/Statistic/Mean.php @@ -34,11 +34,11 @@ class Mean 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 993af85..226cb55 100644 --- a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php +++ b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php @@ -132,7 +132,7 @@ class TokenCountVectorizer implements Transformer 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 3499590..c7cc147 100644 --- a/src/Phpml/Metric/ClassificationReport.php +++ b/src/Phpml/Metric/ClassificationReport.php @@ -163,7 +163,7 @@ class ClassificationReport 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 8a409f4..73f321f 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 73f321f..79b5610 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 dbf66e4..6eb37cb 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 78c57cb..e32eeb0 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 da1f25f..9335775 100644 --- a/src/Phpml/FeatureExtraction/TfIdfTransformer.php +++ b/src/Phpml/FeatureExtraction/TfIdfTransformer.php @@ -43,15 +43,13 @@ class TfIdfTransformer implements Transformer { 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 226cb55..f5fab21 100644 --- a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php +++ b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php @@ -173,8 +173,6 @@ class TokenCountVectorizer implements Transformer /** * @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 cc1767c..0f60223 100644 --- a/src/Phpml/Math/Statistic/Correlation.php +++ b/src/Phpml/Math/Statistic/Correlation.php @@ -33,9 +33,9 @@ class Correlation 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 0e9dc03..3dfcb34 100644 --- a/src/Phpml/Metric/Accuracy.php +++ b/src/Phpml/Metric/Accuracy.php @@ -31,7 +31,7 @@ class Accuracy } 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 22ba555..5cff6e8 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -64,7 +64,7 @@ class Normalizer implements Preprocessor $sample = array_fill(0, $count, 1.0 / $count); } else { foreach ($sample as &$feature) { - $feature = $feature / $norm1; + $feature /= $norm1; } } } @@ -84,7 +84,7 @@ class Normalizer implements Preprocessor $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 6f8eda8..8f7d56d 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 6a3a422..0bc96d8 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 f522e01..c65b08c 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 cf5ce3b..55a5305 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -118,7 +118,7 @@ class SupportVectorMachine $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 4d33310..85f911f 100644 --- a/src/Phpml/Exception/DatasetException.php +++ b/src/Phpml/Exception/DatasetException.php @@ -7,22 +7,31 @@ namespace Phpml\Exception; 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 b02da53..42063f1 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -67,6 +67,8 @@ class InvalidArgumentException extends \Exception } /** + * @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 c160b6c..28fa8af 100644 --- a/src/Phpml/Clustering/KMeans/Cluster.php +++ b/src/Phpml/Clustering/KMeans/Cluster.php @@ -60,6 +60,8 @@ class Cluster extends Point implements IteratorAggregate, Countable * @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 a8d72e5..ce1c44e 100644 --- a/src/Phpml/Clustering/KMeans/Point.php +++ b/src/Phpml/Clustering/KMeans/Point.php @@ -53,11 +53,11 @@ class Point implements ArrayAccess } /** - * @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 40b1f1d..49ded31 100644 --- a/src/Phpml/Clustering/KMeans/Space.php +++ b/src/Phpml/Clustering/KMeans/Space.php @@ -65,7 +65,7 @@ class Space extends SplObjectStorage } /** - * @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 49ded31..c51cc05 100644 --- a/src/Phpml/Clustering/KMeans/Space.php +++ b/src/Phpml/Clustering/KMeans/Space.php @@ -118,7 +118,7 @@ class Space extends SplObjectStorage $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 @@ class Space extends SplObjectStorage $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 0fb54b1..b9c036f 100644 --- a/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php +++ b/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php @@ -33,7 +33,7 @@ class Synapse */ 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 ea7c1c8..94f56ed 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 13a255a..ef54002 100644 --- a/tests/Phpml/Metric/AccuracyTest.php +++ b/tests/Phpml/Metric/AccuracyTest.php @@ -50,6 +50,8 @@ class AccuracyTest extends \PHPUnit_Framework_TestCase $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 aa4d9c3..bd954be 100644 --- a/tests/Phpml/Clustering/KMeansTest.php +++ b/tests/Phpml/Clustering/KMeansTest.php @@ -15,14 +15,14 @@ class KMeansTest extends \PHPUnit_Framework_TestCase $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 @@ class KMeansTest extends \PHPUnit_Framework_TestCase $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 74bfcd7..d6f4681 100644 --- a/tests/Phpml/CrossValidation/RandomSplitTest.php +++ b/tests/Phpml/CrossValidation/RandomSplitTest.php @@ -34,13 +34,13 @@ class RandomSplitTest extends \PHPUnit_Framework_TestCase $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 5a3546c..44e745a 100644 --- a/tests/Phpml/Dataset/CsvDatasetTest.php +++ b/tests/Phpml/Dataset/CsvDatasetTest.php @@ -22,8 +22,8 @@ class CsvDatasetTest extends \PHPUnit_Framework_TestCase $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 @@ class CsvDatasetTest extends \PHPUnit_Framework_TestCase $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 b646c72..9755b5d 100644 --- a/tests/Phpml/Dataset/Demo/GlassDatasetTest.php +++ b/tests/Phpml/Dataset/Demo/GlassDatasetTest.php @@ -13,10 +13,10 @@ class GlassDatasetTest extends \PHPUnit_Framework_TestCase $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 5bc4559..3ee67d0 100644 --- a/tests/Phpml/Dataset/Demo/IrisDatasetTest.php +++ b/tests/Phpml/Dataset/Demo/IrisDatasetTest.php @@ -13,10 +13,10 @@ class IrisDatasetTest extends \PHPUnit_Framework_TestCase $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 ee79d5b..dbac4e0 100644 --- a/tests/Phpml/Dataset/Demo/WineDatasetTest.php +++ b/tests/Phpml/Dataset/Demo/WineDatasetTest.php @@ -13,10 +13,10 @@ class WineDatasetTest extends \PHPUnit_Framework_TestCase $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 f6e9555..49f0e20 100644 --- a/tests/Phpml/Dataset/FilesDatasetTest.php +++ b/tests/Phpml/Dataset/FilesDatasetTest.php @@ -22,8 +22,8 @@ class FilesDatasetTest extends \PHPUnit_Framework_TestCase $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 cdf4460..5426764 100644 --- a/tests/Phpml/Math/SetTest.php +++ b/tests/Phpml/Math/SetTest.php @@ -39,7 +39,7 @@ class SetTest extends \PHPUnit_Framework_TestCase $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 @@ class SetTest extends \PHPUnit_Framework_TestCase $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 2de597c..6e70c61 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 45dd631..c82a195 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 0ea6319..d2efaaa 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 969610c..f050cfd 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 4fa6c21..b404e28 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 | 149 +++++++++++++++++++++--- 1 file changed, 135 insertions(+), 14 deletions(-) diff --git a/src/Phpml/Classification/NaiveBayes.php b/src/Phpml/Classification/NaiveBayes.php index 41441be..17b0fbe 100644 --- a/src/Phpml/Classification/NaiveBayes.php +++ b/src/Phpml/Classification/NaiveBayes.php @@ -6,31 +6,152 @@ namespace Phpml\Classification; 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); } - - arsort($predictions, SORT_NUMERIC); - reset($predictions); - - return key($predictions); + $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); + } + 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 17b0fbe..1f89794 100644 --- a/src/Phpml/Classification/NaiveBayes.php +++ b/src/Phpml/Classification/NaiveBayes.php @@ -12,24 +12,62 @@ 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(); + + /** + * @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 @@ class NaiveBayes implements Classifier }, $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 @@ class NaiveBayes implements Classifier /** * 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 @@ class NaiveBayes implements Classifier $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 28fa8af..5436ee9 100644 --- a/src/Phpml/Clustering/KMeans/Cluster.php +++ b/src/Phpml/Clustering/KMeans/Cluster.php @@ -136,4 +136,12 @@ class Cluster extends Point implements IteratorAggregate, Countable { 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 0000000..033b22b --- /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 0000000..220f876 --- /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 1f89794..e86a91c 100644 --- a/src/Phpml/Classification/NaiveBayes.php +++ b/src/Phpml/Classification/NaiveBayes.php @@ -68,8 +68,8 @@ class NaiveBayes implements Classifier $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 @@ class NaiveBayes implements Classifier */ 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); + $predictions[$label] = $p; } - if (! $isArray) { - return $samplePredictions[0]; - } - 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 0000000..ed4fd9e --- /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 0000000..c6f307d --- /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 0000000..16d4a97 --- /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 0000000..417cafa --- /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 4855691..362f25a 100644 --- a/src/Phpml/Association/Apriori.php +++ b/src/Phpml/Association/Apriori.php @@ -160,7 +160,7 @@ class Apriori implements Associator $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 033b22b..45b6329 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 @@ class DecisionTree implements Classifier { // 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 220f876..d428919 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 @@ class NaiveBayes implements Classifier // 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 ed4fd9e..424f2f1 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 @@ class Cluster extends Point implements IteratorAggregate, Countable 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 c51cc05..5a4d530 100644 --- a/src/Phpml/Clustering/KMeans/Space.php +++ b/src/Phpml/Clustering/KMeans/Space.php @@ -104,7 +104,7 @@ class Space extends SplObjectStorage } } - return array($min, $max); + return [$min, $max]; } /** diff --git a/src/Phpml/Math/Matrix.php b/src/Phpml/Math/Matrix.php index 4e70305..6485a7d 100644 --- a/src/Phpml/Math/Matrix.php +++ b/src/Phpml/Math/Matrix.php @@ -212,7 +212,7 @@ class 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 @@ class Matrix 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 57ef5de..4563108 100644 --- a/tests/Phpml/Association/AprioriTest.php +++ b/tests/Phpml/Association/AprioriTest.php @@ -176,7 +176,7 @@ class AprioriTest extends \PHPUnit_Framework_TestCase * * @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 16d4a97..56d3c63 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 @@ class FuzzyCMeansTest extends \PHPUnit_Framework_TestCase $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 5426764..c6548c7 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 8084dc8..d9dcaff 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 544406e..e6685af 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 6e70c61..a4eb96c 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 e990321..410fd45 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 d502dac..62da509 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 8c9b560..0582351 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 4a00bcd..84a3279 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 ed2d10f..ba6bd74 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 45b6329..1a39cbe 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -64,12 +64,13 @@ class DecisionTree implements Classifier */ 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 2596ada..af81b00 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 e58a1da..3d011ac 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 19609fb..1b664ed 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 c6f307d..25fb94c 100644 --- a/tests/Phpml/Classification/DecisionTreeTest.php +++ b/tests/Phpml/Classification/DecisionTreeTest.php @@ -8,7 +8,7 @@ use Phpml\Classification\DecisionTree; 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 f2edb02..1a0aa1f 100644 --- a/tests/Phpml/Classification/NaiveBayesTest.php +++ b/tests/Phpml/Classification/NaiveBayesTest.php @@ -34,5 +34,15 @@ class NaiveBayesTest extends \PHPUnit_Framework_TestCase $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 423877f..156acb2 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 0000000..626ae42 --- /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 b404e28..433cc3e 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 ab9a2b7..dd722d4 100644 --- a/src/Phpml/Dataset/CsvDataset.php +++ b/src/Phpml/Dataset/CsvDataset.php @@ -4,7 +4,7 @@ declare(strict_types=1); 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 85f911f..6092053 100644 --- a/src/Phpml/Exception/DatasetException.php +++ b/src/Phpml/Exception/DatasetException.php @@ -6,15 +6,6 @@ namespace Phpml\Exception; 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 @@ class DatasetException extends \Exception { 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 0000000..558ae48 --- /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 25fb94c..7ec25ba 100644 --- a/tests/Phpml/Classification/DecisionTreeTest.php +++ b/tests/Phpml/Classification/DecisionTreeTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace tests\Classification; use Phpml\Classification\DecisionTree; +use Phpml\ModelManager; class DecisionTreeTest extends \PHPUnit_Framework_TestCase { @@ -55,6 +56,26 @@ class DecisionTreeTest extends \PHPUnit_Framework_TestCase 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 7c5a75a..9824e5d 100644 --- a/tests/Phpml/Classification/KNearestNeighborsTest.php +++ b/tests/Phpml/Classification/KNearestNeighborsTest.php @@ -6,6 +6,7 @@ namespace tests\Classification; use Phpml\Classification\KNearestNeighbors; use Phpml\Math\Distance\Chebyshev; +use Phpml\ModelManager; class KNearestNeighborsTest extends \PHPUnit_Framework_TestCase { @@ -57,4 +58,27 @@ class KNearestNeighborsTest extends \PHPUnit_Framework_TestCase $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 1a0aa1f..1f66ec8 100644 --- a/tests/Phpml/Classification/NaiveBayesTest.php +++ b/tests/Phpml/Classification/NaiveBayesTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace tests\Classification; use Phpml\Classification\NaiveBayes; +use Phpml\ModelManager; class NaiveBayesTest extends \PHPUnit_Framework_TestCase { @@ -45,4 +46,27 @@ class NaiveBayesTest extends \PHPUnit_Framework_TestCase $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 8b17b53..34111b4 100644 --- a/tests/Phpml/Classification/SVCTest.php +++ b/tests/Phpml/Classification/SVCTest.php @@ -6,6 +6,7 @@ namespace tests\Classification; use Phpml\Classification\SVC; use Phpml\SupportVectorMachine\Kernel; +use Phpml\ModelManager; class SVCTest extends \PHPUnit_Framework_TestCase { @@ -42,4 +43,26 @@ class SVCTest extends \PHPUnit_Framework_TestCase $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 44e745a..65c589a 100644 --- a/tests/Phpml/Dataset/CsvDatasetTest.php +++ b/tests/Phpml/Dataset/CsvDatasetTest.php @@ -9,7 +9,7 @@ use Phpml\Dataset\CsvDataset; 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 0000000..75044e9 --- /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 c668b88..2cd3885 100644 --- a/tests/Phpml/Regression/LeastSquaresTest.php +++ b/tests/Phpml/Regression/LeastSquaresTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace tests\Regression; use Phpml\Regression\LeastSquares; +use Phpml\ModelManager; class LeastSquaresTest extends \PHPUnit_Framework_TestCase { @@ -65,4 +66,28 @@ class LeastSquaresTest extends \PHPUnit_Framework_TestCase $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 5f8bec9..17bb66e 100644 --- a/tests/Phpml/Regression/SVRTest.php +++ b/tests/Phpml/Regression/SVRTest.php @@ -6,6 +6,7 @@ namespace tests\Regression; use Phpml\Regression\SVR; use Phpml\SupportVectorMachine\Kernel; +use Phpml\ModelManager; class SVRTest extends \PHPUnit_Framework_TestCase { @@ -34,4 +35,27 @@ class SVRTest extends \PHPUnit_Framework_TestCase $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 9784154..88043dd 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 27f1829..424fd17 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 52620e9..70e6892 100644 --- a/src/Phpml/Exception/SerializeException.php +++ b/src/Phpml/Exception/SerializeException.php @@ -26,5 +26,4 @@ class SerializeException extends \Exception { 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 60069b2..65d5ea5 100644 --- a/tests/Phpml/Association/AprioriTest.php +++ b/tests/Phpml/Association/AprioriTest.php @@ -6,8 +6,9 @@ namespace tests\Classification; 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 7ec25ba..db2d810 100644 --- a/tests/Phpml/Classification/DecisionTreeTest.php +++ b/tests/Phpml/Classification/DecisionTreeTest.php @@ -6,8 +6,9 @@ namespace tests\Classification; 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 @@ class DecisionTreeTest extends \PHPUnit_Framework_TestCase $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 9824e5d..ea9db77 100644 --- a/tests/Phpml/Classification/KNearestNeighborsTest.php +++ b/tests/Phpml/Classification/KNearestNeighborsTest.php @@ -7,8 +7,9 @@ namespace tests\Classification; 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 1f66ec8..9b2171a 100644 --- a/tests/Phpml/Classification/NaiveBayesTest.php +++ b/tests/Phpml/Classification/NaiveBayesTest.php @@ -6,8 +6,9 @@ namespace tests\Classification; 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 @@ class NaiveBayesTest extends \PHPUnit_Framework_TestCase $testSamples = [[1, 1, 6], [5, 1, 1]]; $testLabels = ['d', 'a']; $this->assertEquals($testLabels, $classifier->predict($testSamples)); - } public function testSaveAndRestore() @@ -68,5 +68,4 @@ class NaiveBayesTest extends \PHPUnit_Framework_TestCase $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 34111b4..89c27e3 100644 --- a/tests/Phpml/Classification/SVCTest.php +++ b/tests/Phpml/Classification/SVCTest.php @@ -7,8 +7,9 @@ namespace tests\Classification; 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 b631508..2d959ac 100644 --- a/tests/Phpml/Clustering/DBSCANTest.php +++ b/tests/Phpml/Clustering/DBSCANTest.php @@ -5,8 +5,9 @@ declare(strict_types=1); 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 56d3c63..85285b2 100644 --- a/tests/Phpml/Clustering/FuzzyCMeansTest.php +++ b/tests/Phpml/Clustering/FuzzyCMeansTest.php @@ -5,8 +5,9 @@ declare(strict_types=1); 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 bd954be..c25306b 100644 --- a/tests/Phpml/Clustering/KMeansTest.php +++ b/tests/Phpml/Clustering/KMeansTest.php @@ -5,8 +5,9 @@ declare(strict_types=1); 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 d6f4681..b2717fd 100644 --- a/tests/Phpml/CrossValidation/RandomSplitTest.php +++ b/tests/Phpml/CrossValidation/RandomSplitTest.php @@ -6,8 +6,9 @@ namespace tests\Phpml\CrossValidation; 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 afe3d81..ef07398 100644 --- a/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php +++ b/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php @@ -6,8 +6,9 @@ namespace tests\Phpml\CrossValidation; 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 bd19bcb..0b13b51 100644 --- a/tests/Phpml/Dataset/ArrayDatasetTest.php +++ b/tests/Phpml/Dataset/ArrayDatasetTest.php @@ -5,8 +5,9 @@ declare(strict_types=1); 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 65c589a..44a112b 100644 --- a/tests/Phpml/Dataset/CsvDatasetTest.php +++ b/tests/Phpml/Dataset/CsvDatasetTest.php @@ -5,8 +5,9 @@ declare(strict_types=1); 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 9755b5d..5e3f94c 100644 --- a/tests/Phpml/Dataset/Demo/GlassDatasetTest.php +++ b/tests/Phpml/Dataset/Demo/GlassDatasetTest.php @@ -5,8 +5,9 @@ declare(strict_types=1); 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 3ee67d0..18ad647 100644 --- a/tests/Phpml/Dataset/Demo/IrisDatasetTest.php +++ b/tests/Phpml/Dataset/Demo/IrisDatasetTest.php @@ -5,8 +5,9 @@ declare(strict_types=1); 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 dbac4e0..a79ed8c 100644 --- a/tests/Phpml/Dataset/Demo/WineDatasetTest.php +++ b/tests/Phpml/Dataset/Demo/WineDatasetTest.php @@ -5,8 +5,9 @@ declare(strict_types=1); 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 49f0e20..d12477f 100644 --- a/tests/Phpml/Dataset/FilesDatasetTest.php +++ b/tests/Phpml/Dataset/FilesDatasetTest.php @@ -5,8 +5,9 @@ declare(strict_types=1); 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 feed892..dd0a185 100644 --- a/tests/Phpml/FeatureExtraction/StopWordsTest.php +++ b/tests/Phpml/FeatureExtraction/StopWordsTest.php @@ -5,8 +5,9 @@ declare(strict_types=1); 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 fd9bb1c..90aa107 100644 --- a/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php +++ b/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php @@ -5,8 +5,9 @@ declare(strict_types=1); 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 2a49b87..a25d5a5 100644 --- a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php +++ b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php @@ -7,8 +7,9 @@ namespace tests\Phpml\FeatureExtraction; 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 c35432d..8806c74 100644 --- a/tests/Phpml/Math/Distance/ChebyshevTest.php +++ b/tests/Phpml/Math/Distance/ChebyshevTest.php @@ -5,8 +5,9 @@ declare(strict_types=1); 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 b3a1f31..38cb6b7 100644 --- a/tests/Phpml/Math/Distance/EuclideanTest.php +++ b/tests/Phpml/Math/Distance/EuclideanTest.php @@ -5,8 +5,9 @@ declare(strict_types=1); 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 07c6510..0e22a15 100644 --- a/tests/Phpml/Math/Distance/ManhattanTest.php +++ b/tests/Phpml/Math/Distance/ManhattanTest.php @@ -5,8 +5,9 @@ declare(strict_types=1); 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 3e8afdb..d8931d0 100644 --- a/tests/Phpml/Math/Distance/MinkowskiTest.php +++ b/tests/Phpml/Math/Distance/MinkowskiTest.php @@ -5,8 +5,9 @@ declare(strict_types=1); 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 4eea13e..8076827 100644 --- a/tests/Phpml/Math/Kernel/RBFTest.php +++ b/tests/Phpml/Math/Kernel/RBFTest.php @@ -5,8 +5,9 @@ declare(strict_types=1); 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 442ef0c..b4adc76 100644 --- a/tests/Phpml/Math/MatrixTest.php +++ b/tests/Phpml/Math/MatrixTest.php @@ -5,8 +5,9 @@ declare(strict_types=1); 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 7fc525c..50ced9e 100644 --- a/tests/Phpml/Math/ProductTest.php +++ b/tests/Phpml/Math/ProductTest.php @@ -5,8 +5,9 @@ declare(strict_types=1); 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 c6548c7..101763c 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 17bb66e..d334973 100644 --- a/tests/Phpml/Regression/SVRTest.php +++ b/tests/Phpml/Regression/SVRTest.php @@ -7,8 +7,9 @@ namespace tests\Regression; 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 @@ class SVRTest extends \PHPUnit_Framework_TestCase public function testSaveAndRestore() { - $samples = [[60], [61], [62], [63], [65]]; $targets = [3.1, 3.6, 3.8, 4, 4.1]; @@ -57,5 +57,4 @@ class SVRTest extends \PHPUnit_Framework_TestCase $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 cb0ee52..33d52c0 100644 --- a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php +++ b/tests/Phpml/SupportVectorMachine/DataTransformerTest.php @@ -5,8 +5,9 @@ declare(strict_types=1); 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 4b4e03b..ca9f299 100644 --- a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php @@ -7,8 +7,9 @@ namespace tests\SupportVectorMachine; 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 8f2daf3..1133835 100644 --- a/tests/Phpml/Tokenization/WhitespaceTokenizerTest.php +++ b/tests/Phpml/Tokenization/WhitespaceTokenizerTest.php @@ -5,8 +5,9 @@ declare(strict_types=1); 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 959adfb..3a6abba 100644 --- a/tests/Phpml/Tokenization/WordTokenizerTest.php +++ b/tests/Phpml/Tokenization/WordTokenizerTest.php @@ -5,8 +5,9 @@ declare(strict_types=1); 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 2ba8cc2..c03d0ed 100644 --- a/src/Phpml/ModelManager.php +++ b/src/Phpml/ModelManager.php @@ -4,25 +4,26 @@ declare(strict_types=1); 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 @@ class ModelManager /** * @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 e5d7f5e..066aad1 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 781d5fd..11b0807 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 bf33f90..c584354 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 1a39cbe..1a04802 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 @@ class DecisionTree implements Classifier $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 @@ class DecisionTree implements Classifier 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 @@ class DecisionTree implements Classifier 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 @@ class DecisionTree implements Classifier $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 d428919..1993864 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 0000000..817869e --- /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 0000000..37df7ae --- /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 0000000..e7dfcad --- /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 0000000..d32507c --- /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 ceaf510..ad60e87 100644 --- a/src/Phpml/Math/Distance/Euclidean.php +++ b/src/Phpml/Math/Distance/Euclidean.php @@ -24,10 +24,9 @@ class Euclidean implements Distance } $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 c584354..62a906d 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 1a04802..6a860eb 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 @@ class DecisionTree implements Classifier $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 @@ class DecisionTree implements Classifier $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 @@ class DecisionTree implements Classifier } $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 @@ class DecisionTree implements Classifier */ 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 1993864..e30fc10 100644 --- a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -6,7 +6,6 @@ namespace Phpml\Classification\DecisionTree; 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 @@ class DecisionTreeLeaf 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 @@ class DecisionTreeLeaf 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 @@ class DecisionTreeLeaf $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 817869e..d579b24 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 @@ class Bagging implements Classifier $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 @@ class Bagging implements Classifier * @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 37df7ae..025badf 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 @@ class RandomForest extends Bagging } /** - * 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 @@ class RandomForest extends Bagging 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 @@ class RandomForest extends Bagging $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 dd722d4..483b1af 100644 --- a/src/Phpml/Dataset/CsvDataset.php +++ b/src/Phpml/Dataset/CsvDataset.php @@ -8,6 +8,11 @@ use Phpml\Exception\FileException; class CsvDataset extends ArrayDataset { + /** + * @var array + */ + protected $columnNames; + /** * @param string $filepath * @param int $features @@ -26,7 +31,10 @@ class CsvDataset extends ArrayDataset } 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 @@ class CsvDataset extends ArrayDataset } 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 9aaa43a..2815804 100644 --- a/src/Phpml/Exception/MatrixException.php +++ b/src/Phpml/Exception/MatrixException.php @@ -21,4 +21,12 @@ class MatrixException extends \Exception { 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 6485a7d..39f5c5a 100644 --- a/src/Phpml/Math/Matrix.php +++ b/src/Phpml/Math/Matrix.php @@ -233,6 +233,10 @@ class Matrix 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 @@ class Matrix 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 b4adc76..48a6fe9 100644 --- a/tests/Phpml/Math/MatrixTest.php +++ b/tests/Phpml/Math/MatrixTest.php @@ -141,6 +141,20 @@ class MatrixTest extends TestCase $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 6a860eb..c73f870 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 @@ class DecisionTree implements Classifier 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 @@ class DecisionTree implements Classifier } /** + * 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 @@ class DecisionTree implements Classifier 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 0000000..94283d9 --- /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 0000000..18d4449 --- /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 0000000..963638e --- /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 5cff6e8..42a8f1c 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -5,17 +5,35 @@ declare(strict_types=1); 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 @@ class Normalizer implements Preprocessor */ 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 @@ class Normalizer implements Preprocessor */ 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 @@ class Normalizer implements Preprocessor } } } + + /** + * @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 0000000..7ea63ab --- /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 0000000..f83e095 --- /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 0000000..bf1b384 --- /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 99ebf4e..07d121c 100644 --- a/tests/Phpml/Preprocessing/NormalizerTest.php +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -100,4 +100,32 @@ class NormalizerTest extends TestCase $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 | 70 ++----- .../Classification/Linear/DecisionStump.php | 127 +++++++++++- .../Classification/Linear/Perceptron.php | 23 ++- .../Classification/Ensemble/AdaBoostTest.php | 64 ++++++ 7 files changed, 462 insertions(+), 60 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 c73f870..231d766 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 @@ class DecisionTree implements Classifier { $this->maxDepth = $maxDepth; } + /** * @param array $samples * @param array $targets @@ -209,6 +210,17 @@ class DecisionTree implements Classifier $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 @@ class DecisionTree implements Classifier 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 @@ class DecisionTree implements Classifier } /** - * 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 e30fc10..bbb3175 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 0000000..70440a6 --- /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 94283d9..aeff95e 100644 --- a/src/Phpml/Classification/Linear/Adaline.php +++ b/src/Phpml/Classification/Linear/Adaline.php @@ -8,7 +8,6 @@ use Phpml\Helper\Predictable; 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); - } - - parent::train($samples, $targets); + parent::__construct($learningRate, $maxIterations, $normalizeInputs); } /** @@ -100,22 +78,8 @@ class Adaline extends Perceptron 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 @@ class Adaline extends Perceptron } /** - * @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); - return parent::predictSample($sample); + $error = 0; + foreach ($col as $index => $val) { + $error += $val * $updates[$index]; + } + + $this->weights[$i] += $this->learningRate * $error; + } + } } } diff --git a/src/Phpml/Classification/Linear/DecisionStump.php b/src/Phpml/Classification/Linear/DecisionStump.php index 18d4449..1220d48 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -8,6 +8,7 @@ use Phpml\Helper\Predictable; 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 @@ class DecisionStump extends DecisionTree */ 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 @@ class DecisionStump extends DecisionTree $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 963638e..78a204a 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -7,6 +7,7 @@ namespace Phpml\Classification\Linear; 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 @@ class Perceptron implements Classifier 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 @@ class Perceptron implements Classifier 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 @@ class Perceptron implements Classifier */ 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 0000000..c9e4d86 --- /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 11b0807..7829691 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 62a906d..c362a2c 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 2815804..1b01659 100644 --- a/src/Phpml/Exception/MatrixException.php +++ b/src/Phpml/Exception/MatrixException.php @@ -27,6 +27,6 @@ class MatrixException extends \Exception */ 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 48a6fe9..0b46612 100644 --- a/tests/Phpml/Math/MatrixTest.php +++ b/tests/Phpml/Math/MatrixTest.php @@ -146,13 +146,13 @@ class MatrixTest extends TestCase */ 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 | 117 +++++++-- src/Phpml/Classification/Linear/Adaline.php | 6 + .../Classification/Linear/DecisionStump.php | 247 +++++++++++++----- .../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 231d766..b2b4db3 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -110,12 +110,13 @@ class DecisionTree implements Classifier } } - 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 @@ class DecisionTree implements Classifier * @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 70440a6..3d1e418 100644 --- a/src/Phpml/Classification/Ensemble/AdaBoost.php +++ b/src/Phpml/Classification/Ensemble/AdaBoost.php @@ -5,6 +5,9 @@ declare(strict_types=1); 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 @@ class AdaBoost implements Classifier // 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 @@ class AdaBoost implements Classifier // 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 @@ class AdaBoost implements 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); + $ref = new \ReflectionClass($this->baseClassifier); + if ($this->classifierOptions) { + $classifier = $ref->newInstanceArgs($this->classifierOptions); + } else { + $classifier = $ref->newInstance(); + } - $errorRate = $stump->getTrainingErrorRate(); - if ($errorRate < $minErrorRate) { - $bestClassifier = $stump; - $minErrorRate = $errorRate; + 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 [$bestClassifier, $minErrorRate]; + 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 $wrong / $total; } /** @@ -154,10 +229,10 @@ class AdaBoost implements Classifier /** * 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 aeff95e..13674f1 100644 --- a/src/Phpml/Classification/Linear/Adaline.php +++ b/src/Phpml/Classification/Linear/Adaline.php @@ -76,10 +76,16 @@ class Adaline extends Perceptron // 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 1220d48..1605a20 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -6,18 +6,19 @@ namespace Phpml\Classification\Linear; 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 @@ class DecisionStump extends DecisionTree */ 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; + } + } + + // Assign determined best values to the stump + foreach ($bestSplit as $name => $value) { + $this->{$name} = $value; } } /** - * Used to set sample weights. + * 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) * - * @param array $weights + * @param float $count */ - public function setSampleWeights(array $weights) + public function setNumericalSplitCount(float $count) { - $this->weights = $weights; + $this->numSplitCount = $count; } /** - * Returns the training error rate, the proportion of wrong predictions - * over the total number of samples + * Determines best split point for the given column * - * @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 int $col * - * @param array $samples - * @param array $targets + * @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); - - if ($errorRate < $bestErrorRate) { - $bestErrorRate = $errorRate; - $bestThreshold = $threshold; - $bestOperator = $operator; + $errorRate = $this->calculateErrorRate($threshold, $operator, $values); + if ($errorRate < $split['trainingErrorRate']) { + $split = ['value' => $threshold, '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 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 ($split == null || $split['trainingErrorRate'] < $errorRate) { + $split = ['value' => $val, 'operator' => $operator, + 'column' => $col, 'trainingErrorRate' => $errorRate]; + } + }// for + } + + 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 @@ class DecisionStump extends DecisionTree * @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 78a204a..bc31da1 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 @@ class Perceptron implements Classifier $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 @@ class Perceptron implements Classifier { $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 @@ class Perceptron implements Classifier 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 @@ class Perceptron implements Classifier $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 0000000..36a294e --- /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 bf1b384..64954f7 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 417cafa..5a2dd57 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 d579b24..1bb2027 100644 --- a/src/Phpml/Classification/Ensemble/Bagging.php +++ b/src/Phpml/Classification/Ensemble/Bagging.php @@ -6,10 +6,8 @@ namespace Phpml\Classification\Ensemble; 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 025badf..273eb21 100644 --- a/src/Phpml/Classification/Ensemble/RandomForest.php +++ b/src/Phpml/Classification/Ensemble/RandomForest.php @@ -4,9 +4,7 @@ declare(strict_types=1); 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 13674f1..194451a 100644 --- a/src/Phpml/Classification/Linear/Adaline.php +++ b/src/Phpml/Classification/Linear/Adaline.php @@ -4,10 +4,7 @@ declare(strict_types=1); 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 bc31da1..e2c684d 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -5,7 +5,6 @@ declare(strict_types=1); 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 36a294e..c0ec045 100644 --- a/src/Phpml/Classification/WeightedClassifier.php +++ b/src/Phpml/Classification/WeightedClassifier.php @@ -1,9 +1,9 @@ - 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 b2b4db3..0a70d2f 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 1605a20..de86fe9 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -5,13 +5,13 @@ declare(strict_types=1); 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 @@ class DecisionStump extends WeightedClassifier * @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 @@ class DecisionStump extends WeightedClassifier 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 @@ class DecisionStump extends WeightedClassifier // 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 @@ class DecisionStump extends WeightedClassifier 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 @@ class DecisionStump extends WeightedClassifier // 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 @@ class DecisionStump extends WeightedClassifier 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 @@ class DecisionStump extends WeightedClassifier 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 @@ class DecisionStump extends WeightedClassifier * @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]++; + } + + // 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 / $total; + 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 e2c684d..32e41f2 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -5,12 +5,13 @@ declare(strict_types=1); 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 @@ class Perceptron implements Classifier * @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 @@ class Perceptron implements Classifier // 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 @@ class Perceptron implements Classifier 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 @@ class Perceptron implements Classifier 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 0000000..1288363 --- /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 0000000..df27f07 --- /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 7ea63ab..c07fbd2 100644 --- a/tests/Phpml/Classification/Linear/AdalineTest.php +++ b/tests/Phpml/Classification/Linear/AdalineTest.php @@ -30,6 +30,21 @@ class AdalineTest extends TestCase $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 f83e095..4060ce3 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 @@ class DecisionStumpTest extends TestCase $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 64954f7..ef820fc 100644 --- a/tests/Phpml/Classification/Linear/PerceptronTest.php +++ b/tests/Phpml/Classification/Linear/PerceptronTest.php @@ -30,6 +30,21 @@ class PerceptronTest extends TestCase $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 0000000..6bbf63b --- /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 0a70d2f..6e890c9 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -4,6 +4,7 @@ declare(strict_types=1); 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 @@ class DecisionTree implements Classifier $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 @@ class DecisionTree implements Classifier } } - 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 @@ class DecisionTree implements Classifier } } - 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 @@ class DecisionTree implements Classifier $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 @@ class DecisionTree implements Classifier 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 @@ class DecisionTree implements Classifier $bestGiniVal = $gini; } } + return $bestSplit; } @@ -239,10 +248,10 @@ class DecisionTree implements Classifier * * @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 @@ class DecisionTree implements Classifier } /** - * @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 @@ class DecisionTree implements Classifier } 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 @@ class DecisionTree implements Classifier } $giniParts[$i] = (1 - $part) * $sum; } + return array_sum($giniParts) / count($colValues); } @@ -295,14 +306,14 @@ class DecisionTree implements Classifier * @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 @@ class DecisionTree implements Classifier * @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 @@ class DecisionTree implements Classifier 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 @@ class DecisionTree implements Classifier * * @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 @@ class DecisionTree implements Classifier * * @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 @@ class DecisionTree implements Classifier * 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 @@ class DecisionTree implements Classifier /** * 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 de86fe9..8287bbc 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -135,7 +135,7 @@ class DecisionStump extends WeightedClassifier '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 c0ec045..4af3de4 100644 --- a/src/Phpml/Classification/WeightedClassifier.php +++ b/src/Phpml/Classification/WeightedClassifier.php @@ -6,7 +6,10 @@ namespace Phpml\Classification; 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 424fd17..84f4b38 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 8287bbc..13bc4a5 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 @@ class DecisionStump extends WeightedClassifier /** * @param array $samples * @param array $targets + * @throws \Exception */ protected function trainBinary(array $samples, array $targets) { @@ -213,12 +205,11 @@ class DecisionStump extends WeightedClassifier } /** - * * @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 @@ class DecisionStump extends WeightedClassifier 'prob' => $prob, 'column' => $col, 'trainingErrorRate' => $errorRate]; } - }// for + } } return $split; @@ -275,7 +266,7 @@ class DecisionStump extends WeightedClassifier * * @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 @@ class DecisionStump extends WeightedClassifier * * @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 5bbefec..48a5b3a 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 55a5305..27b9e5a 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -4,9 +4,14 @@ declare(strict_types=1); 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 @@ class SupportVectorMachine /** * @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 @@ class SupportVectorMachine 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 | 148 +++----- 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, 1162 insertions(+), 152 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 194451a..8d94be4 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 @@ class Adaline extends Perceptron */ 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(); - } + // The cost function is the sum of squares + $callback = function ($weights, $sample, $target) { + $this->weights = $weights; - // Batch learning is executed: - $currIter = 0; - while ($this->maxIterations > $currIter++) { - $weights = $this->weights; + $output = $this->output($sample); + $gradient = $output - $target; + $error = $gradient ** 2; - $outputs = array_map([$this, 'output'], $this->samples); - $updates = array_map([$this, 'gradient'], $this->targets, $outputs); + return [$error, $gradient]; + }; - $this->updateWeights($updates); + $isBatch = $this->trainingType == self::BATCH_TRAINING; - if ($this->earlyStop($weights)) { - break; - } - } - } - - /** - * 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; - } - - /** - * 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); - - $error = 0; - foreach ($col as $index => $val) { - $error += $val * $updates[$index]; - } - - $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 0000000..a0ec290 --- /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 32e41f2..8280bcb 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -6,6 +6,8 @@ namespace Phpml\Classification\Linear; 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 $threshold = 1e-5; + protected $enableEarlyStop = true; + + /** + * @var array + */ + protected $costValues = []; /** * Initalize a perceptron classifier with given learning rate and maximum @@ -97,20 +93,6 @@ class Perceptron implements Classifier $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 @@ class Perceptron implements Classifier $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 */ - protected function runTraining() + public function setEarlyStop(bool $enable = true) { - $currIter = 0; - $bestWeights = null; - $bestScore = count($this->samples); - $bestWeightIter = 0; + $this->enableEarlyStop = $enable; - 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; - } - } - - // 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; + return $this; } /** - * @param array $oldWeights + * Returns the cost values obtained during the training. * - * @return boolean + * @return array */ - protected function earlyStop($oldWeights) + public function getCostValues() { - // 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); + return $this->costValues; + } - if (array_sum($diff) == 0) { - return true; - } + /** + * Trains the perceptron model with Stochastic Gradient Descent optimization + * to get the correct set of weights + */ + protected function runTraining() + { + // The cost function is the sum of squares + $callback = function ($weights, $sample, $target) { + $this->weights = $weights; - return false; + $prediction = $this->outputClass($sample); + $gradient = $prediction - $target; + $error = $gradient**2; + + return [$error, $gradient]; + }; + + $this->runGradientDescent($callback); + } + + /** + * Executes Stochastic Gradient Descent algorithm for + * the given cost function + */ + protected function runGradientDescent(\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); + + $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 1288363..9e7bc82 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 @@ trait OneVsRest $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 0000000..9bcb338 --- /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 0000000..1402930 --- /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 0000000..9ef4c4d --- /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 0000000..5379a28 --- /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 c9e4d86..8c17752 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 ef820fc..1f40c46 100644 --- a/tests/Phpml/Classification/Linear/PerceptronTest.php +++ b/tests/Phpml/Classification/Linear/PerceptronTest.php @@ -16,6 +16,7 @@ class PerceptronTest extends TestCase $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 @@ class PerceptronTest extends TestCase $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 @@ class PerceptronTest extends TestCase $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 483b1af..8bcd3c4 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 @@ class CsvDataset extends ArrayDataset } 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 1ad84d3..5ba1665 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 8baf731..4b602cf 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 5b3f999..76a157b 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 | 155 ++++++++++++------ .../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, 292 insertions(+), 116 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 8d94be4..f34dc5c 100644 --- a/src/Phpml/Classification/Linear/Adaline.php +++ b/src/Phpml/Classification/Linear/Adaline.php @@ -53,8 +53,11 @@ class Adaline extends Perceptron /** * 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 @@ class Adaline extends Perceptron $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 13bc4a5..99f982f 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -89,15 +89,13 @@ class DecisionStump extends WeightedClassifier * @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 @@ class DecisionStump extends WeightedClassifier // 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 @@ class DecisionStump extends WeightedClassifier '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 @@ class DecisionStump extends WeightedClassifier /** * 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 @@ class DecisionStump extends WeightedClassifier // 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 @@ class DecisionStump extends WeightedClassifier // 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 @@ class DecisionStump extends WeightedClassifier } /** + * @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 @@ class DecisionStump extends WeightedClassifier 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 @@ class DecisionStump extends WeightedClassifier * 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 @@ class DecisionStump extends WeightedClassifier $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 @@ class DecisionStump extends WeightedClassifier 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 a0ec290..bd56d34 100644 --- a/src/Phpml/Classification/Linear/LogisticRegression.php +++ b/src/Phpml/Classification/Linear/LogisticRegression.php @@ -123,20 +123,23 @@ class LogisticRegression extends Adaline /** * 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 @@ class LogisticRegression extends Adaline * 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 8280bcb..2cf96cc 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -10,20 +10,17 @@ use Phpml\Helper\Optimizer\StochasticGD; 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 @@ class Perceptron implements Classifier $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(); + $this->runTraining($samples, $targets); + } + + protected function resetBinary() + { + $this->labels = []; + $this->optimizer = null; + $this->featureCount = 0; + $this->weights = null; + $this->costValues = []; } /** @@ -151,8 +163,11 @@ class Perceptron implements Classifier /** * 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 @@ class Perceptron implements Classifier 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 9e7bc82..98269cd 100644 --- a/src/Phpml/Helper/OneVsRest.php +++ b/src/Phpml/Helper/OneVsRest.php @@ -6,30 +6,23 @@ namespace Phpml\Helper; trait OneVsRest { - /** - * @var array - */ - protected $samples = []; /** * @var array */ - protected $targets = []; + protected $classifiers = []; + + /** + * All provided training targets' labels. + * + * @var array + */ + protected $allLabels = []; /** * @var array */ - protected $classifiers; - - /** - * @var array - */ - protected $labels; - - /** - * @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) { + // 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 + 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 + + 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 + // 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; - $this->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; - } - } - - // 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(); - } + $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 @@ trait OneVsRest */ 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 @@ trait OneVsRest * * @param array $samples * @param array $targets + * @param array $labels */ - abstract protected function trainBinary(array $samples, array $targets); + abstract protected function trainBinary(array $samples, array $targets, array $labels); + + /** + * To be overwritten by OneVsRest classifiers. + * + * @return void + */ + 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 9bcb338..18ae89a 100644 --- a/src/Phpml/Helper/Optimizer/ConjugateGradient.php +++ b/src/Phpml/Helper/Optimizer/ConjugateGradient.php @@ -57,6 +57,8 @@ class ConjugateGradient extends GD } } + $this->clear(); + return $this->theta; } diff --git a/src/Phpml/Helper/Optimizer/GD.php b/src/Phpml/Helper/Optimizer/GD.php index 1402930..8974c8e 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 @@ class GD extends StochasticGD } } + $this->clear(); + return $this->theta; } @@ -105,4 +107,15 @@ class GD extends StochasticGD } } } + + /** + * 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 5379a28..e9e318a 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 @@ class StochasticGD extends Optimizer } } + $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 @@ class StochasticGD extends Optimizer { 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 0000000..df18872 --- /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 1f40c46..132a6d7 100644 --- a/tests/Phpml/Classification/Linear/PerceptronTest.php +++ b/tests/Phpml/Classification/Linear/PerceptronTest.php @@ -48,7 +48,23 @@ class PerceptronTest extends TestCase $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 2cf96cc..91ffacf 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -11,7 +11,6 @@ use Phpml\Helper\Optimizer\GD; use Phpml\Classification\Classifier; use Phpml\Preprocessing\Normalizer; use Phpml\IncrementalEstimator; -use Phpml\Helper\PartiallyTrainable; class Perceptron implements Classifier, IncrementalEstimator { @@ -95,7 +94,7 @@ class Perceptron implements Classifier, 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 = []) { return $this->trainByLabel($samples, $targets, $labels); } @@ -107,7 +106,6 @@ class Perceptron implements Classifier, IncrementalEstimator */ 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 98269cd..e207c46 100644 --- a/src/Phpml/Helper/OneVsRest.php +++ b/src/Phpml/Helper/OneVsRest.php @@ -44,7 +44,7 @@ trait OneVsRest * @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 @@ trait OneVsRest */ 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 df18872..fc6912d 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 27b9e5a..f3828b4 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -6,12 +6,11 @@ namespace Phpml\SupportVectorMachine; 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 c362a2c..bab758c 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 0000000..86070c7 --- /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 0000000..422dae4 --- /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 42063f1..f6b0031 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -55,7 +55,7 @@ class InvalidArgumentException extends \Exception */ 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 ad60e87..1158f5d 100644 --- a/src/Phpml/Math/Distance/Euclidean.php +++ b/src/Phpml/Math/Distance/Euclidean.php @@ -31,4 +31,17 @@ class Euclidean implements Distance 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 0000000..27557bb --- /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 39f5c5a..25101f3 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 @@ class Matrix return $this->matrix; } + /** + * @return float + */ + public function toScalar() + { + return $this->matrix[0][0]; + } + /** * @return int */ @@ -103,14 +118,10 @@ class Matrix 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 @@ class Matrix */ 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 @@ class Matrix 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 @@ class Matrix { 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 0000000..4a9b613 --- /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 0000000..14b2d7d --- /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 0000000..8f65e98 --- /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 0000000..4bca1bd --- /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 0b46612..257fd72 100644 --- a/tests/Phpml/Math/MatrixTest.php +++ b/tests/Phpml/Math/MatrixTest.php @@ -188,4 +188,80 @@ class MatrixTest extends TestCase $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 0000000..4b025a3 --- /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 42a8f1c..8392db7 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -134,7 +134,12 @@ class Normalizer implements Preprocessor 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 07d121c..2492fae 100644 --- a/tests/Phpml/Preprocessing/NormalizerTest.php +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -111,6 +111,9 @@ class NormalizerTest extends TestCase 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 @@ class NormalizerTest extends TestCase 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 | 60 ++-- tests/Phpml/DimensionReduction/LDATest.php | 65 ++++ 9 files changed, 736 insertions(+), 132 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 bab758c..b17ac72 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 0000000..b399002 --- /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 86070c7..51fb17a 100644 --- a/src/Phpml/DimensionReduction/KernelPCA.php +++ b/src/Phpml/DimensionReduction/KernelPCA.php @@ -86,7 +86,7 @@ class KernelPCA extends PCA $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 0000000..28f34d6 --- /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 422dae4..db2110d 100644 --- a/src/Phpml/DimensionReduction/PCA.php +++ b/src/Phpml/DimensionReduction/PCA.php @@ -4,27 +4,12 @@ declare(strict_types=1); 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 @@ class PCA $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 @@ class PCA 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 27557bb..5cbc121 100644 --- a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -130,10 +130,10 @@ class EigenvalueDecomposition $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 0000000..1aeb239 --- /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 25101f3..c996e7f 100644 --- a/src/Phpml/Math/Matrix.php +++ b/src/Phpml/Math/Matrix.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Phpml\Math; +use Phpml\Math\LinearAlgebra\LUDecomposition; use Phpml\Exception\InvalidArgumentException; use Phpml\Exception\MatrixException; @@ -137,32 +138,8 @@ class Matrix 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 @@ class Matrix throw MatrixException::notSquareMatrix(); } - if ($this->isSingular()) { - throw MatrixException::singularMatrix(); + $LU = new LUDecomposition($this); + $identity = $this->getIdentity(); + $inverse = $LU->solve($identity); + + return new self($inverse, 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; } - $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; - } - } - - $cofactorMatrix = new self($newMatrix, false); - - 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 0000000..5ebe018 --- /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 5a2dd57..0ea0c2a 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 d9dcaff..889f720 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 6eb37cb..0000000 --- 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 f3828b4..9f6da70 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -9,8 +9,8 @@ use Phpml\Helper\Trainable; class SupportVectorMachine { use Trainable; - - /** + + /** * @var int */ private $type; @@ -128,6 +128,26 @@ class SupportVectorMachine $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 6e890c9..da8b81b 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -102,19 +102,21 @@ class DecisionTree implements Classifier $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 @@ class DecisionTree implements Classifier /** * @param array $records - * @param int $depth + * @param int $depth + * * @return DecisionTreeLeaf */ protected function getSplitLeaf(array $records, int $depth = 0) : DecisionTreeLeaf @@ -163,10 +166,10 @@ class DecisionTree implements Classifier // 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 @@ class DecisionTree implements Classifier /** * @param array $records + * * @return DecisionTreeLeaf */ protected function getBestSplit(array $records) : DecisionTreeLeaf @@ -251,7 +255,7 @@ class DecisionTree implements Classifier 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 @@ class DecisionTree implements Classifier } /** - * @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 @@ class DecisionTree implements Classifier 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 @@ class DecisionTree implements Classifier $part += pow($countMatrix[$label][$i] / floatval($sum), 2); } } + $giniParts[$i] = (1 - $part) * $sum; } @@ -304,6 +312,7 @@ class DecisionTree implements Classifier /** * @param array $samples + * * @return array */ protected function preprocess(array $samples) : array @@ -311,7 +320,7 @@ class DecisionTree implements Classifier // 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 @@ class DecisionTree implements Classifier /** * @param array $columnValues + * * @return bool */ protected static function isCategoricalColumn(array $columnValues) : bool @@ -348,6 +358,7 @@ class DecisionTree implements Classifier if ($floatValues) { return false; } + if (count($numericValues) !== $count) { return true; } @@ -365,7 +376,9 @@ class DecisionTree implements Classifier * randomly selected for each split operation. * * @param int $numFeatures + * * @return $this + * * @throws InvalidArgumentException */ public function setNumFeatures(int $numFeatures) @@ -394,7 +407,9 @@ class DecisionTree implements Classifier * column importances are desired to be inspected. * * @param array $names + * * @return $this + * * @throws InvalidArgumentException */ public function setColumnNames(array $names) @@ -458,8 +473,9 @@ class DecisionTree implements Classifier * 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 @@ class DecisionTree implements Classifier 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 @@ class DecisionTree implements Classifier /** * @param array $sample + * * @return mixed */ protected function predictSample(array $sample) @@ -497,6 +516,7 @@ class DecisionTree implements Classifier 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 bbb3175..787108f 100644 --- a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -92,6 +92,8 @@ class DecisionTreeLeaf * 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 @@ class DecisionTreeLeaf } 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 3d1e418..38571da 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 @@ class AdaBoost implements Classifier /** * @param array $samples * @param array $targets + * + * @throws \Exception */ public function train(array $samples, array $targets) { @@ -123,7 +126,6 @@ class AdaBoost implements Classifier // 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 @@ class AdaBoost implements Classifier $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 @@ class AdaBoost implements Classifier * 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 1bb2027..1af155d 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 @@ class Bagging implements Classifier * 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 @@ class Bagging implements Classifier * * @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 @@ class Bagging implements Classifier $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 @@ class Bagging implements Classifier 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 273eb21..7849cd8 100644 --- a/src/Phpml/Classification/Ensemble/RandomForest.php +++ b/src/Phpml/Classification/Ensemble/RandomForest.php @@ -5,7 +5,6 @@ declare(strict_types=1); 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 @@ class RandomForest extends Bagging * 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 @@ class RandomForest extends Bagging * 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 @@ class RandomForest extends Bagging /** * @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 f34dc5c..b94de28 100644 --- a/src/Phpml/Classification/Linear/Adaline.php +++ b/src/Phpml/Classification/Linear/Adaline.php @@ -4,11 +4,8 @@ declare(strict_types=1); 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 99f982f..5a3247f 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -87,6 +87,8 @@ class DecisionStump extends WeightedClassifier /** * @param array $samples * @param array $targets + * @param array $labels + * * @throws \Exception */ protected function trainBinary(array $samples, array $targets, array $labels) @@ -237,13 +239,13 @@ class DecisionStump extends WeightedClassifier /** * - * @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 @@ class DecisionStump extends WeightedClassifier $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 bd56d34..bc6a3c9 100644 --- a/src/Phpml/Classification/Linear/LogisticRegression.php +++ b/src/Phpml/Classification/Linear/LogisticRegression.php @@ -4,21 +4,19 @@ declare(strict_types=1); 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 @@ class LogisticRegression extends Adaline 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 @@ class LogisticRegression extends Adaline * * @param array $samples * @param array $targets + * + * @throws \Exception */ protected function runTraining(array $samples, array $targets) { @@ -140,12 +140,18 @@ class LogisticRegression extends Adaline 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 @@ class LogisticRegression extends Adaline * Returns the appropriate callback function for the selected cost function * * @return \Closure + * + * @throws \Exception */ protected function getCostFunction() { @@ -203,7 +211,7 @@ class LogisticRegression extends Adaline return $callback; case 'sse': - /** + /* * Sum of squared errors or least squared errors cost function: * J(x) = ∑ (y - h(x))^2 * @@ -224,6 +232,9 @@ class LogisticRegression extends Adaline }; return $callback; + + default: + throw new \Exception(sprintf('Logistic regression has invalid cost function: %s.', $this->costFunction)); } } @@ -245,6 +256,7 @@ class LogisticRegression extends Adaline * 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 @@ class LogisticRegression extends Adaline * * @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 91ffacf..f4a8791 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 @@ class Perceptron implements Classifier, IncrementalEstimator */ public function partialTrain(array $samples, array $targets, array $labels = []) { - return $this->trainByLabel($samples, $targets, $labels); + $this->trainByLabel($samples, $targets, $labels); } /** @@ -140,6 +140,8 @@ class Perceptron implements Classifier, IncrementalEstimator * for $maxIterations times * * @param bool $enable + * + * @return $this */ public function setEarlyStop(bool $enable = true) { @@ -185,12 +187,14 @@ class Perceptron implements Classifier, IncrementalEstimator * 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 @@ class Perceptron implements Classifier, IncrementalEstimator * * @param array $sample * @param mixed $label + * + * @return float */ protected function predictProbability(array $sample, $label) { @@ -277,6 +283,7 @@ class Perceptron implements Classifier, IncrementalEstimator /** * @param array $sample + * * @return mixed */ protected function predictSampleBinary(array $sample) @@ -285,6 +292,6 @@ class Perceptron implements Classifier, IncrementalEstimator $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 af81b00..1a634da 100644 --- a/src/Phpml/Classification/NaiveBayes.php +++ b/src/Phpml/Classification/NaiveBayes.php @@ -89,7 +89,7 @@ class NaiveBayes implements Classifier $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 @@ class NaiveBayes implements Classifier /** * 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 @@ class NaiveBayes implements Classifier /** * 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 @@ class NaiveBayes implements Classifier $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 424f2f1..c6a3c46 100644 --- a/src/Phpml/Clustering/FuzzyCMeans.php +++ b/src/Phpml/Clustering/FuzzyCMeans.php @@ -7,6 +7,7 @@ namespace Phpml\Clustering; 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 @@ class FuzzyCMeans implements Clusterer 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 @@ class FuzzyCMeans implements Clusterer 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 @@ class FuzzyCMeans implements Clusterer 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 @@ class FuzzyCMeans implements Clusterer $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 @@ class FuzzyCMeans implements Clusterer { $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 @@ class FuzzyCMeans implements Clusterer // 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 @@ class FuzzyCMeans implements Clusterer } 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 @@ class FuzzyCMeans implements Clusterer 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 5a4d530..0276880 100644 --- a/src/Phpml/Clustering/KMeans/Space.php +++ b/src/Phpml/Clustering/KMeans/Space.php @@ -156,7 +156,11 @@ class Space extends SplObjectStorage 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 8bcd3c4..b2e9407 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 @@ class CsvDataset extends ArrayDataset $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 b399002..6c0ef05 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 @@ class KernelPCA extends PCA * 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 @@ class KernelPCA extends PCA $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 @@ class KernelPCA extends PCA * 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 @@ class KernelPCA extends PCA * Returns the callable kernel function * * @return \Closure + * + * @throws \Exception */ protected function getKernel() { @@ -181,6 +185,9 @@ class KernelPCA extends PCA 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 @@ class KernelPCA extends PCA * @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 28f34d6..e094c35 100644 --- a/src/Phpml/DimensionReduction/LDA.php +++ b/src/Phpml/DimensionReduction/LDA.php @@ -4,7 +4,6 @@ declare(strict_types=1); 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 @@ class LDA extends EigenTransformerBase * 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 @@ class LDA extends EigenTransformerBase $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 @@ class LDA extends EigenTransformerBase * @param array $sample * * @return array + * + * @throws \Exception */ public function transform(array $sample) { @@ -238,7 +240,7 @@ class LDA extends EigenTransformerBase 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 db2110d..acaa8e0 100644 --- a/src/Phpml/DimensionReduction/PCA.php +++ b/src/Phpml/DimensionReduction/PCA.php @@ -6,7 +6,6 @@ namespace Phpml\DimensionReduction; use Phpml\Math\Statistic\Covariance; use Phpml\Math\Statistic\Mean; -use Phpml\Math\Matrix; class PCA extends EigenTransformerBase { @@ -86,7 +85,7 @@ class PCA extends EigenTransformerBase { // 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 @@ class PCA extends EigenTransformerBase * each dimension therefore dimensions will be centered to zero * * @param array $data - * @param int $n + * @param int $n * * @return array */ @@ -109,7 +108,7 @@ class PCA extends EigenTransformerBase // 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 @@ class PCA extends EigenTransformerBase * @param array $sample * * @return array + * + * @throws \Exception */ public function transform(array $sample) { @@ -131,7 +132,7 @@ class PCA extends EigenTransformerBase 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 6092053..ca7b065 100644 --- a/src/Phpml/Exception/DatasetException.php +++ b/src/Phpml/Exception/DatasetException.php @@ -6,7 +6,6 @@ namespace Phpml\Exception; class DatasetException extends \Exception { - /** * @param string $path * diff --git a/src/Phpml/Exception/FileException.php b/src/Phpml/Exception/FileException.php index 558ae48..20b2936 100644 --- a/src/Phpml/Exception/FileException.php +++ b/src/Phpml/Exception/FileException.php @@ -6,7 +6,6 @@ namespace Phpml\Exception; class FileException extends \Exception { - /** * @param string $filepath * diff --git a/src/Phpml/Exception/InvalidArgumentException.php b/src/Phpml/Exception/InvalidArgumentException.php index f6b0031..bf6d8cc 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 @@ class InvalidArgumentException extends \Exception */ 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 70e6892..5753eb7 100644 --- a/src/Phpml/Exception/SerializeException.php +++ b/src/Phpml/Exception/SerializeException.php @@ -6,7 +6,6 @@ namespace Phpml\Exception; class SerializeException extends \Exception { - /** * @param string $filepath * diff --git a/src/Phpml/Helper/OneVsRest.php b/src/Phpml/Helper/OneVsRest.php index e207c46..8d71fbc 100644 --- a/src/Phpml/Helper/OneVsRest.php +++ b/src/Phpml/Helper/OneVsRest.php @@ -6,7 +6,6 @@ namespace Phpml\Helper; trait OneVsRest { - /** * @var array */ @@ -35,18 +34,18 @@ trait OneVsRest // 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 @@ trait OneVsRest // 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 @@ trait OneVsRest // 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 @@ trait OneVsRest */ 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 @@ trait OneVsRest * 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 18ae89a..44bcd14 100644 --- a/src/Phpml/Helper/Optimizer/ConjugateGradient.php +++ b/src/Phpml/Helper/Optimizer/ConjugateGradient.php @@ -18,8 +18,8 @@ namespace Phpml\Helper\Optimizer; 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 @@ class ConjugateGradient extends GD $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 @@ class ConjugateGradient extends GD * * @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 @@ class ConjugateGradient extends GD */ 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 @@ class ConjugateGradient extends GD * * @param float $d * - * @return array + * @return float */ protected function getAlpha(float $d) { @@ -157,14 +157,14 @@ class ConjugateGradient extends GD * @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 @@ class mp * * @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 @@ class mp * * @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 @@ class mp * 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 8974c8e..b88b0c7 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 @@ class GD extends StochasticGD list($cost, $grad, $penalty) = array_pad($result, 3, 0); $costs[] = $cost; - $gradient[]= $grad; + $gradient[] = $grad; $totalPenalty += $penalty; } @@ -91,8 +91,8 @@ class GD extends StochasticGD 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 9ef4c4d..09668a9 100644 --- a/src/Phpml/Helper/Optimizer/Optimizer.php +++ b/src/Phpml/Helper/Optimizer/Optimizer.php @@ -31,7 +31,7 @@ abstract class Optimizer // 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 @@ abstract class Optimizer * Sets the weights manually * * @param array $theta + * + * @return $this + * + * @throws \Exception */ public function setInitialTheta(array $theta) { @@ -56,6 +60,9 @@ abstract class Optimizer * 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 e9e318a..fa2401a 100644 --- a/src/Phpml/Helper/Optimizer/StochasticGD.php +++ b/src/Phpml/Helper/Optimizer/StochasticGD.php @@ -166,7 +166,6 @@ class StochasticGD extends Optimizer $currIter = 0; $bestTheta = null; $bestScore = 0.0; - $bestWeightIter = 0; $this->costValues = []; while ($this->maxIterations > $currIter++) { @@ -180,7 +179,6 @@ class StochasticGD extends Optimizer 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 @@ class StochasticGD extends Optimizer $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 097edaa..2ef9017 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 fc6912d..4a0d1cc 100644 --- a/src/Phpml/IncrementalEstimator.php +++ b/src/Phpml/IncrementalEstimator.php @@ -6,7 +6,6 @@ namespace Phpml; 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 8ca7d84..2cd92db 100644 --- a/src/Phpml/Math/Kernel/RBF.php +++ b/src/Phpml/Math/Kernel/RBF.php @@ -23,8 +23,8 @@ class RBF implements Kernel } /** - * @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 5cbc121..7f0ec4b 100644 --- a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -33,7 +33,6 @@ use Phpml\Math\Matrix; 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 @@ class EigenvalueDecomposition // 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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition 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 @@ class EigenvalueDecomposition } // 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 @@ class EigenvalueDecomposition } } 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 @@ class EigenvalueDecomposition 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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition 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 @@ class EigenvalueDecomposition 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 @@ class EigenvalueDecomposition } } $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition } } - /** - * 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 @@ class EigenvalueDecomposition } } - /** * Nonsymmetric reduction from Hessenberg to real Schur form. * @@ -424,7 +465,7 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition // 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 @@ class EigenvalueDecomposition --$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 @@ class EigenvalueDecomposition } 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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition 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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition 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 @@ class EigenvalueDecomposition } 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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition 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 @@ class EigenvalueDecomposition } // 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 @@ class EigenvalueDecomposition } // 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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition } // 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 @@ class EigenvalueDecomposition } } // 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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition return $this->d; } - /** * Return the imaginary parts of the eigenvalues
* d = imag(diag(D)) @@ -868,7 +877,6 @@ class EigenvalueDecomposition return $this->e; } - /** * Return the block diagonal eigenvalue matrix * @@ -876,15 +884,19 @@ class EigenvalueDecomposition */ 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 1aeb239..de6a15d 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 @@ class LUDecomposition } // 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 @@ class LUDecomposition } // 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 @@ class LUDecomposition /** - * 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 @@ class LUDecomposition /** - * 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 @@ class LUDecomposition /** - * Return pivot permutation vector. + * Return pivot permutation vector. * - * @return array Pivot vector + * @return array Pivot vector */ public function getPivot() { @@ -184,9 +187,9 @@ class LUDecomposition /** - * Alias for getPivot + * Alias for getPivot * - * @see getPivot + * @see getPivot */ public function getDoublePivot() { @@ -195,9 +198,9 @@ class LUDecomposition /** - * 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 @@ class LUDecomposition return false; } } + return true; } // function isNonsingular() /** - * Count determinants + * Count determinants * - * @return array d matrix deterninat + * @return float|int d matrix determinant + * + * @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 @@ class LUDecomposition 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 @@ class LUDecomposition } // 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 @@ class LUDecomposition { $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 c996e7f..3c31052 100644 --- a/src/Phpml/Math/Matrix.php +++ b/src/Phpml/Math/Matrix.php @@ -139,6 +139,7 @@ class Matrix } $lu = new LUDecomposition($this); + return $this->determinant = $lu->det(); } @@ -232,6 +233,8 @@ class Matrix * Element-wise addition of the matrix with another one * * @param Matrix $other + * + * @return Matrix */ public function add(Matrix $other) { @@ -242,6 +245,8 @@ class Matrix * Element-wise subtracting of another matrix from this one * * @param Matrix $other + * + * @return Matrix */ public function subtract(Matrix $other) { @@ -252,7 +257,9 @@ class Matrix * 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 @@ class Matrix $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 @@ class Matrix 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 @@ class Matrix */ public static function transposeArray(array $array) { - return (new Matrix($array, false))->transpose()->toArray(); + return (new self($array, false))->transpose()->toArray(); } /** @@ -359,8 +366,8 @@ class Matrix */ 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 4a9b613..8c8781d 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 @@ class Covariance * 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 @@ class Covariance /** * Returns the covariance matrix of n-dimensional data * - * @param array $data + * @param array $data + * @param array|null $means * * @return array */ @@ -133,19 +138,20 @@ class Covariance 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 df27f07..d09edba 100644 --- a/src/Phpml/Math/Statistic/Gaussian.php +++ b/src/Phpml/Math/Statistic/Gaussian.php @@ -31,7 +31,7 @@ class Gaussian * * @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 581a122..bd9657e 100644 --- a/src/Phpml/Math/Statistic/Mean.php +++ b/src/Phpml/Math/Statistic/Mean.php @@ -68,7 +68,7 @@ class Mean */ 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 c7cc147..6fc026c 100644 --- a/src/Phpml/Metric/ClassificationReport.php +++ b/src/Phpml/Metric/ClassificationReport.php @@ -112,8 +112,8 @@ class ClassificationReport 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 c03d0ed..08ab3e6 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 @@ class ModelManager $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 @@ class ModelManager /** * @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 136e8bf..43ac512 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -44,10 +44,12 @@ class Backpropagation implements Training */ 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 8392db7..c61b447 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -84,7 +84,7 @@ class Normalizer implements Preprocessor $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 9f6da70..c6ec017 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -130,6 +130,8 @@ class SupportVectorMachine /** * @param string $binPath + * + * @return $this */ public function setBinPath(string $binPath) { @@ -140,6 +142,8 @@ class SupportVectorMachine /** * @param string $varPath + * + * @return $this */ public function setVarPath(string $varPath) { @@ -230,8 +234,8 @@ class SupportVectorMachine } /** - * @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 | 107 ++++----------- 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, 371 insertions(+), 345 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 b17ac72..48b7b48 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 156acb2..8d284af 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 0582351..0000000 --- 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 0000000..6f11f68 --- /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 c1c0eef..0000000 --- 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 433cc3e..8c9c10c 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 0000000..c5d00bf --- /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 bf6d8cc..3e2bff5 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -66,6 +66,14 @@ class InvalidArgumentException extends \Exception 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 @@ class InvalidArgumentException extends \Exception */ 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 af2d723..cd90e3f 100644 --- a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php +++ b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php @@ -71,7 +71,7 @@ abstract class LayeredNetwork implements Network 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 04664f9..5d7f94e 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -4,34 +4,93 @@ declare(strict_types=1); 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 @@ class MultilayerPerceptron extends LayeredNetwork $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 5194438..7c246be 100644 --- a/src/Phpml/NeuralNetwork/Node/Neuron.php +++ b/src/Phpml/NeuralNetwork/Node/Neuron.php @@ -68,7 +68,7 @@ class Neuron implements Node 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 d876af2..fcb6d73 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 43ac512..741db2b 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -4,18 +4,11 @@ declare(strict_types=1); 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 + * @var array */ - public function __construct(Network $network, int $theta = 1) + private $prevSigmas; + + /** + * @param int $theta + */ + public function __construct(int $theta) { - $this->network = $network; $this->theta = $theta; } /** - * @param array $samples - * @param array $targets - * @param float $desiredError - * @param int $maxIterations + * @param array $layers + * @param mixed $targetClass */ - public function train(array $samples, array $targets, float $desiredError = 0.001, int $maxIterations = 10000) + public function backpropagate(array $layers, $targetClass) { - $samplesCount = count($samples); - for ($i = 0; $i < $maxIterations; ++$i) { - $resultsWithinError = $this->trainSamples($samples, $targets, $desiredError); - - if ($resultsWithinError === $samplesCount) { - 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; - } - - /** - * @param array $sample - * @param array $target - */ - private function trainSample(array $sample, array $target) - { - $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 @@ class Backpropagation implements Training { $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 72e6a81..0000000 --- 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 0000000..9f8b3fc --- /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 e51d485..0000000 --- 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 6bd0398..a7bc6fe 100644 --- a/tests/Phpml/NeuralNetwork/Node/NeuronTest.php +++ b/tests/Phpml/NeuralNetwork/Node/NeuronTest.php @@ -46,7 +46,7 @@ class NeuronTest extends TestCase $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 a1915a3..0000000 --- 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 0000000..96cc110 --- /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 dd0a185..4979860 100644 --- a/tests/Phpml/FeatureExtraction/StopWordsTest.php +++ b/tests/Phpml/FeatureExtraction/StopWordsTest.php @@ -45,4 +45,12 @@ class StopWordsTest extends TestCase $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 6f11f68..d2f746d 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 c5d00bf..bde49a2 100644 --- a/src/Phpml/Classification/MLPClassifier.php +++ b/src/Phpml/Classification/MLPClassifier.php @@ -4,17 +4,8 @@ declare(strict_types=1); 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 3e2bff5..277aecd 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -108,4 +108,8 @@ class InvalidArgumentException extends \Exception 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 cd90e3f..b20f6bb 100644 --- a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php +++ b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php @@ -32,6 +32,14 @@ abstract class LayeredNetwork implements Network 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 5d7f94e..2503774 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -5,6 +5,7 @@ declare(strict_types=1); 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; 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 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator throw InvalidArgumentException::invalidLayersNumber(); } - $nClasses = count($classes); - if ($nClasses < 2) { + if (count($classes) < 2) { throw InvalidArgumentException::invalidClassesNumber(); } + $this->classes = array_values($classes); - $this->iterations = $iterations; + $this->inputLayerFeatures = $inputLayerFeatures; + $this->hiddenLayers = $hiddenLayers; + $this->activationFunction = $activationFunction; + $this->theta = $theta; - $this->addInputLayer($inputLayerFeatures); - $this->addNeuronLayers($hiddenLayers, $activationFunction); - $this->addNeuronLayers([$nClasses], $activationFunction); + $this->initNetwork(); + } + + /** + * @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 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator */ 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 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator * @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 741db2b..ba90b45 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 @@ class Backpropagation */ 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 @@ class Backpropagation } $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 9f8b3fc..3a009c3 100644 --- a/tests/Phpml/Classification/MLPClassifierTest.php +++ b/tests/Phpml/Classification/MLPClassifierTest.php @@ -5,8 +5,8 @@ declare(strict_types=1); 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 @@ class MLPClassifierTest extends TestCase 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 @@ class MLPClassifierTest extends TestCase $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 @@ class MLPClassifierTest extends TestCase $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 @@ class MLPClassifierTest extends TestCase 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 a6b3d56..ca2914a 100644 --- a/src/Phpml/Pipeline.php +++ b/src/Phpml/Pipeline.php @@ -67,8 +67,11 @@ class Pipeline implements Estimator */ 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 @@ class Pipeline implements Estimator 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 4454dee..92a6223 100644 --- a/tests/Phpml/PipelineTest.php +++ b/tests/Phpml/PipelineTest.php @@ -6,11 +6,13 @@ namespace tests; 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 @@ class PipelineTest extends TestCase $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 48b7b48..9adddb2 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 bde49a2..7f9043b 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 277aecd..b618d00 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -67,6 +67,8 @@ class InvalidArgumentException extends \Exception } /** + * @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 2503774..b7364b2 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -114,6 +114,9 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, /** * @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 da8b81b..c0c71f3 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -101,7 +101,8 @@ class DecisionTree implements Classifier } 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 b94de28..df648e8 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 bc6a3c9..90ef4d1 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 f4a8791..145a992 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -99,11 +99,11 @@ class Perceptron implements Classifier, IncrementalEstimator $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 38ae9c4..bba5d09 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 fa2401a..82e860a 100644 --- a/src/Phpml/Helper/Optimizer/StochasticGD.php +++ b/src/Phpml/Helper/Optimizer/StochasticGD.php @@ -243,7 +243,9 @@ class StochasticGD extends Optimizer 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 7f0ec4b..642e8b3 100644 --- a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -301,7 +301,7 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition } $n = $n - 2; $iter = 0; - // No convergence yet + // No convergence yet } else { // Form shift $x = $this->H[$n][$n]; @@ -715,7 +715,7 @@ class EigenvalueDecomposition } 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 @@ class EigenvalueDecomposition } } } - // 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 8c8781d..779b895 100644 --- a/src/Phpml/Math/Statistic/Covariance.php +++ b/src/Phpml/Math/Statistic/Covariance.php @@ -150,7 +150,12 @@ class Covariance $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 e32eeb0..d4e5651 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 c6ec017..b29bfa5 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 @@ class SupportVectorMachine */ 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 2492fae..a8a8826 100644 --- a/tests/Phpml/Preprocessing/NormalizerTest.php +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -124,10 +124,12 @@ class NormalizerTest extends TestCase // 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 09668a9..24787e1 100644 --- a/src/Phpml/Helper/Optimizer/Optimizer.php +++ b/src/Phpml/Helper/Optimizer/Optimizer.php @@ -64,5 +64,5 @@ abstract class Optimizer * @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 44bcd14..67a65a4 100644 --- a/src/Phpml/Helper/Optimizer/ConjugateGradient.php +++ b/src/Phpml/Helper/Optimizer/ConjugateGradient.php @@ -352,11 +352,11 @@ class mp * 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 79b5610..bac2747 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 889f720..7d2f451 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 88043dd..18ae994 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 84f4b38..6979497 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 0ea0c2a..9fb8baa 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 c0c71f3..6cf6870 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -144,7 +144,7 @@ class DecisionTree implements Classifier // 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 @@ class DecisionTree implements Classifier if ($split->evaluate($record)) { $leftRecords[] = $recordNo; } else { - $rightRecords[]= $recordNo; + $rightRecords[] = $recordNo; } // Group remaining targets @@ -183,7 +183,7 @@ class DecisionTree implements Classifier $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 787108f..53c3386 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 @@ class DecisionTreeLeaf 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 @@ class DecisionTreeLeaf 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 @@ class DecisionTreeLeaf * Returns HTML representation of the node including children nodes * * @param $columnNames + * * @return string */ public function getHTML($columnNames = null) @@ -135,29 +138,34 @@ class DecisionTreeLeaf } 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 38571da..95daf49 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 @@ class AdaBoost implements Classifier * 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 @@ class AdaBoost implements Classifier // 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 @@ class AdaBoost implements Classifier { $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 @@ class AdaBoost implements Classifier * Calculates alpha of a classifier * * @param float $errorRate + * * @return float */ protected function calculateAlpha(float $errorRate) @@ -227,6 +229,7 @@ class AdaBoost implements Classifier if ($errorRate == 0) { $errorRate = 1e-10; } + return 0.5 * log((1 - $errorRate) / $errorRate); } @@ -234,7 +237,7 @@ class AdaBoost implements Classifier * Updates the sample weights * * @param Classifier $classifier - * @param float $alpha + * @param float $alpha */ protected function updateWeights(Classifier $classifier, float $alpha) { @@ -254,6 +257,7 @@ class AdaBoost implements Classifier /** * @param array $sample + * * @return mixed */ public function predictSample(array $sample) @@ -264,6 +268,6 @@ class AdaBoost implements Classifier $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 1af155d..716a6bc 100644 --- a/src/Phpml/Classification/Ensemble/Bagging.php +++ b/src/Phpml/Classification/Ensemble/Bagging.php @@ -84,10 +84,11 @@ class Bagging implements Classifier 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 @@ class Bagging implements Classifier * names are neglected. * * @param string $classifier - * @param array $classifierOptions + * @param array $classifierOptions * * @return $this */ @@ -135,6 +136,7 @@ class Bagging implements Classifier /** * @param int $index + * * @return array */ protected function getRandomSubset(int $index) @@ -168,6 +170,7 @@ class Bagging implements Classifier $classifiers[] = $this->initSingleClassifier($obj); } + return $classifiers; } @@ -183,6 +186,7 @@ class Bagging implements Classifier /** * @param array $sample + * * @return mixed */ protected function predictSample(array $sample) @@ -196,6 +200,7 @@ class Bagging implements Classifier $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 7849cd8..e6677cb 100644 --- a/src/Phpml/Classification/Ensemble/RandomForest.php +++ b/src/Phpml/Classification/Ensemble/RandomForest.php @@ -50,7 +50,7 @@ class RandomForest extends Bagging 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 @@ class RandomForest extends Bagging } $this->featureSubsetRatio = $ratio; + return $this; } @@ -74,7 +75,7 @@ class RandomForest extends Bagging 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 @@ class RandomForest extends Bagging * 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 @@ class RandomForest extends Bagging 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 df648e8..d10fff4 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 @@ class Adaline extends Perceptron 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 5a3247f..776a6a2 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -106,7 +106,7 @@ class DecisionStump extends WeightedClassifier 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 @@ class DecisionStump extends WeightedClassifier * * @param array $samples * @param array $targets - * @param int $col + * @param int $col * * @return array */ @@ -192,8 +192,8 @@ class DecisionStump extends WeightedClassifier } // 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 @@ class DecisionStump extends WeightedClassifier /** * @param array $samples * @param array $targets - * @param int $col + * @param int $col * * @return array */ @@ -217,7 +217,7 @@ class DecisionStump extends WeightedClassifier { $values = array_column($samples, $col); $valueCounts = array_count_values($values); - $distinctVals= array_keys($valueCounts); + $distinctVals = array_keys($valueCounts); $split = null; @@ -236,7 +236,6 @@ class DecisionStump extends WeightedClassifier return $split; } - /** * * @param mixed $leftValue @@ -264,10 +263,10 @@ class DecisionStump extends WeightedClassifier * 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 @@ class DecisionStump extends WeightedClassifier $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 @@ class DecisionStump extends WeightedClassifier // 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 @@ class DecisionStump extends WeightedClassifier */ 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 90ef4d1..0447ef8 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 @@ class LogisticRegression extends Adaline ) { $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 @@ class LogisticRegression extends Adaline 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 145a992..000059f 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 @@ class Perceptron implements Classifier, IncrementalEstimator $prediction = $this->outputClass($sample); $gradient = $prediction - $target; - $error = $gradient**2; + $error = $gradient ** 2; return [$error, $gradient]; }; @@ -231,6 +231,7 @@ class Perceptron implements Classifier, IncrementalEstimator * 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 @@ class Perceptron implements Classifier, IncrementalEstimator * 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 @@ class Perceptron implements Classifier, IncrementalEstimator 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 7f9043b..dfb5394 100644 --- a/src/Phpml/Classification/MLPClassifier.php +++ b/src/Phpml/Classification/MLPClassifier.php @@ -9,7 +9,6 @@ use Phpml\NeuralNetwork\Network\MultilayerPerceptron; class MLPClassifier extends MultilayerPerceptron implements Classifier { - /** * @param mixed $target * @@ -22,6 +21,7 @@ class MLPClassifier extends MultilayerPerceptron implements Classifier if (!in_array($target, $this->classes)) { throw InvalidArgumentException::invalidTarget($target); } + return array_search($target, $this->classes); } @@ -42,6 +42,7 @@ class MLPClassifier extends MultilayerPerceptron implements Classifier $max = $value; } } + return $this->classes[$predictedClass]; } diff --git a/src/Phpml/Classification/NaiveBayes.php b/src/Phpml/Classification/NaiveBayes.php index 1a634da..8daaf86 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 @@ class NaiveBayes implements Classifier /** * 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 @@ class NaiveBayes implements Classifier $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 @@ class NaiveBayes implements Classifier // 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 @@ class NaiveBayes implements Classifier $samples[] = $this->samples[$i]; } } + return $samples; } /** * @param array $sample + * * @return mixed */ protected function predictSample(array $sample) @@ -174,7 +179,7 @@ class NaiveBayes implements Classifier $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 @@ class NaiveBayes implements Classifier arsort($predictions, SORT_NUMERIC); reset($predictions); + return key($predictions); } } diff --git a/src/Phpml/Clustering/FuzzyCMeans.php b/src/Phpml/Clustering/FuzzyCMeans.php index c6a3c46..da1398e 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 @@ class FuzzyCMeans implements Clusterer * * @param int $row * @param int $col + * * @return float */ protected function getDistanceCalc(int $row, int $col) @@ -179,6 +180,7 @@ class FuzzyCMeans implements Clusterer $val = pow($dist1 / $dist2, 2.0 / ($this->fuzziness - 1)); $sum += $val; } + return $sum; } @@ -212,13 +214,14 @@ class FuzzyCMeans implements Clusterer /** * @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 0276880..14c1760 100644 --- a/src/Phpml/Clustering/KMeans/Space.php +++ b/src/Phpml/Clustering/KMeans/Space.php @@ -66,7 +66,7 @@ class Space extends SplObjectStorage /** * @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 6c0ef05..5e27a13 100644 --- a/src/Phpml/DimensionReduction/EigenTransformerBase.php +++ b/src/Phpml/DimensionReduction/EigenTransformerBase.php @@ -54,7 +54,7 @@ abstract class EigenTransformerBase { $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 94e18c9..908c441 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 @@ class KernelPCA extends PCA { $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 @@ class KernelPCA extends PCA */ 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 @@ class KernelPCA extends PCA 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 @@ class KernelPCA extends PCA // 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 @@ class KernelPCA extends PCA 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 e094c35..a2df627 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 @@ class LDA extends EigenTransformerBase 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 @@ class LDA extends EigenTransformerBase 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 @@ class LDA extends EigenTransformerBase 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 @@ class LDA extends EigenTransformerBase 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 @@ class LDA extends EigenTransformerBase 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 acaa8e0..7d3fd4f 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 @@ class PCA extends EigenTransformerBase /** * @param array $data - * @param int $n + * @param int $n */ protected function calculateMeans(array $data, int $n) { @@ -129,7 +129,7 @@ class PCA extends EigenTransformerBase 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 b618d00..313ca79 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -73,7 +73,7 @@ class InvalidArgumentException extends \Exception */ 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 9335775..61f7e65 100644 --- a/src/Phpml/FeatureExtraction/TfIdfTransformer.php +++ b/src/Phpml/FeatureExtraction/TfIdfTransformer.php @@ -32,7 +32,7 @@ class TfIdfTransformer implements Transformer $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 8d71fbc..72757df 100644 --- a/src/Phpml/Helper/OneVsRest.php +++ b/src/Phpml/Helper/OneVsRest.php @@ -109,6 +109,7 @@ trait OneVsRest // multiple instances of this classifier $classifier = clone $this; $classifier->reset(); + return $classifier; } @@ -121,6 +122,7 @@ trait OneVsRest * * @param array $targets * @param mixed $label + * * @return array Binarized targets and target's labels */ private function binarizeTargets($targets, $label) @@ -131,10 +133,10 @@ trait OneVsRest } $labels = [$label, $notLabel]; + return [$targets, $labels]; } - /** * @param array $sample * @@ -153,6 +155,7 @@ trait OneVsRest } arsort($probs, SORT_NUMERIC); + return key($probs); } diff --git a/src/Phpml/Helper/Optimizer/GD.php b/src/Phpml/Helper/Optimizer/GD.php index b88b0c7..8babc7d 100644 --- a/src/Phpml/Helper/Optimizer/GD.php +++ b/src/Phpml/Helper/Optimizer/GD.php @@ -42,7 +42,7 @@ class GD extends StochasticGD $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 @@ class GD extends StochasticGD 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 82e860a..df29261 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 @@ class StochasticGD extends Optimizer * 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 0788193..2af835e 100644 --- a/src/Phpml/Math/Distance/Minkowski.php +++ b/src/Phpml/Math/Distance/Minkowski.php @@ -43,6 +43,6 @@ class Minkowski implements Distance $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 642e8b3..c67673b 100644 --- a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -20,10 +20,12 @@ declare(strict_types=1); * * @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 @@ class EigenvalueDecomposition $this->e[0] = 0.0; } - /** * Symmetric tridiagonal QL algorithm. * @@ -239,7 +247,7 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition 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 @@ class EigenvalueDecomposition } } - /** * Nonsymmetric reduction to Hessenberg form. * @@ -341,7 +348,7 @@ class EigenvalueDecomposition */ private function orthes() { - $low = 0; + $low = 0; $high = $this->n - 1; for ($m = $low + 1; $m <= $high - 1; ++$m) { @@ -451,7 +458,7 @@ class EigenvalueDecomposition { // 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 @@ class EigenvalueDecomposition // 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 @@ class EigenvalueDecomposition } 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 @@ class EigenvalueDecomposition 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 @@ class EigenvalueDecomposition } $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 @@ class EigenvalueDecomposition 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 @@ class EigenvalueDecomposition $this->V[$i][$j] = $z; } } - } // end hqr2 + } /** * Return the eigenvector matrix * - * @access public * * @return array */ @@ -899,4 +905,4 @@ class EigenvalueDecomposition return $D; } -} // class EigenvalueDecomposition +} diff --git a/src/Phpml/Math/LinearAlgebra/LUDecomposition.php b/src/Phpml/Math/LinearAlgebra/LUDecomposition.php index de6a15d..7a143f1 100644 --- a/src/Phpml/Math/LinearAlgebra/LUDecomposition.php +++ b/src/Phpml/Math/LinearAlgebra/LUDecomposition.php @@ -17,11 +17,14 @@ declare(strict_types=1); * @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 @@ class LUDecomposition // 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 @@ class LUDecomposition } } } - } // function __construct() - + } /** * Get lower triangular factor. @@ -150,9 +156,9 @@ class LUDecomposition } } } - return new Matrix($L); - } // function getL() + return new Matrix($L); + } /** * Get upper triangular factor. @@ -171,9 +177,9 @@ class LUDecomposition } } } - return new Matrix($U); - } // function getU() + return new Matrix($U); + } /** * Return pivot permutation vector. @@ -183,8 +189,7 @@ class LUDecomposition public function getPivot() { return $this->piv; - } // function getPivot() - + } /** * Alias for getPivot @@ -194,8 +199,7 @@ class LUDecomposition public function getDoublePivot() { return $this->getPivot(); - } // function getDoublePivot() - + } /** * Is the matrix nonsingular? @@ -211,8 +215,7 @@ class LUDecomposition } return true; - } // function isNonsingular() - + } /** * Count determinants @@ -233,8 +236,7 @@ class LUDecomposition } return $d; - } // function det() - + } /** * Solve A*X = B @@ -257,7 +259,7 @@ class LUDecomposition // 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 @@ class LUDecomposition } } } + return $X; - } // function solve() + } /** * @param array $matrix @@ -302,4 +305,4 @@ class LUDecomposition return $R; } -} // class LUDecomposition +} diff --git a/src/Phpml/Math/Matrix.php b/src/Phpml/Math/Matrix.php index 3c31052..fd91234 100644 --- a/src/Phpml/Math/Matrix.php +++ b/src/Phpml/Math/Matrix.php @@ -122,7 +122,6 @@ class Matrix 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 779b895..e0a239d 100644 --- a/src/Phpml/Math/Statistic/Covariance.php +++ b/src/Phpml/Math/Statistic/Covariance.php @@ -80,7 +80,7 @@ class Covariance } 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 d09edba..ae4c9a6 100644 --- a/src/Phpml/Math/Statistic/Gaussian.php +++ b/src/Phpml/Math/Statistic/Gaussian.php @@ -39,7 +39,8 @@ class Gaussian // 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 @@ class Gaussian 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 bd9657e..6dd9853 100644 --- a/src/Phpml/Math/Statistic/Mean.php +++ b/src/Phpml/Math/Statistic/Mean.php @@ -34,7 +34,7 @@ class Mean 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 b7364b2..21510c4 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -138,6 +138,7 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, /** * @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 c61b447..fc00030 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 @@ class Normalizer implements Preprocessor $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 @@ class Normalizer implements Preprocessor 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 db2d810..3b61166 100644 --- a/tests/Phpml/Classification/DecisionTreeTest.php +++ b/tests/Phpml/Classification/DecisionTreeTest.php @@ -11,20 +11,20 @@ use PHPUnit\Framework\TestCase; 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 @@ class DecisionTreeTest extends TestCase array_walk($input, function (&$v) { array_splice($v, 4, 1); }); + return [$input, $targets]; } @@ -54,6 +55,7 @@ class DecisionTreeTest extends TestCase $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 002697e..a00b176 100644 --- a/tests/Phpml/Classification/Ensemble/BaggingTest.php +++ b/tests/Phpml/Classification/Ensemble/BaggingTest.php @@ -13,25 +13,25 @@ use PHPUnit\Framework\TestCase; 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 @@ class BaggingTest extends TestCase $classifier = new Bagging($numBaseClassifiers); $classifier->setSubsetRatio(1.0); $classifier->setClassifer(DecisionTree::class, ['depth' => 10]); + return $classifier; } @@ -104,7 +105,7 @@ class BaggingTest extends TestCase { return [ DecisionTree::class => ['depth' => 5], - NaiveBayes::class => [] + NaiveBayes::class => [] ]; } @@ -113,7 +114,7 @@ class BaggingTest extends TestCase // 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 @@ class BaggingTest extends TestCase 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 be587ef..8468893 100644 --- a/tests/Phpml/Classification/Ensemble/RandomForestTest.php +++ b/tests/Phpml/Classification/Ensemble/RandomForestTest.php @@ -14,12 +14,13 @@ class RandomForestTest extends BaggingTest { $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 3a009c3..db30afd 100644 --- a/tests/Phpml/Classification/MLPClassifierTest.php +++ b/tests/Phpml/Classification/MLPClassifierTest.php @@ -180,6 +180,7 @@ class MLPClassifierTest extends TestCase [0, 1, 2] ); } + /** * @expectedException \Phpml\Exception\InvalidArgumentException */ diff --git a/tests/Phpml/Clustering/FuzzyCMeansTest.php b/tests/Phpml/Clustering/FuzzyCMeansTest.php index 85285b2..68cc0db 100644 --- a/tests/Phpml/Clustering/FuzzyCMeansTest.php +++ b/tests/Phpml/Clustering/FuzzyCMeansTest.php @@ -21,6 +21,7 @@ class FuzzyCMeansTest extends TestCase } } $this->assertCount(0, $samples); + return $fcm; } diff --git a/tests/Phpml/DimensionReduction/LDATest.php b/tests/Phpml/DimensionReduction/LDATest.php index 5ebe018..713e205 100644 --- a/tests/Phpml/DimensionReduction/LDATest.php +++ b/tests/Phpml/DimensionReduction/LDATest.php @@ -57,7 +57,7 @@ class LDATest extends TestCase // 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 8f65e98..a4784f9 100644 --- a/tests/Phpml/DimensionReduction/PCATest.php +++ b/tests/Phpml/DimensionReduction/PCATest.php @@ -47,7 +47,7 @@ class PCATest extends TestCase // 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 4bca1bd..e2c615e 100644 --- a/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php +++ b/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php @@ -22,7 +22,7 @@ class EigenDecompositionTest extends TestCase [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 @@ class EigenDecompositionTest extends TestCase $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 @@ class EigenDecompositionTest extends TestCase $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 @@ class EigenDecompositionTest extends TestCase // 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 6bbf63b..a0c9700 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 066aad1..400b6d1 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 @@ class ModelManagerTest extends TestCase */ 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 a8a8826..5c8efb1 100644 --- a/tests/Phpml/Preprocessing/NormalizerTest.php +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -106,9 +106,9 @@ class NormalizerTest extends TestCase // 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 b2e9407..65ff515 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 @@ class CsvDataset extends ArrayDataset } 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 44a112b..a3a377e 100644 --- a/tests/Phpml/Dataset/CsvDatasetTest.php +++ b/tests/Phpml/Dataset/CsvDatasetTest.php @@ -36,4 +36,14 @@ class CsvDatasetTest extends TestCase $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 0000000..f8f3c40 --- /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 53c3386..9002458 100644 --- a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -138,9 +138,11 @@ class DecisionTreeLeaf } 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 0000000..72f1b95 --- /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 9adddb2..9d649c5 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 0000000..0963e09 --- /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 0000000..b0c6ecf --- /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 0000000..76674a0 --- /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 0000000..a390bf0 --- /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 238a7d2..ad5e180 100644 --- a/src/Phpml/SupportVectorMachine/DataTransformer.php +++ b/src/Phpml/SupportVectorMachine/DataTransformer.php @@ -57,7 +57,7 @@ class DataTransformer $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 65d5ea5..c715d6f 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 72f1b95..854e4e5 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 3b61166..f0ebc04 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 8c17752..8b8d0db 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 a00b176..a158e3e 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 8468893..cc1cd0f 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 ea9db77..aeee5ef 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 da69598..5989462 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 4060ce3..678544e 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 132a6d7..c01da33 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 9b2171a..82c69eb 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 89c27e3..f143eef 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 2d959ac..31fc1e6 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 68cc0db..39f2643 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 c25306b..43d41ee 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 14b2d7d..e586fc8 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 713e205..637fb1e 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 a4784f9..c26782a 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 e2c615e..8293d5e 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 4b025a3..d2abe7b 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 e3e0852..2f5c31b 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 5c8efb1..542eef5 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 96c97dc..f4405f4 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 d334973..e8f77de 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 33d52c0..9aa1558 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 ca9f299..1953829 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 1133835..97fa833 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 3a6abba..09db222 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 6cf6870..feab32e 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -225,9 +225,9 @@ class DecisionTree implements Classifier // 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 @@ class DecisionTree implements Classifier $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 9002458..b658c21 100644 --- a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -81,7 +81,7 @@ class DecisionTreeLeaf 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 @@ class DecisionTreeLeaf $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 776a6a2..2780638 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -285,7 +285,7 @@ class DecisionStump extends WeightedClassifier } $target = $targets[$index]; - if (strval($predicted) != strval($targets[$index])) { + if ((string) $predicted != (string) $targets[$index]) { $wrong += $this->weights[$index]; } @@ -300,7 +300,7 @@ class DecisionStump extends WeightedClassifier 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 @@ class DecisionStump extends WeightedClassifier 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 0447ef8..e3f0482 100644 --- a/src/Phpml/Classification/Linear/LogisticRegression.php +++ b/src/Phpml/Classification/Linear/LogisticRegression.php @@ -288,7 +288,7 @@ class LogisticRegression extends Adaline { $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 000059f..1c534a2 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -113,7 +113,7 @@ class Perceptron implements Classifier, IncrementalEstimator // 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 @@ class Perceptron implements Classifier, IncrementalEstimator { $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 8293d5e..1db2c66 100644 --- a/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php +++ b/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php @@ -36,7 +36,8 @@ class EigenDecompositionTest extends TestCase // (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 313ca79..2c32a6a 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -39,7 +39,7 @@ class InvalidArgumentException extends \Exception */ 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 @@ class InvalidArgumentException extends \Exception */ 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 @@ class InvalidArgumentException extends \Exception */ 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 @@ class InvalidArgumentException extends \Exception 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 b29bfa5..9ee3c3b 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Phpml\SupportVectorMachine; +use Phpml\Exception\InvalidArgumentException; use Phpml\Helper\Trainable; class SupportVectorMachine @@ -140,25 +141,29 @@ class SupportVectorMachine /** * @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 @@ class SupportVectorMachine 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 1953829..4cc6d57 100644 --- a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php @@ -80,4 +80,34 @@ SV $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 ebb1f5d..9e65063 100644 --- a/src/Phpml/Clustering/DBSCAN.php +++ b/src/Phpml/Clustering/DBSCAN.php @@ -94,17 +94,19 @@ class DBSCAN implements Clusterer { $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 31fc1e6..a093b20 100644 --- a/tests/Phpml/Clustering/DBSCANTest.php +++ b/tests/Phpml/Clustering/DBSCANTest.php @@ -31,4 +31,17 @@ class DBSCANTest extends TestCase $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 9d649c5..4c8fd85 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 8d284af..0798c2e 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 b658c21..d88c8c9 100644 --- a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Phpml\Classification\DecisionTree; +use Phpml\Math\Comparison; + class DecisionTreeLeaf { /** @@ -79,12 +81,7 @@ class DecisionTreeLeaf $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 2780638..179c117 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -8,6 +8,7 @@ use Phpml\Helper\Predictable; use Phpml\Helper\OneVsRest; use Phpml\Classification\WeightedClassifier; use Phpml\Classification\DecisionTree; +use Phpml\Math\Comparison; class DecisionStump extends WeightedClassifier { @@ -236,29 +237,6 @@ class DecisionStump extends WeightedClassifier 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 @@ class DecisionStump extends WeightedClassifier $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 @@ class DecisionStump extends WeightedClassifier */ 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 2c32a6a..8dcbd03 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -157,4 +157,14 @@ class InvalidArgumentException extends \Exception { 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 72757df..7776ccd 100644 --- a/src/Phpml/Helper/OneVsRest.php +++ b/src/Phpml/Helper/OneVsRest.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Phpml\Helper; +use Phpml\Classification\Classifier; + trait OneVsRest { /** @@ -100,7 +102,7 @@ trait OneVsRest /** * 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 0000000..1c8b6aa --- /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 0000000..2d41273 --- /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 4c8fd85..f3ee24c 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 | 58 ++------ 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, 302 insertions(+), 1734 deletions(-) diff --git a/docs/math/distance.md b/docs/math/distance.md index fd491ea..6970742 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 362f25a..ee9c383 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 @@ class Apriori implements Associator * * @param mixed[] $set * @param mixed[] $subset - * - * @return float */ private function confidence(array $set, array $subset) : float { @@ -276,8 +271,6 @@ class Apriori implements Associator * @see \Phpml\Association\Apriori::samples * * @param mixed[] $sample - * - * @return float */ private function support(array $sample) : float { @@ -290,8 +283,6 @@ class Apriori implements Associator * @see \Phpml\Association\Apriori::samples * * @param mixed[] $sample - * - * @return int */ private function frequency(array $sample) : int { @@ -307,8 +298,6 @@ class Apriori implements Associator * * @param mixed[][] $system * @param mixed[] $set - * - * @return bool */ private function contains(array $system, array $set) : bool { @@ -322,8 +311,6 @@ class Apriori implements Associator * * @param mixed[] $set * @param mixed[] $subset - * - * @return bool */ private function subset(array $set, array $subset) : bool { @@ -335,8 +322,6 @@ class Apriori implements Associator * * @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 feab32e..c8e8674 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -4,11 +4,11 @@ declare(strict_types=1); 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 @@ class DecisionTree implements Classifier /** * @param array $records * @param int $depth - * - * @return DecisionTreeLeaf */ protected function getSplitLeaf(array $records, int $depth = 0) : DecisionTreeLeaf { @@ -190,11 +184,6 @@ class DecisionTree implements Classifier 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 @@ class DecisionTree implements Classifier /** * @param mixed $baseValue - * @param array $colValues - * @param array $targets - * - * @return float */ public function getGiniIndex($baseValue, array $colValues, array $targets) : float { @@ -342,8 +327,6 @@ class DecisionTree implements Classifier /** * @param array $columnValues - * - * @return bool */ protected static function isCategoricalColumn(array $columnValues) : bool { @@ -376,8 +359,6 @@ class DecisionTree implements Classifier * 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 @@ class DecisionTree implements Classifier /** * 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 @@ class DecisionTree implements Classifier * 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 @@ class DecisionTree implements Classifier return $this; } - /** - * @return string - */ - public function getHtml() + public function getHtml() : string { return $this->tree->getHTML($this->columnNames); } @@ -436,10 +410,8 @@ class DecisionTree implements Classifier * 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 @@ class DecisionTree implements Classifier /** * 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 @@ class DecisionTree implements Classifier } /** - * @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 d88c8c9..2bcc3ac 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 @@ class DecisionTreeLeaf /** * 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 @@ class DecisionTreeLeaf /** * 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 95daf49..9fdd65b 100644 --- a/src/Phpml/Classification/Ensemble/AdaBoost.php +++ b/src/Phpml/Classification/Ensemble/AdaBoost.php @@ -4,13 +4,13 @@ declare(strict_types=1); 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 @@ class AdaBoost implements Classifier /** * 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 @@ class AdaBoost implements Classifier } /** - * @param array $samples - * @param array $targets - * * @throws \Exception */ public function train(array $samples, array $targets) @@ -143,10 +135,8 @@ class AdaBoost implements Classifier /** * 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 @@ class AdaBoost implements Classifier /** * 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 @@ class AdaBoost implements Classifier /** * 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 @@ class AdaBoost implements 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 @@ class AdaBoost implements Classifier /** * Updates the sample weights - * - * @param Classifier $classifier - * @param float $alpha */ protected function updateWeights(Classifier $classifier, float $alpha) { @@ -256,8 +233,6 @@ class AdaBoost implements Classifier } /** - * @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 716a6bc..ebc7528 100644 --- a/src/Phpml/Classification/Ensemble/Bagging.php +++ b/src/Phpml/Classification/Ensemble/Bagging.php @@ -4,10 +4,10 @@ declare(strict_types=1); 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 @@ class Bagging implements Classifier * 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 @@ class Bagging implements Classifier * 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 @@ class Bagging implements Classifier 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 @@ class Bagging implements Classifier } } - /** - * @param int $index - * - * @return array - */ - protected function getRandomSubset(int $index) + protected function getRandomSubset(int $index) : array { $samples = []; $targets = []; @@ -154,10 +138,7 @@ class Bagging implements Classifier return [$samples, $targets]; } - /** - * @return array - */ - protected function initClassifiers() + protected function initClassifiers() : array { $classifiers = []; for ($i = 0; $i < $this->numClassifier; ++$i) { @@ -185,8 +166,6 @@ class Bagging implements 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 e6677cb..4928ea5 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 @@ class RandomForest extends Bagging /** * RandomForest algorithm is usable *only* with DecisionTree * - * @param string $classifier - * @param array $classifierOptions - * * @return $this * * @throws \Exception @@ -85,10 +80,8 @@ class RandomForest extends Bagging * 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 @@ class RandomForest extends Bagging * 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 b52c95b..c7783d8 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 @@ class KNearestNeighbors implements Classifier } /** - * @param array $sample - * * @return mixed */ protected function predictSample(array $sample) @@ -61,13 +58,9 @@ class KNearestNeighbors implements Classifier } /** - * @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 d10fff4..bcc014e 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 @@ class Adaline extends Perceptron /** * 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 179c117..014dceb 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -4,10 +4,10 @@ declare(strict_types=1); 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 @@ class DecisionStump extends WeightedClassifier } /** - * @param array $samples - * @param array $targets - * @param array $labels - * * @throws \Exception */ protected function trainBinary(array $samples, array $targets, array $labels) @@ -151,8 +145,6 @@ class DecisionStump extends WeightedClassifier * 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 @@ class DecisionStump extends WeightedClassifier /** * 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 @@ class DecisionStump extends WeightedClassifier 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 @@ class DecisionStump extends WeightedClassifier /** * 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 @@ class DecisionStump extends WeightedClassifier * 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 @@ class DecisionStump extends WeightedClassifier } /** - * @param array $sample - * * @return mixed */ protected function predictSampleBinary(array $sample) @@ -322,17 +289,11 @@ class DecisionStump extends WeightedClassifier 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 e3f0482..bd100ba 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 @@ class LogisticRegression extends Adaline /** * Sets the learning rate if gradient descent algorithm is * selected for training - * - * @param float $learningRate */ public function setLearningRate(float $learningRate) { @@ -113,8 +105,6 @@ class LogisticRegression extends Adaline /** * 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 @@ class LogisticRegression extends Adaline * 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 @@ class LogisticRegression extends Adaline * * @param array $samples * @param array $targets - * @param \Closure $gradientFunc */ protected function runConjugateGradient(array $samples, array $targets, \Closure $gradientFunc) { @@ -170,11 +156,9 @@ class LogisticRegression extends Adaline /** * 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 @@ class LogisticRegression extends Adaline /** * 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 @@ class LogisticRegression extends Adaline /** * 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 @@ class LogisticRegression extends Adaline * * 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 1c534a2..d5f424b 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -4,13 +4,13 @@ declare(strict_types=1); namespace Phpml\Classification\Linear; -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; +use Phpml\Helper\OneVsRest; +use Phpml\Helper\Optimizer\GD; +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 @@ class Perceptron implements Classifier, IncrementalEstimator $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 @@ class Perceptron implements Classifier, IncrementalEstimator * 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 @@ class Perceptron implements Classifier, IncrementalEstimator /** * Returns the cost values obtained during the training. - * - * @return array */ - public function getCostValues() + public function getCostValues() : array { return $this->costValues; } @@ -163,9 +148,6 @@ class Perceptron implements Classifier, IncrementalEstimator /** * 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 @@ class Perceptron implements Classifier, IncrementalEstimator /** * 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 @@ class Perceptron implements Classifier, IncrementalEstimator /** * 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 @@ class Perceptron implements Classifier, IncrementalEstimator /** * 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 @@ class Perceptron implements Classifier, IncrementalEstimator /** * 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 @@ class Perceptron implements Classifier, IncrementalEstimator * 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 dfb5394..64e6f50 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 @@ class MLPClassifier extends MultilayerPerceptron implements Classifier } /** - * @param array $sample - * * @return mixed */ protected function predictSample(array $sample) @@ -47,7 +43,6 @@ class MLPClassifier extends MultilayerPerceptron implements Classifier } /** - * @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 8daaf86..08073da 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 @@ class NaiveBayes implements Classifier /** * 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 @@ class NaiveBayes implements Classifier /** * 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 @@ class NaiveBayes implements Classifier /** * 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 @@ class NaiveBayes implements Classifier } /** - * @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 bba5d09..8cabcbc 100644 --- a/src/Phpml/Classification/SVC.php +++ b/src/Phpml/Classification/SVC.php @@ -10,17 +10,6 @@ use Phpml\SupportVectorMachine\Type; 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 0c58b2e..ad24af2 100644 --- a/src/Phpml/Clustering/Clusterer.php +++ b/src/Phpml/Clustering/Clusterer.php @@ -6,10 +6,5 @@ namespace Phpml\Clustering; 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 9e65063..70cf302 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 @@ class DBSCAN implements Clusterer $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 @@ class DBSCAN implements Clusterer 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 @@ class DBSCAN implements Clusterer 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 da1398e..d14c0be 100644 --- a/src/Phpml/Clustering/FuzzyCMeans.php +++ b/src/Phpml/Clustering/FuzzyCMeans.php @@ -4,8 +4,8 @@ declare(strict_types=1); 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 @@ class FuzzyCMeans implements Clusterer $this->updateClusters(); } - /** - * @param int $rows - * @param int $cols - */ protected function generateRandomMembership(int $rows, int $cols) { $this->membership = []; @@ -155,14 +146,7 @@ class FuzzyCMeans implements Clusterer } } - /** - * - * @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 @@ class FuzzyCMeans implements Clusterer 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 a9e9083..0a776a4 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 @@ class KMeans implements Clusterer $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 7cb9f12..b7c7ecf 100644 --- a/src/Phpml/Clustering/KMeans/Cluster.php +++ b/src/Phpml/Clustering/KMeans/Cluster.php @@ -4,10 +4,10 @@ declare(strict_types=1); 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 @@ class Cluster extends Point implements IteratorAggregate, Countable $this->points = new SplObjectStorage(); } - /** - * @return array - */ - public function getPoints() + public function getPoints() : array { $points = []; foreach ($this->points as $point) { @@ -45,10 +38,7 @@ class Cluster extends Point implements IteratorAggregate, Countable return $points; } - /** - * @return array - */ - public function toArray() + public function toArray() : array { return [ 'centroid' => parent::toArray(), @@ -56,14 +46,7 @@ class Cluster extends Point implements IteratorAggregate, Countable ]; } - /** - * @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 @@ class Cluster extends Point implements IteratorAggregate, Countable 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 @@ class Cluster extends Point implements IteratorAggregate, Countable { 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 ce1c44e..08fa11e 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 @@ class Point implements ArrayAccess } /** - * @param array $points - * * @return mixed */ public function getClosest(array $points) @@ -77,20 +66,15 @@ class Point implements ArrayAccess 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 14c1760..bf2a345 100644 --- a/src/Phpml/Clustering/KMeans/Space.php +++ b/src/Phpml/Clustering/KMeans/Space.php @@ -4,10 +4,10 @@ declare(strict_types=1); 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 @@ class Space extends SplObjectStorage $this->dimension = $dimension; } - /** - * @return array - */ - public function toArray() + public function toArray() : array { $points = []; foreach ($this as $point) { @@ -41,12 +35,7 @@ class Space extends SplObjectStorage 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 @@ class Space extends SplObjectStorage } /** - * @param array $coordinates * @param null $data */ public function addPoint(array $coordinates, $data = null) @@ -77,10 +65,7 @@ class Space extends SplObjectStorage parent::attach($point, $data); } - /** - * @return int - */ - public function getDimension() + public function getDimension() : int { return $this->dimension; } @@ -107,13 +92,7 @@ class Space extends SplObjectStorage 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 @@ class Space extends SplObjectStorage } /** - * @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 @@ class Space extends SplObjectStorage } /** - * @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 @@ class Space extends SplObjectStorage return $clusters; } - /** - * @param $clusters - * - * @return bool - */ - protected function iterate($clusters) + protected function iterate($clusters) : bool { $convergence = true; @@ -209,12 +177,7 @@ class Space extends SplObjectStorage 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 @@ class Space extends SplObjectStorage 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 69c44c1..7ec1875 100644 --- a/src/Phpml/CrossValidation/RandomSplit.php +++ b/src/Phpml/CrossValidation/RandomSplit.php @@ -8,10 +8,6 @@ use Phpml\Dataset\Dataset; 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 add181c..77eab3c 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 @@ abstract class Split 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 e6c80a2..41f5d34 100644 --- a/src/Phpml/CrossValidation/StratifiedRandomSplit.php +++ b/src/Phpml/CrossValidation/StratifiedRandomSplit.php @@ -9,10 +9,6 @@ use Phpml\Dataset\Dataset; 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 @@ class StratifiedRandomSplit extends RandomSplit } /** - * @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 @@ class StratifiedRandomSplit extends RandomSplit 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 6f765fe..96feec7 100644 --- a/src/Phpml/Dataset/ArrayDataset.php +++ b/src/Phpml/Dataset/ArrayDataset.php @@ -37,7 +37,7 @@ class ArrayDataset implements Dataset /** * @return array */ - public function getSamples(): array + public function getSamples() : array { return $this->samples; } @@ -45,7 +45,7 @@ class ArrayDataset implements Dataset /** * @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 65ff515..ef33e2c 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 @@ class CsvDataset extends ArrayDataset 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 f851d85..ce75a8a 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 f7c789f..73bc4b0 100644 --- a/src/Phpml/Dataset/FilesDataset.php +++ b/src/Phpml/Dataset/FilesDataset.php @@ -8,11 +8,6 @@ use Phpml\Exception\DatasetException; class FilesDataset extends ArrayDataset { - /** - * @param string $rootPath - * - * @throws DatasetException - */ public function __construct(string $rootPath) { if (!is_dir($rootPath)) { @@ -22,9 +17,6 @@ class FilesDataset extends ArrayDataset $this->scanRootPath($rootPath); } - /** - * @param string $rootPath - */ private function scanRootPath(string $rootPath) { foreach (glob($rootPath.DIRECTORY_SEPARATOR.'*', GLOB_ONLYDIR) as $dir) { @@ -32,9 +24,6 @@ class FilesDataset extends ArrayDataset } } - /** - * @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 5e27a13..913148b 100644 --- a/src/Phpml/DimensionReduction/EigenTransformerBase.php +++ b/src/Phpml/DimensionReduction/EigenTransformerBase.php @@ -90,7 +90,7 @@ abstract class EigenTransformerBase * * @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 908c441..1c7cecb 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 @@ class KernelPCA extends PCA * 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 @@ class KernelPCA extends PCA /** * 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 @@ class KernelPCA extends PCA * 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 @@ class KernelPCA extends PCA /** * 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 @@ class KernelPCA extends PCA } } - /** - * @param array $sample - * - * @return array - */ - protected function getDistancePairs(array $sample) + protected function getDistancePairs(array $sample) : array { $kernel = $this->getKernel(); @@ -211,12 +189,7 @@ class KernelPCA extends PCA 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 @@ class KernelPCA extends PCA * 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 a2df627..8a94f46 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 @@ class LDA extends EigenTransformerBase /** * 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 @@ class LDA extends EigenTransformerBase /** * 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 @@ class LDA extends EigenTransformerBase /** * 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 @@ class LDA extends EigenTransformerBase * 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 @@ class LDA extends EigenTransformerBase * 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 @@ class LDA extends EigenTransformerBase /** * 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 @@ class LDA extends EigenTransformerBase * 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 7d3fd4f..f5cb219 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 @@ class PCA extends EigenTransformerBase * 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 @@ class PCA extends EigenTransformerBase 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 @@ class PCA extends EigenTransformerBase /** * 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 @@ class PCA extends EigenTransformerBase * 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 ca7b065..5d3e0db 100644 --- a/src/Phpml/Exception/DatasetException.php +++ b/src/Phpml/Exception/DatasetException.php @@ -6,12 +6,7 @@ namespace Phpml\Exception; 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 20b2936..39b9b03 100644 --- a/src/Phpml/Exception/FileException.php +++ b/src/Phpml/Exception/FileException.php @@ -6,32 +6,17 @@ namespace Phpml\Exception; 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 8dcbd03..d96bb33 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -6,164 +6,95 @@ namespace Phpml\Exception; 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 1b01659..a52feaa 100644 --- a/src/Phpml/Exception/MatrixException.php +++ b/src/Phpml/Exception/MatrixException.php @@ -6,26 +6,17 @@ namespace Phpml\Exception; 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 e9762d3..a7604e8 100644 --- a/src/Phpml/Exception/NormalizerException.php +++ b/src/Phpml/Exception/NormalizerException.php @@ -6,10 +6,7 @@ namespace Phpml\Exception; 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 5753eb7..913667a 100644 --- a/src/Phpml/Exception/SerializeException.php +++ b/src/Phpml/Exception/SerializeException.php @@ -6,22 +6,12 @@ namespace Phpml\Exception; 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 ad01bbb..b6717b9 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 f5fab21..eb7a4ac 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 @@ class TokenCountVectorizer implements Transformer $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 @@ class TokenCountVectorizer implements Transformer $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 @@ class TokenCountVectorizer implements Transformer } } - /** - * @param string $sample - */ private function transformSample(string &$sample) { $counts = []; @@ -122,8 +102,6 @@ class TokenCountVectorizer implements Transformer } /** - * @param string $token - * * @return int|bool */ private function getTokenIndex(string $token) @@ -135,9 +113,6 @@ class TokenCountVectorizer implements Transformer return $this->vocabulary[$token] ?? false; } - /** - * @param string $token - */ private function addTokenToVocabulary(string $token) { if ($this->isStopWord($token)) { @@ -149,19 +124,11 @@ class TokenCountVectorizer implements Transformer } } - /** - * @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 @@ class TokenCountVectorizer implements Transformer ++$this->frequencies[$token]; } - /** - * @param array $samples - */ private function checkDocumentFrequency(array &$samples) { if ($this->minDF > 0) { @@ -184,10 +148,6 @@ class TokenCountVectorizer implements Transformer } } - /** - * @param array $sample - * @param array $beyondMinimum - */ private function resetBeyondMinimum(array &$sample, array $beyondMinimum) { foreach ($beyondMinimum as $index) { @@ -195,12 +155,7 @@ class TokenCountVectorizer implements Transformer } } - /** - * @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 7776ccd..5ae126c 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 @@ trait OneVsRest $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 @@ trait OneVsRest * $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 @@ trait OneVsRest } /** - * @param array $sample - * * @return mixed */ protected function predictSample(array $sample) @@ -163,10 +150,6 @@ trait OneVsRest /** * 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 @@ trait OneVsRest * 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 @@ trait OneVsRest /** * 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 67a65a4..994971d 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 @@ class ConjugateGradient extends GD /** * 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 @@ class ConjugateGradient extends GD /** * 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 @@ class ConjugateGradient extends GD * 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 @@ class ConjugateGradient extends GD * 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 @@ class ConjugateGradient extends GD * * 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 @@ class ConjugateGradient extends GD * 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 @@ class mp /** * 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 @@ class mp /** * 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 @@ class mp /** * 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 @@ class mp /** * 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 @@ class mp /** * 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 @@ class mp /** * 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 8babc7d..ae3c6e2 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 @@ class GD extends StochasticGD /** * 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 @@ class GD extends StochasticGD 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 @@ class GD extends StochasticGD /** * 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 24787e1..ee61321 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 @@ abstract class Optimizer /** * Sets the weights manually * - * @param array $theta - * * @return $this * * @throws \Exception @@ -59,10 +55,6 @@ abstract class Optimizer /** * 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 df29261..0a622d4 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 @@ class StochasticGD extends Optimizer * 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 @@ class StochasticGD extends Optimizer * 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 @@ class StochasticGD extends Optimizer } /** - * @param float $learningRate - * * @return $this */ public function setLearningRate(float $learningRate) @@ -133,8 +125,6 @@ class StochasticGD extends Optimizer } /** - * @param int $maxIterations - * * @return $this */ public function setMaxIterations(int $maxIterations) @@ -150,14 +140,8 @@ class StochasticGD extends Optimizer * * 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 @@ class StochasticGD extends Optimizer return $this->theta = $bestTheta; } - /** - * @return float - */ - protected function updateTheta() + protected function updateTheta() : float { $jValue = 0.0; $theta = $this->theta; @@ -231,12 +212,8 @@ class StochasticGD extends Optimizer /** * 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 @@ class StochasticGD extends Optimizer /** * 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 1c8b6aa..de7414d 100644 --- a/src/Phpml/Math/Comparison.php +++ b/src/Phpml/Math/Comparison.php @@ -9,12 +9,6 @@ use Phpml\Exception\InvalidArgumentException; 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 3df5c2c..696ee4b 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 ed2911c..40cdfbc 100644 --- a/src/Phpml/Math/Distance/Chebyshev.php +++ b/src/Phpml/Math/Distance/Chebyshev.php @@ -10,14 +10,9 @@ use Phpml\Math\Distance; 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 1158f5d..f6a87cf 100644 --- a/src/Phpml/Math/Distance/Euclidean.php +++ b/src/Phpml/Math/Distance/Euclidean.php @@ -10,14 +10,9 @@ use Phpml\Math\Distance; 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 @@ class Euclidean implements Distance /** * 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 b6f6eb8..72a9d1f 100644 --- a/src/Phpml/Math/Distance/Manhattan.php +++ b/src/Phpml/Math/Distance/Manhattan.php @@ -10,14 +10,9 @@ use Phpml\Math\Distance; 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 2af835e..17df39d 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 2cd92db..e47dbb5 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 @@ class RBF implements Kernel /** * @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 c67673b..d24b1a9 100644 --- a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -835,7 +835,6 @@ class EigenvalueDecomposition /** * 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 7a143f1..164a72f 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 @@ declare(strict_types=1); 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 @@ class LUDecomposition * * @return Matrix Lower triangular factor */ - public function getL() + public function getL() : Matrix { $L = []; for ($i = 0; $i < $this->m; ++$i) { @@ -165,7 +165,7 @@ class LUDecomposition * * @return Matrix Upper triangular factor */ - public function getU() + public function getU() : Matrix { $U = []; for ($i = 0; $i < $this->n; ++$i) { @@ -186,7 +186,7 @@ class LUDecomposition * * @return array Pivot vector */ - public function getPivot() + public function getPivot() : array { return $this->piv; } @@ -247,7 +247,7 @@ class LUDecomposition * * @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 @@ class LUDecomposition 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 fd91234..6145521 100644 --- a/src/Phpml/Math/Matrix.php +++ b/src/Phpml/Math/Matrix.php @@ -4,9 +4,9 @@ declare(strict_types=1); 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 @@ class Matrix $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 @@ class Matrix 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 @@ class Matrix 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 @@ class Matrix 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 @@ class 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 @@ class Matrix 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 @@ class Matrix /** * 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 @@ class Matrix 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 @@ class Matrix /** * 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 @@ class Matrix 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 @@ class Matrix return new self($newMatrix, false); } - /** - * @return bool - */ public function isSingular() : bool { return 0 == $this->getDeterminant(); @@ -344,12 +268,8 @@ class Matrix /** * 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 @@ class Matrix /** * 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 20fc780..c5c5aa7 100644 --- a/src/Phpml/Math/Set.php +++ b/src/Phpml/Math/Set.php @@ -21,11 +21,6 @@ class Set implements \IteratorAggregate /** * 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 @@ class Set implements \IteratorAggregate /** * 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 @@ class Set implements \IteratorAggregate /** * 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 @@ class Set implements \IteratorAggregate /** * 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 @@ class Set implements \IteratorAggregate /** * Creates the power set of A. * - * @param Set $a - * * @return Set[] */ public static function power(Set $a) : array @@ -115,8 +95,6 @@ class Set implements \IteratorAggregate /** * @param string|int|float $element - * - * @return Set */ public function add($element) : Set { @@ -125,8 +103,6 @@ class Set implements \IteratorAggregate /** * @param string[]|int[]|float[] $elements - * - * @return Set */ public function addAll(array $elements) : Set { @@ -137,8 +113,6 @@ class Set implements \IteratorAggregate /** * @param string|int|float $element - * - * @return Set */ public function remove($element) : Set { @@ -147,8 +121,6 @@ class Set implements \IteratorAggregate /** * @param string[]|int[]|float[] $elements - * - * @return Set */ public function removeAll(array $elements) : Set { @@ -159,8 +131,6 @@ class Set implements \IteratorAggregate /** * @param string|int|float $element - * - * @return bool */ public function contains($element) : bool { @@ -169,8 +139,6 @@ class Set implements \IteratorAggregate /** * @param string[]|int[]|float[] $elements - * - * @return bool */ public function containsAll(array $elements) : bool { @@ -185,25 +153,16 @@ class Set implements \IteratorAggregate 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 0f60223..9bcf271 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 e0a239d..2c72e6c 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 @@ class Covariance /** * 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 @@ class Covariance /** * 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 ae4c9a6..bdf8308 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 @@ class Gaussian /** * Returns probability density of the given $value * - * @param float $value - * * @return float|int */ public function pdf(float $value) @@ -46,14 +40,8 @@ class Gaussian /** * 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 6dd9853..22cd4bb 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 7655283..3da8ef5 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 6fc026c..dac82a0 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 @@ class ClassificationReport $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 @@ class ClassificationReport } /** - * @param int $truePositive - * @param int $falsePositive - * * @return float|string */ private function computePrecision(int $truePositive, int $falsePositive) @@ -137,9 +110,6 @@ class ClassificationReport } /** - * @param int $truePositive - * @param int $falseNegative - * * @return float|string */ private function computeRecall(int $truePositive, int $falseNegative) @@ -151,13 +121,7 @@ class ClassificationReport 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 @@ class ClassificationReport * * @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 664e355..9dc2595 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 @@ class ConfusionMatrix * * @return array */ - private static function generateMatrixWithZeros(array $labels): array + private static function generateMatrixWithZeros(array $labels) : array { $count = count($labels); $matrix = []; @@ -60,7 +60,7 @@ class ConfusionMatrix * * @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 08ab3e6..28594be 100644 --- a/src/Phpml/ModelManager.php +++ b/src/Phpml/ModelManager.php @@ -4,18 +4,11 @@ declare(strict_types=1); 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 @@ class ModelManager } } - /** - * @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 f209f34..65ba7b4 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 350404b..75b2ff1 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 0e3e848..081b8a5 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 93b1001..5c66fd9 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 76674a0..60ade03 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 23ac7ce..dec45a2 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 0963e09..dbe8ee6 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 632bc00..524bf35 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 @@ class Layer } /** - * @param string $nodeClass * @param ActivationFunction|null $activationFunction * * @return Neuron @@ -47,9 +42,6 @@ class Layer return new $nodeClass(); } - /** - * @param Node $node - */ public function addNode(Node $node) { $this->nodes[] = $node; @@ -58,7 +50,7 @@ class Layer /** * @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 c6c25af..af04f4a 100644 --- a/src/Phpml/NeuralNetwork/Network.php +++ b/src/Phpml/NeuralNetwork/Network.php @@ -16,15 +16,12 @@ interface Network /** * @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 b20f6bb..3f8bc04 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 @@ abstract class LayeredNetwork implements Network /** * @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 @@ abstract class LayeredNetwork implements Network /** * @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 21510c4..530fbef 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -5,16 +5,16 @@ declare(strict_types=1); 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 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, $this->initNetwork(); } - /** - * @return void - */ private function initNetwork() { $this->addInputLayer($this->inputLayerFeatures); @@ -100,10 +90,6 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, $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 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, } /** - * @param array $samples - * @param array $targets - * @param array $classes - * * @throws InvalidArgumentException */ public function partialTrain(array $samples, array $targets, array $classes = []) @@ -131,38 +113,25 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, } /** - * @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 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, } } - /** - * @param Layer $nextLayer - * @param Layer $currentLayer - */ private function generateLayerSynapses(Layer $nextLayer, Layer $currentLayer) { foreach ($nextLayer->getNodes() as $nextNeuron) { @@ -201,10 +166,6 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, } } - /** - * @param Layer $currentLayer - * @param Neuron $nextNeuron - */ private function generateNeuronSynapses(Layer $currentLayer, Neuron $nextNeuron) { foreach ($currentLayer->getNodes() as $currentNeuron) { @@ -212,10 +173,6 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, } } - /** - * @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 65d5cdc..6627c02 100644 --- a/src/Phpml/NeuralNetwork/Node.php +++ b/src/Phpml/NeuralNetwork/Node.php @@ -6,8 +6,5 @@ namespace Phpml\NeuralNetwork; 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 bf2e5ff..4f32884 100644 --- a/src/Phpml/NeuralNetwork/Node/Bias.php +++ b/src/Phpml/NeuralNetwork/Node/Bias.php @@ -8,10 +8,7 @@ use Phpml\NeuralNetwork\Node; 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 1531868..569b303 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 7c246be..520c3b2 100644 --- a/src/Phpml/NeuralNetwork/Node/Neuron.php +++ b/src/Phpml/NeuralNetwork/Node/Neuron.php @@ -5,8 +5,8 @@ declare(strict_types=1); 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 @@ class Neuron implements Node $this->output = 0; } - /** - * @param Synapse $synapse - */ public function addSynapse(Synapse $synapse) { $this->synapses[] = $synapse; @@ -51,10 +45,7 @@ class Neuron implements Node 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 b9c036f..35c8a0c 100644 --- a/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php +++ b/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php @@ -28,42 +28,27 @@ class Synapse $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 ba90b45..b113454 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 @@ class Backpropagation $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 @@ class Backpropagation 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 62e2e7a..23560fe 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 ca2914a..5330daf 100644 --- a/src/Phpml/Pipeline.php +++ b/src/Phpml/Pipeline.php @@ -29,17 +29,11 @@ class Pipeline implements 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 @@ class Pipeline implements 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 805d3f6..ee9282b 100644 --- a/src/Phpml/Preprocessing/Imputer.php +++ b/src/Phpml/Preprocessing/Imputer.php @@ -81,7 +81,7 @@ class Imputer implements Preprocessor * * @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 88fbd3a..4c57d8d 100644 --- a/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php +++ b/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php @@ -4,17 +4,15 @@ declare(strict_types=1); 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 d4e19e0..cf60f7e 100644 --- a/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php +++ b/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php @@ -4,17 +4,15 @@ declare(strict_types=1); 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 0bf8e8c..9aea453 100644 --- a/src/Phpml/Preprocessing/Imputer/Strategy/MostFrequentStrategy.php +++ b/src/Phpml/Preprocessing/Imputer/Strategy/MostFrequentStrategy.php @@ -4,8 +4,8 @@ declare(strict_types=1); 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 fc00030..412e06e 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -5,8 +5,8 @@ declare(strict_types=1); 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 1b664ed..28602cd 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 @@ class LeastSquares implements Regression } /** - * @param array $sample - * * @return mixed */ public function predictSample(array $sample) @@ -57,18 +51,12 @@ class LeastSquares implements Regression 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 @@ class LeastSquares implements Regression /** * 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 @@ class LeastSquares implements Regression 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 d4e5651..54215e0 100644 --- a/src/Phpml/Regression/SVR.php +++ b/src/Phpml/Regression/SVR.php @@ -10,17 +10,6 @@ use Phpml\SupportVectorMachine\Type; 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 ad5e180..b057d01 100644 --- a/src/Phpml/SupportVectorMachine/DataTransformer.php +++ b/src/Phpml/SupportVectorMachine/DataTransformer.php @@ -6,13 +6,6 @@ namespace Phpml\SupportVectorMachine; 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 @@ class DataTransformer return $set; } - /** - * @param array $samples - * - * @return string - */ public static function testSet(array $samples): string { if (!is_array($samples[0])) { @@ -46,13 +34,7 @@ class DataTransformer 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 @@ class DataTransformer 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 @@ class DataTransformer 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 9ee3c3b..6b6c48d 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 @@ class SupportVectorMachine $this->varPath = $rootPath.'var'.DIRECTORY_SEPARATOR; } - /** - * @param string $binPath - * - * @throws InvalidArgumentException - */ public function setBinPath(string $binPath) { $this->ensureDirectorySeparator($binPath); @@ -151,11 +132,6 @@ class SupportVectorMachine $this->binPath = $binPath; } - /** - * @param string $varPath - * - * @throws InvalidArgumentException - */ public function setVarPath(string $varPath) { if (!is_writable($varPath)) { @@ -166,10 +142,6 @@ class SupportVectorMachine $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 @@ class SupportVectorMachine 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 @@ class SupportVectorMachine 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 @@ class SupportVectorMachine return ''; } - /** - * @param string $trainingSetFileName - * @param string $modelFileName - * - * @return string - */ private function buildTrainCommand(string $trainingSetFileName, string $modelFileName): string { return sprintf( @@ -276,9 +234,6 @@ class SupportVectorMachine ); } - /** - * @param string $path - */ private function ensureDirectorySeparator(string &$path) { if (substr($path, -1) !== DIRECTORY_SEPARATOR) { @@ -286,11 +241,6 @@ class SupportVectorMachine } } - /** - * @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 9a145c5..e1f0f35 100644 --- a/src/Phpml/Tokenization/Tokenizer.php +++ b/src/Phpml/Tokenization/Tokenizer.php @@ -6,10 +6,5 @@ namespace Phpml\Tokenization; 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 ff91829..14e7d0a 100644 --- a/src/Phpml/Tokenization/WhitespaceTokenizer.php +++ b/src/Phpml/Tokenization/WhitespaceTokenizer.php @@ -6,12 +6,7 @@ namespace Phpml\Tokenization; 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 431ae00..03d134b 100644 --- a/src/Phpml/Tokenization/WordTokenizer.php +++ b/src/Phpml/Tokenization/WordTokenizer.php @@ -6,12 +6,7 @@ namespace Phpml\Tokenization; 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 a158e3e..afc358a 100644 --- a/tests/Phpml/Classification/Ensemble/BaggingTest.php +++ b/tests/Phpml/Classification/Ensemble/BaggingTest.php @@ -4,8 +4,8 @@ declare(strict_types=1); 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 cc1cd0f..3ff51e0 100644 --- a/tests/Phpml/Classification/Ensemble/RandomForestTest.php +++ b/tests/Phpml/Classification/Ensemble/RandomForestTest.php @@ -4,8 +4,8 @@ declare(strict_types=1); 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 db30afd..95438c6 100644 --- a/tests/Phpml/Classification/MLPClassifierTest.php +++ b/tests/Phpml/Classification/MLPClassifierTest.php @@ -5,8 +5,8 @@ declare(strict_types=1); 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 @@ class MLPClassifierTest extends TestCase * * @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 f143eef..1b529d0 100644 --- a/tests/Phpml/Classification/SVCTest.php +++ b/tests/Phpml/Classification/SVCTest.php @@ -5,8 +5,8 @@ declare(strict_types=1); 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 637fb1e..4498ea5 100644 --- a/tests/Phpml/DimensionReduction/LDATest.php +++ b/tests/Phpml/DimensionReduction/LDATest.php @@ -4,8 +4,8 @@ declare(strict_types=1); 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 118e9fe..4105b6f 100644 --- a/tests/Phpml/NeuralNetwork/LayerTest.php +++ b/tests/Phpml/NeuralNetwork/LayerTest.php @@ -4,8 +4,8 @@ declare(strict_types=1); 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 7f7f08e..126d70d 100644 --- a/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php +++ b/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php @@ -4,8 +4,8 @@ declare(strict_types=1); 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 92a6223..7fc3545 100644 --- a/tests/Phpml/PipelineTest.php +++ b/tests/Phpml/PipelineTest.php @@ -9,8 +9,8 @@ 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\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 f4405f4..ac457a9 100644 --- a/tests/Phpml/Regression/LeastSquaresTest.php +++ b/tests/Phpml/Regression/LeastSquaresTest.php @@ -4,8 +4,8 @@ declare(strict_types=1); 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 e8f77de..4ea19a3 100644 --- a/tests/Phpml/Regression/SVRTest.php +++ b/tests/Phpml/Regression/SVRTest.php @@ -4,9 +4,9 @@ declare(strict_types=1); 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 c8e8674..f99b032 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -72,10 +72,6 @@ class DecisionTree implements Classifier $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 @@ class DecisionTree implements Classifier } } - /** - * @param array $samples - * - * @return array - */ public static function getColumnTypes(array $samples) : array { $types = []; @@ -122,10 +113,6 @@ class DecisionTree implements Classifier 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 @@ class DecisionTree implements Classifier * * 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 @@ class DecisionTree implements Classifier 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 @@ class DecisionTree implements Classifier 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 bcc014e..2a649ad 100644 --- a/src/Phpml/Classification/Linear/Adaline.php +++ b/src/Phpml/Classification/Linear/Adaline.php @@ -52,7 +52,6 @@ class Adaline extends Perceptron /** * 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 bd100ba..bfbeb44 100644 --- a/src/Phpml/Classification/Linear/LogisticRegression.php +++ b/src/Phpml/Classification/Linear/LogisticRegression.php @@ -138,9 +138,6 @@ class LogisticRegression extends Adaline /** * 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 4af3de4..0a5f192 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 96feec7..e27b2e3 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 @@ class ArrayDataset implements Dataset $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 913148b..df18d11 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 @@ abstract class EigenTransformerBase /** * 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 61f7e65..c5e1e59 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 @@ class TfIdfTransformer implements Transformer } } - /** - * @param array $samples - */ public function fit(array $samples) { $this->countTokensFrequency($samples); @@ -36,9 +30,6 @@ class TfIdfTransformer implements Transformer } } - /** - * @param array $samples - */ public function transform(array &$samples) { foreach ($samples as &$sample) { @@ -48,9 +39,6 @@ class TfIdfTransformer implements Transformer } } - /** - * @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 994971d..7333b9a 100644 --- a/src/Phpml/Helper/Optimizer/ConjugateGradient.php +++ b/src/Phpml/Helper/Optimizer/ConjugateGradient.php @@ -17,12 +17,6 @@ namespace Phpml\Helper\Optimizer; */ 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 d24b1a9..b76ddb9 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 35ef79c..ab1e75a 100644 --- a/src/Phpml/Math/Product.php +++ b/src/Phpml/Math/Product.php @@ -7,9 +7,6 @@ namespace Phpml\Math; 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 22cd4bb..50b3788 100644 --- a/src/Phpml/Math/Statistic/Mean.php +++ b/src/Phpml/Math/Statistic/Mean.php @@ -9,8 +9,6 @@ use Phpml\Exception\InvalidArgumentException; class Mean { /** - * @param array $numbers - * * @throws InvalidArgumentException */ public static function arithmetic(array $numbers) : float @@ -21,8 +19,6 @@ class Mean } /** - * @param array $numbers - * * @return float|mixed * * @throws InvalidArgumentException @@ -44,8 +40,6 @@ class Mean } /** - * @param array $numbers - * * @return mixed * * @throws InvalidArgumentException @@ -60,8 +54,6 @@ class Mean } /** - * @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 3dfcb34..3fd545c 100644 --- a/src/Phpml/Metric/Accuracy.php +++ b/src/Phpml/Metric/Accuracy.php @@ -9,10 +9,6 @@ use Phpml\Exception\InvalidArgumentException; 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 dac82a0..f4fdaee 100644 --- a/src/Phpml/Metric/ClassificationReport.php +++ b/src/Phpml/Metric/ClassificationReport.php @@ -130,12 +130,6 @@ class ClassificationReport 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 9dc2595..5c7bc0b 100644 --- a/src/Phpml/Metric/ConfusionMatrix.php +++ b/src/Phpml/Metric/ConfusionMatrix.php @@ -6,13 +6,6 @@ namespace Phpml\Metric; 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 @@ class ConfusionMatrix return $matrix; } - /** - * @param array $labels - * - * @return array - */ private static function generateMatrixWithZeros(array $labels) : array { $count = count($labels); @@ -55,11 +43,6 @@ class ConfusionMatrix 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 3f8bc04..1234c41 100644 --- a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php +++ b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php @@ -39,9 +39,6 @@ abstract class LayeredNetwork implements Network 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 35c8a0c..adb4b02 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 5330daf..a49fb27 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 @@ class Pipeline implements 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 @@ class Pipeline implements Estimator } /** - * @param array $samples - * * @return mixed */ public function predict(array $samples) @@ -78,9 +71,6 @@ class Pipeline implements Estimator 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 ee9282b..cc90c46 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 @@ class Imputer implements Preprocessor $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 @@ class Imputer implements Preprocessor } } - /** - * @param array $sample - */ private function preprocessSample(array &$sample) { foreach ($sample as $column => &$value) { @@ -75,12 +64,6 @@ class Imputer implements Preprocessor } } - /** - * @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 4c57d8d..91badfb 100644 --- a/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php +++ b/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php @@ -9,9 +9,6 @@ use Phpml\Preprocessing\Imputer\Strategy; 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 cf60f7e..f010bea 100644 --- a/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php +++ b/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php @@ -9,9 +9,6 @@ use Phpml\Preprocessing\Imputer\Strategy; 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 9aea453..9a8fd63 100644 --- a/src/Phpml/Preprocessing/Imputer/Strategy/MostFrequentStrategy.php +++ b/src/Phpml/Preprocessing/Imputer/Strategy/MostFrequentStrategy.php @@ -10,8 +10,6 @@ use Phpml\Preprocessing\Imputer\Strategy; 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 412e06e..07777f9 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -46,9 +46,6 @@ class Normalizer implements Preprocessor $this->norm = $norm; } - /** - * @param array $samples - */ public function fit(array $samples) { if ($this->fitted) { @@ -67,9 +64,6 @@ class Normalizer implements Preprocessor $this->fitted = true; } - /** - * @param array $samples - */ public function transform(array &$samples) { $methods = [ @@ -86,9 +80,6 @@ class Normalizer implements Preprocessor } } - /** - * @param array $sample - */ private function normalizeL1(array &$sample) { $norm1 = 0; @@ -106,9 +97,6 @@ class Normalizer implements Preprocessor } } - /** - * @param array $sample - */ private function normalizeL2(array &$sample) { $norm2 = 0; @@ -126,9 +114,6 @@ class Normalizer implements Preprocessor } } - /** - * @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 95438c6..1745bc6 100644 --- a/tests/Phpml/Classification/MLPClassifierTest.php +++ b/tests/Phpml/Classification/MLPClassifierTest.php @@ -189,11 +189,6 @@ class MLPClassifierTest extends TestCase 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 ef07398..57023d3 100644 --- a/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php +++ b/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php @@ -46,12 +46,6 @@ class StratifiedRandomSplitTest extends TestCase $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 2d41273..8fff3cb 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 257fd72..0285b61 100644 --- a/tests/Phpml/Math/MatrixTest.php +++ b/tests/Phpml/Math/MatrixTest.php @@ -261,7 +261,6 @@ class MatrixTest extends TestCase $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 85cf9f8..a62b1c9 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php @@ -10,9 +10,6 @@ use PHPUnit\Framework\TestCase; 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 7835573..19b6cca 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php @@ -10,9 +10,6 @@ use PHPUnit\Framework\TestCase; 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 b490832..b27dfa0 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php @@ -10,10 +10,6 @@ use PHPUnit\Framework\TestCase; 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 a390bf0..5e14ce8 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php @@ -10,10 +10,6 @@ use PHPUnit\Framework\TestCase; 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 add1b34..6894006 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php @@ -10,10 +10,6 @@ use PHPUnit\Framework\TestCase; 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 b0c6ecf..571a197 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php @@ -10,10 +10,6 @@ use PHPUnit\Framework\TestCase; 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 94f56ed..7c5b9bb 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 7c5b9bb..48162fd 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 18ae994..8db4934 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 ee9c383..f1398d2 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 @@ class Apriori implements Associator /** * 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 @@ class Apriori implements Associator * * @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 f99b032..653b1bf 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 @@ class DecisionTree implements Classifier $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 @@ class DecisionTree implements Classifier /** * 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 9fdd65b..5bdca1b 100644 --- a/src/Phpml/Classification/Ensemble/AdaBoost.php +++ b/src/Phpml/Classification/Ensemble/AdaBoost.php @@ -84,7 +84,7 @@ class AdaBoost implements Classifier /** * 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 @@ class AdaBoost implements Classifier /** * @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 @@ class AdaBoost implements 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 @@ class AdaBoost implements Classifier /** * 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 ebc7528..8d2bbf9 100644 --- a/src/Phpml/Classification/Ensemble/Bagging.php +++ b/src/Phpml/Classification/Ensemble/Bagging.php @@ -106,7 +106,7 @@ class Bagging implements Classifier 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 @@ class Bagging implements Classifier $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 c7783d8..a261631 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 2a649ad..64d25a4 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 014dceb..e1486a6 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 @@ class DecisionStump extends WeightedClassifier /** * @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 @@ class DecisionStump extends WeightedClassifier * 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 @@ class DecisionStump extends WeightedClassifier // 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 @@ class DecisionStump extends WeightedClassifier // 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 @@ class DecisionStump extends WeightedClassifier 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 @@ class DecisionStump extends WeightedClassifier 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 bfbeb44..e8881be 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 @@ class LogisticRegression extends Adaline * 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 @@ class LogisticRegression extends Adaline * 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 @@ class LogisticRegression extends Adaline /** * 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 d5f424b..77eb717 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -88,12 +88,12 @@ class Perceptron implements Classifier, IncrementalEstimator $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 @@ class Perceptron implements Classifier, IncrementalEstimator $this->runTraining($samples, $targets); } - protected function resetBinary() + protected function resetBinary(): void { $this->labels = []; $this->optimizer = null; @@ -148,6 +148,8 @@ class Perceptron implements Classifier, IncrementalEstimator /** * 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 @@ class Perceptron implements Classifier, IncrementalEstimator * 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 64e6f50..b76091d 100644 --- a/src/Phpml/Classification/MLPClassifier.php +++ b/src/Phpml/Classification/MLPClassifier.php @@ -45,7 +45,7 @@ class MLPClassifier extends MultilayerPerceptron implements Classifier /** * @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 08073da..97f734a 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 @@ class NaiveBayes implements Classifier * 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 8cabcbc..de71bbe 100644 --- a/src/Phpml/Classification/SVC.php +++ b/src/Phpml/Classification/SVC.php @@ -14,7 +14,7 @@ class SVC extends SupportVectorMachine implements Classifier 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 0a5f192..c9b1f97 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 70cf302..1968b83 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 d14c0be..6eccea0 100644 --- a/src/Phpml/Clustering/FuzzyCMeans.php +++ b/src/Phpml/Clustering/FuzzyCMeans.php @@ -71,7 +71,7 @@ class FuzzyCMeans implements Clusterer $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 @@ class FuzzyCMeans implements Clusterer $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 @@ class FuzzyCMeans implements Clusterer } } - protected function updateClusters() + protected function updateClusters(): void { $dim = $this->space->getDimension(); if (!$this->clusters) { @@ -136,7 +136,7 @@ class FuzzyCMeans implements Clusterer 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 0a776a4..a4e85bc 100644 --- a/src/Phpml/Clustering/KMeans.php +++ b/src/Phpml/Clustering/KMeans.php @@ -9,8 +9,8 @@ use Phpml\Exception\InvalidArgumentException; 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 b7c7ecf..22545b6 100644 --- a/src/Phpml/Clustering/KMeans/Cluster.php +++ b/src/Phpml/Clustering/KMeans/Cluster.php @@ -64,17 +64,17 @@ class Cluster extends Point implements IteratorAggregate, Countable 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 @@ class Cluster extends Point implements IteratorAggregate, Countable 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 08fa11e..f90de8a 100644 --- a/src/Phpml/Clustering/KMeans/Point.php +++ b/src/Phpml/Clustering/KMeans/Point.php @@ -93,7 +93,7 @@ class Point implements ArrayAccess * @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 @@ class Point implements ArrayAccess /** * @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 bf2a345..4412d53 100644 --- a/src/Phpml/Clustering/KMeans/Space.php +++ b/src/Phpml/Clustering/KMeans/Space.php @@ -47,7 +47,7 @@ class Space extends SplObjectStorage /** * @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 @@ class Space extends SplObjectStorage * @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 @@ class Space extends SplObjectStorage 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 7ec1875..8507ee5 100644 --- a/src/Phpml/CrossValidation/RandomSplit.php +++ b/src/Phpml/CrossValidation/RandomSplit.php @@ -8,7 +8,7 @@ use Phpml\Dataset\Dataset; 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 77eab3c..e485ffb 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 @@ abstract class Split 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 41f5d34..153cb8f 100644 --- a/src/Phpml/CrossValidation/StratifiedRandomSplit.php +++ b/src/Phpml/CrossValidation/StratifiedRandomSplit.php @@ -9,7 +9,7 @@ use Phpml\Dataset\Dataset; 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 73bc4b0..ca04a3e 100644 --- a/src/Phpml/Dataset/FilesDataset.php +++ b/src/Phpml/Dataset/FilesDataset.php @@ -17,14 +17,14 @@ class FilesDataset extends ArrayDataset $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 df18d11..a6352ba 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 1c7cecb..d11e1a6 100644 --- a/src/Phpml/DimensionReduction/KernelPCA.php +++ b/src/Phpml/DimensionReduction/KernelPCA.php @@ -10,10 +10,10 @@ use Phpml\Math\Matrix; 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 8a94f46..26b2324 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 f5cb219..25b7186 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 @@ class PCA extends EigenTransformerBase 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 c5e1e59..6efd90f 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 @@ class TfIdfTransformer implements Transformer } } - public function transform(array &$samples) + public function transform(array &$samples): void { foreach ($samples as &$sample) { foreach ($sample as $index => &$feature) { @@ -39,7 +39,7 @@ class TfIdfTransformer implements Transformer } } - 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 eb7a4ac..e00fc69 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 @@ class TokenCountVectorizer implements Transformer $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 @@ class TokenCountVectorizer implements Transformer 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 @@ class TokenCountVectorizer implements Transformer } } - private function transformSample(string &$sample) + private function transformSample(string &$sample): void { $counts = []; $tokens = $this->tokenizer->tokenize($sample); @@ -113,7 +113,7 @@ class TokenCountVectorizer implements Transformer 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 @@ class TokenCountVectorizer implements Transformer 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 @@ class TokenCountVectorizer implements Transformer ++$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 @@ class TokenCountVectorizer implements Transformer } } - 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 5ae126c..15d62d8 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 @@ trait OneVsRest $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 @@ trait OneVsRest $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 @@ trait OneVsRest /** * 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 @@ trait OneVsRest * * @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 7333b9a..c119eae 100644 --- a/src/Phpml/Helper/Optimizer/ConjugateGradient.php +++ b/src/Phpml/Helper/Optimizer/ConjugateGradient.php @@ -61,7 +61,7 @@ class ConjugateGradient extends GD */ protected function gradient(array $theta) : array { - list(, $gradient) = parent::gradient($theta); + [, $gradient] = parent::gradient($theta); return $gradient; } @@ -71,7 +71,7 @@ class ConjugateGradient extends GD */ 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 ae3c6e2..38b4253 100644 --- a/src/Phpml/Helper/Optimizer/GD.php +++ b/src/Phpml/Helper/Optimizer/GD.php @@ -31,7 +31,7 @@ class GD extends StochasticGD $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 @@ class GD extends StochasticGD $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 @@ class GD extends StochasticGD 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 @@ class GD extends StochasticGD /** * 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 0a622d4..f1b0979 100644 --- a/src/Phpml/Helper/Optimizer/StochasticGD.php +++ b/src/Phpml/Helper/Optimizer/StochasticGD.php @@ -191,7 +191,7 @@ class StochasticGD extends Optimizer $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 @@ class StochasticGD extends Optimizer /** * 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 3d011ac..86ffaf1 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 b76ddb9..6261d26 100644 --- a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -119,7 +119,7 @@ class EigenvalueDecomposition /** * 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 @@ class EigenvalueDecomposition * 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 @@ class EigenvalueDecomposition * 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 @@ class EigenvalueDecomposition * @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 @@ class EigenvalueDecomposition * 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 2c72e6c..627a8a6 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 @@ class Covariance * @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 @@ class Covariance * * @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 50b3788..eb1baef 100644 --- a/src/Phpml/Math/Statistic/Mean.php +++ b/src/Phpml/Math/Statistic/Mean.php @@ -56,7 +56,7 @@ class Mean /** * @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 f4fdaee..ae4c11a 100644 --- a/src/Phpml/Metric/ClassificationReport.php +++ b/src/Phpml/Metric/ClassificationReport.php @@ -76,7 +76,7 @@ class ClassificationReport 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 @@ class ClassificationReport } } - 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 5c7bc0b..0f0b738 100644 --- a/src/Phpml/Metric/ConfusionMatrix.php +++ b/src/Phpml/Metric/ConfusionMatrix.php @@ -6,7 +6,7 @@ namespace Phpml\Metric; 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 28594be..2fa14f5 100644 --- a/src/Phpml/ModelManager.php +++ b/src/Phpml/ModelManager.php @@ -9,7 +9,7 @@ use Phpml\Exception\SerializeException; 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 524bf35..c70bdb3 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 @@ class Layer * * @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 @@ class Layer 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 1234c41..3baa5ac 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 @@ abstract class LayeredNetwork implements Network 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 530fbef..9ef3f73 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 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, $this->initNetwork(); } - private function initNetwork() + private function initNetwork(): void { $this->addInputLayer($this->inputLayerFeatures); $this->addNeuronLayers($this->hiddenLayers, $this->activationFunction); @@ -90,7 +90,7 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, $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 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, /** * @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 class MultilayerPerceptron extends LayeredNetwork implements Estimator, */ 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 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, } } - private function addBiasNodes() + private function addBiasNodes(): void { $biasLayers = count($this->layers) - 1; for ($i = 0; $i < $biasLayers; ++$i) { @@ -157,7 +157,7 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, } } - 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 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, } } - 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 569b303..8ff78ea 100644 --- a/src/Phpml/NeuralNetwork/Node/Input.php +++ b/src/Phpml/NeuralNetwork/Node/Input.php @@ -23,7 +23,7 @@ class Input implements Node 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 520c3b2..096d54f 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 @@ class Neuron implements Node 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 adb4b02..0883f4e 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 @@ class Synapse 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 b113454..98683ab 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -32,7 +32,7 @@ class Backpropagation /** * @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 a49fb27..a72e634 100644 --- a/src/Phpml/Pipeline.php +++ b/src/Phpml/Pipeline.php @@ -28,12 +28,12 @@ class Pipeline implements 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 @@ class Pipeline implements 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 @@ class Pipeline implements Estimator 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 cc90c46..d2dbfcd 100644 --- a/src/Phpml/Preprocessing/Imputer.php +++ b/src/Phpml/Preprocessing/Imputer.php @@ -8,8 +8,8 @@ use Phpml\Preprocessing\Imputer\Strategy; 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 @@ class Imputer implements Preprocessor $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 07777f9..f038345 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -10,9 +10,9 @@ use Phpml\Math\Statistic\StandardDeviation; 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 @@ class Normalizer implements Preprocessor $this->norm = $norm; } - public function fit(array $samples) + public function fit(array $samples): void { if ($this->fitted) { return; @@ -64,7 +64,7 @@ class Normalizer implements Preprocessor $this->fitted = true; } - public function transform(array &$samples) + public function transform(array &$samples): void { $methods = [ self::NORM_L1 => 'normalizeL1', @@ -80,7 +80,7 @@ class Normalizer implements Preprocessor } } - private function normalizeL1(array &$sample) + private function normalizeL1(array &$sample): void { $norm1 = 0; foreach ($sample as $feature) { @@ -97,7 +97,7 @@ class Normalizer implements Preprocessor } } - private function normalizeL2(array &$sample) + private function normalizeL2(array &$sample): void { $norm2 = 0; foreach ($sample as $feature) { @@ -114,7 +114,7 @@ class Normalizer implements Preprocessor } } - 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 28602cd..f8adcb2 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 @@ class LeastSquares implements Regression /** * 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 54215e0..e3032ac 100644 --- a/src/Phpml/Regression/SVR.php +++ b/src/Phpml/Regression/SVR.php @@ -15,7 +15,7 @@ class SVR extends SupportVectorMachine implements Regression 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 9918a3f..af76e6d 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 6b6c48d..cbee23d 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -97,7 +97,7 @@ class SupportVectorMachine 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 @@ class SupportVectorMachine $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 @@ class SupportVectorMachine $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 @@ class SupportVectorMachine $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 @@ class SupportVectorMachine ); } - 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 1b454a5..1dea9df 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 c715d6f..3b47483 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 @@ class AprioriTest extends TestCase $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 @@ class AprioriTest extends TestCase $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 @@ class AprioriTest extends TestCase $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 @@ class AprioriTest extends TestCase $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 @@ class AprioriTest extends TestCase $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 @@ class AprioriTest extends TestCase $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 @@ class AprioriTest extends TestCase $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 @@ class AprioriTest extends TestCase $this->assertEquals(2, $this->invoke($apriori, 'frequency', [['epsilon']])); } - public function testContains() + public function testContains(): void { $apriori = new Apriori(); @@ -151,7 +151,7 @@ class AprioriTest extends TestCase $this->assertFalse($this->invoke($apriori, 'contains', [[['a'], ['b']], ['c']])); } - public function testSubset() + public function testSubset(): void { $apriori = new Apriori(); @@ -160,7 +160,7 @@ class AprioriTest extends TestCase $this->assertFalse($this->invoke($apriori, 'subset', [['a'], ['a', 'b']])); } - public function testEquals() + public function testEquals(): void { $apriori = new Apriori(); @@ -187,7 +187,7 @@ class AprioriTest extends TestCase 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 854e4e5..626ed64 100644 --- a/tests/Phpml/Classification/DecisionTree/DecisionTreeLeafTest.php +++ b/tests/Phpml/Classification/DecisionTree/DecisionTreeLeafTest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; 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 f0ebc04..c7d2d2a 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 @@ class DecisionTreeTest extends TestCase 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 @@ class DecisionTreeTest extends TestCase 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 @@ class DecisionTreeTest extends TestCase $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 8b8d0db..2cc8090 100644 --- a/tests/Phpml/Classification/Ensemble/AdaBoostTest.php +++ b/tests/Phpml/Classification/Ensemble/AdaBoostTest.php @@ -42,7 +42,7 @@ class AdaBoostTest extends TestCase 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 afc358a..5bca8de 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 @@ class BaggingTest extends TestCase $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 @@ class BaggingTest extends TestCase 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 @@ class BaggingTest extends TestCase $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 @@ class BaggingTest extends TestCase } 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 3ff51e0..e4ca9e5 100644 --- a/tests/Phpml/Classification/Ensemble/RandomForestTest.php +++ b/tests/Phpml/Classification/Ensemble/RandomForestTest.php @@ -23,7 +23,7 @@ class RandomForestTest extends BaggingTest 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 aeee5ef..7ef6182 100644 --- a/tests/Phpml/Classification/KNearestNeighborsTest.php +++ b/tests/Phpml/Classification/KNearestNeighborsTest.php @@ -11,7 +11,7 @@ use PHPUnit\Framework\TestCase; 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 @@ class KNearestNeighborsTest extends TestCase $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 @@ class KNearestNeighborsTest extends TestCase $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 @@ class KNearestNeighborsTest extends TestCase $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 5989462..46f1a34 100644 --- a/tests/Phpml/Classification/Linear/AdalineTest.php +++ b/tests/Phpml/Classification/Linear/AdalineTest.php @@ -10,7 +10,7 @@ use PHPUnit\Framework\TestCase; 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 @@ class AdalineTest extends TestCase $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 678544e..fa522ad 100644 --- a/tests/Phpml/Classification/Linear/DecisionStumpTest.php +++ b/tests/Phpml/Classification/Linear/DecisionStumpTest.php @@ -53,7 +53,7 @@ class DecisionStumpTest extends TestCase 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 c01da33..2721067 100644 --- a/tests/Phpml/Classification/Linear/PerceptronTest.php +++ b/tests/Phpml/Classification/Linear/PerceptronTest.php @@ -10,7 +10,7 @@ use PHPUnit\Framework\TestCase; 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 @@ class PerceptronTest extends TestCase $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 1745bc6..9f9c9a9 100644 --- a/tests/Phpml/Classification/MLPClassifierTest.php +++ b/tests/Phpml/Classification/MLPClassifierTest.php @@ -11,7 +11,7 @@ use PHPUnit\Framework\TestCase; class MLPClassifierTest extends TestCase { - public function testMLPClassifierLayersInitialization() + public function testMLPClassifierLayersInitialization(): void { $mlp = new MLPClassifier(2, [2], [0, 1]); @@ -32,7 +32,7 @@ class MLPClassifierTest extends TestCase $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 @@ class MLPClassifierTest extends TestCase } } - public function testBackpropagationLearning() + public function testBackpropagationLearning(): void { // Single layer 2 classes. $network = new MLPClassifier(2, [2], ['a', 'b']); @@ -65,7 +65,7 @@ class MLPClassifierTest extends TestCase $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 @@ class MLPClassifierTest extends TestCase $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 @@ class MLPClassifierTest extends TestCase $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 @@ class MLPClassifierTest extends TestCase $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 @@ class MLPClassifierTest extends TestCase $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 @@ class MLPClassifierTest extends TestCase /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnInvalidLayersNumber() + public function testThrowExceptionOnInvalidLayersNumber(): void { new MLPClassifier(2, [], [0, 1]); } @@ -171,7 +171,7 @@ class MLPClassifierTest extends TestCase /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnInvalidPartialTrainingClasses() + public function testThrowExceptionOnInvalidPartialTrainingClasses(): void { $classifier = new MLPClassifier(2, [2], [0, 1]); $classifier->partialTrain( @@ -184,7 +184,7 @@ class MLPClassifierTest extends TestCase /** * @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 82c69eb..5b14f7e 100644 --- a/tests/Phpml/Classification/NaiveBayesTest.php +++ b/tests/Phpml/Classification/NaiveBayesTest.php @@ -10,7 +10,7 @@ use PHPUnit\Framework\TestCase; 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 @@ class NaiveBayesTest extends TestCase $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 @@ class NaiveBayesTest extends TestCase $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 1b529d0..f734297 100644 --- a/tests/Phpml/Classification/SVCTest.php +++ b/tests/Phpml/Classification/SVCTest.php @@ -11,7 +11,7 @@ use PHPUnit\Framework\TestCase; 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 @@ class SVCTest extends TestCase $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 @@ class SVCTest extends TestCase $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 a093b20..d8fb0fe 100644 --- a/tests/Phpml/Clustering/DBSCANTest.php +++ b/tests/Phpml/Clustering/DBSCANTest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; 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 @@ class DBSCANTest extends TestCase $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 39f2643..5aed678 100644 --- a/tests/Phpml/Clustering/FuzzyCMeansTest.php +++ b/tests/Phpml/Clustering/FuzzyCMeansTest.php @@ -25,7 +25,7 @@ class FuzzyCMeansTest extends TestCase 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 43d41ee..e665c9f 100644 --- a/tests/Phpml/Clustering/KMeansTest.php +++ b/tests/Phpml/Clustering/KMeansTest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; 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 @@ class KMeansTest extends TestCase $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 @@ class KMeansTest extends TestCase /** * @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 b2717fd..070e36b 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 @@ class RandomSplitTest extends TestCase /** * @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 @@ class RandomSplitTest extends TestCase $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 @@ class RandomSplitTest extends TestCase $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 @@ class RandomSplitTest extends TestCase $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 57023d3..e953ca7 100644 --- a/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php +++ b/tests/Phpml/CrossValidation/StratifiedRandomSplitTest.php @@ -10,7 +10,7 @@ use PHPUnit\Framework\TestCase; 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 @@ class StratifiedRandomSplitTest extends TestCase $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 0b13b51..41e037b 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 a3a377e..f5cc851 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 @@ class CsvDatasetTest extends TestCase $this->assertCount(10, $dataset->getTargets()); } - public function testSampleCsvDatasetWithoutHeaderRow() + public function testSampleCsvDatasetWithoutHeaderRow(): void { $filePath = dirname(__FILE__).'/Resources/dataset.csv'; @@ -37,7 +37,7 @@ class CsvDatasetTest extends TestCase $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 5e3f94c..8feef45 100644 --- a/tests/Phpml/Dataset/Demo/GlassDatasetTest.php +++ b/tests/Phpml/Dataset/Demo/GlassDatasetTest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; 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 18ad647..faa48c6 100644 --- a/tests/Phpml/Dataset/Demo/IrisDatasetTest.php +++ b/tests/Phpml/Dataset/Demo/IrisDatasetTest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; 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 a79ed8c..e0324b4 100644 --- a/tests/Phpml/Dataset/Demo/WineDatasetTest.php +++ b/tests/Phpml/Dataset/Demo/WineDatasetTest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; 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 d12477f..0592d06 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 e586fc8..66b4622 100644 --- a/tests/Phpml/DimensionReduction/KernelPCATest.php +++ b/tests/Phpml/DimensionReduction/KernelPCATest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; class KernelPCATest extends TestCase { - public function testKernelPCA() + public function testKernelPCA(): void { // Acceptable error $epsilon = 0.001; @@ -37,7 +37,7 @@ class KernelPCATest extends TestCase // 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 4498ea5..19124a0 100644 --- a/tests/Phpml/DimensionReduction/LDATest.php +++ b/tests/Phpml/DimensionReduction/LDATest.php @@ -10,7 +10,7 @@ use PHPUnit\Framework\TestCase; class LDATest extends TestCase { - public function testLDA() + public function testLDA(): void { // Acceptable error $epsilon = 0.001; @@ -43,7 +43,7 @@ class LDATest extends TestCase $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 c26782a..0a60004 100644 --- a/tests/Phpml/DimensionReduction/PCATest.php +++ b/tests/Phpml/DimensionReduction/PCATest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; class PCATest extends TestCase { - public function testPCA() + public function testPCA(): void { // Acceptable error $epsilon = 0.001; @@ -39,7 +39,7 @@ class PCATest extends TestCase // 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 @@ class PCATest extends TestCase $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 4979860..4b715ef 100644 --- a/tests/Phpml/FeatureExtraction/StopWordsTest.php +++ b/tests/Phpml/FeatureExtraction/StopWordsTest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; class StopWordsTest extends TestCase { - public function testCustomStopWords() + public function testCustomStopWords(): void { $stopWords = new StopWords(['lorem', 'ipsum', 'dolor']); @@ -25,12 +25,12 @@ class StopWordsTest extends TestCase /** * @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 @@ class StopWordsTest extends TestCase $this->assertFalse($stopWords->isStopWord('strategy')); } - public function testPolishStopWords() + public function testPolishStopWords(): void { $stopWords = StopWords::factory('Polish'); @@ -46,7 +46,7 @@ class StopWordsTest extends TestCase $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 90aa107..0e1adbc 100644 --- a/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php +++ b/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; 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 a25d5a5..3419a29 100644 --- a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php +++ b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php @@ -11,7 +11,7 @@ use PHPUnit\Framework\TestCase; class TokenCountVectorizerTest extends TestCase { - public function testTransformationWithWhitespaceTokenizer() + public function testTransformationWithWhitespaceTokenizer(): void { $samples = [ 'Lorem ipsum dolor sit amet dolor', @@ -47,7 +47,7 @@ class TokenCountVectorizerTest extends TestCase $this->assertSame($tokensCounts, $samples); } - public function testTransformationWithMinimumDocumentTokenCountFrequency() + public function testTransformationWithMinimumDocumentTokenCountFrequency(): void { // word at least in half samples $samples = [ @@ -100,7 +100,7 @@ class TokenCountVectorizerTest extends TestCase $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 8fff3cb..d31b1ae 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 @@ class ComparisonTest extends TestCase * @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 8806c74..893c000 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 @@ class ChebyshevTest extends TestCase /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnInvalidArguments() + public function testThrowExceptionOnInvalidArguments(): void { $a = [0, 1, 2]; $b = [0, 2]; @@ -30,7 +30,7 @@ class ChebyshevTest extends TestCase $this->distanceMetric->distance($a, $b); } - public function testCalculateDistanceForOneDimension() + public function testCalculateDistanceForOneDimension(): void { $a = [4]; $b = [2]; @@ -41,7 +41,7 @@ class ChebyshevTest extends TestCase $this->assertEquals($expectedDistance, $actualDistance); } - public function testCalculateDistanceForTwoDimensions() + public function testCalculateDistanceForTwoDimensions(): void { $a = [4, 6]; $b = [2, 5]; @@ -52,7 +52,7 @@ class ChebyshevTest extends TestCase $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 38cb6b7..03bf7f3 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 @@ class EuclideanTest extends TestCase /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnInvalidArguments() + public function testThrowExceptionOnInvalidArguments(): void { $a = [0, 1, 2]; $b = [0, 2]; @@ -30,7 +30,7 @@ class EuclideanTest extends TestCase $this->distanceMetric->distance($a, $b); } - public function testCalculateDistanceForOneDimension() + public function testCalculateDistanceForOneDimension(): void { $a = [4]; $b = [2]; @@ -41,7 +41,7 @@ class EuclideanTest extends TestCase $this->assertEquals($expectedDistance, $actualDistance); } - public function testCalculateDistanceForTwoDimensions() + public function testCalculateDistanceForTwoDimensions(): void { $a = [4, 6]; $b = [2, 5]; @@ -52,7 +52,7 @@ class EuclideanTest extends TestCase $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 0e22a15..9c20edd 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 @@ class ManhattanTest extends TestCase /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnInvalidArguments() + public function testThrowExceptionOnInvalidArguments(): void { $a = [0, 1, 2]; $b = [0, 2]; @@ -30,7 +30,7 @@ class ManhattanTest extends TestCase $this->distanceMetric->distance($a, $b); } - public function testCalculateDistanceForOneDimension() + public function testCalculateDistanceForOneDimension(): void { $a = [4]; $b = [2]; @@ -41,7 +41,7 @@ class ManhattanTest extends TestCase $this->assertEquals($expectedDistance, $actualDistance); } - public function testCalculateDistanceForTwoDimensions() + public function testCalculateDistanceForTwoDimensions(): void { $a = [4, 6]; $b = [2, 5]; @@ -52,7 +52,7 @@ class ManhattanTest extends TestCase $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 d8931d0..81ecd97 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 @@ class MinkowskiTest extends TestCase /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnInvalidArguments() + public function testThrowExceptionOnInvalidArguments(): void { $a = [0, 1, 2]; $b = [0, 2]; @@ -30,7 +30,7 @@ class MinkowskiTest extends TestCase $this->distanceMetric->distance($a, $b); } - public function testCalculateDistanceForOneDimension() + public function testCalculateDistanceForOneDimension(): void { $a = [4]; $b = [2]; @@ -41,7 +41,7 @@ class MinkowskiTest extends TestCase $this->assertEquals($expectedDistance, $actualDistance); } - public function testCalculateDistanceForTwoDimensions() + public function testCalculateDistanceForTwoDimensions(): void { $a = [4, 6]; $b = [2, 5]; @@ -52,7 +52,7 @@ class MinkowskiTest extends TestCase $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 @@ class MinkowskiTest extends TestCase $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 8076827..3ed7017 100644 --- a/tests/Phpml/Math/Kernel/RBFTest.php +++ b/tests/Phpml/Math/Kernel/RBFTest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; 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 1db2c66..688874c 100644 --- a/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php +++ b/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php @@ -10,7 +10,7 @@ use PHPUnit\Framework\TestCase; 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 0285b61..cd9fff2 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 @@ class MatrixTest extends TestCase /** * @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 @@ class MatrixTest extends TestCase /** * @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 @@ class MatrixTest extends TestCase $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 @@ class MatrixTest extends TestCase /** * @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 @@ class MatrixTest extends TestCase $matrix1->multiply($matrix2); } - public function testMatrixMultiplyByMatrix() + public function testMatrixMultiplyByMatrix(): void { $matrix1 = new Matrix([ [1, 2, 3], @@ -117,7 +117,7 @@ class MatrixTest extends TestCase $this->assertEquals($product, $matrix1->multiply($matrix2)->toArray()); } - public function testDivideByScalar() + public function testDivideByScalar(): void { $matrix = new Matrix([ [4, 6, 8], @@ -135,7 +135,7 @@ class MatrixTest extends TestCase /** * @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 @@ class MatrixTest extends TestCase /** * @expectedException \Phpml\Exception\MatrixException */ - public function testThrowExceptionWhenInverseIfMatrixIsSingular() + public function testThrowExceptionWhenInverseIfMatrixIsSingular(): void { $matrix = new Matrix([ [0, 0, 0], @@ -155,7 +155,7 @@ class MatrixTest extends TestCase $matrix->inverse(); } - public function testInverseMatrix() + public function testInverseMatrix(): void { //http://ncalculators.com/matrix/inverse-matrix.htm $matrix = new Matrix([ @@ -173,7 +173,7 @@ class MatrixTest extends TestCase $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 @@ class MatrixTest extends TestCase $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 @@ class MatrixTest extends TestCase $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 @@ class MatrixTest extends TestCase $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 @@ class MatrixTest extends TestCase $this->assertEquals($result, $m1->subtract($m2)->toArray()[0]); } - public function testTransposeArray() + public function testTransposeArray(): void { $array = [ [1, 1, 1], @@ -250,7 +250,7 @@ class MatrixTest extends TestCase $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 50ced9e..9c3a4b4 100644 --- a/tests/Phpml/Math/ProductTest.php +++ b/tests/Phpml/Math/ProductTest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; 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 101763c..a572a42 100644 --- a/tests/Phpml/Math/SetTest.php +++ b/tests/Phpml/Math/SetTest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; 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 @@ class SetTest extends TestCase $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 @@ class SetTest extends TestCase $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 @@ class SetTest extends TestCase $this->assertEquals(2, $difference->cardinality()); } - public function testPower() + public function testPower(): void { $power = Set::power(new Set(['A', 'B'])); @@ -45,7 +45,7 @@ class SetTest extends TestCase $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 @@ class SetTest extends TestCase $this->assertCount(2, $cartesian); } - public function testContains() + public function testContains(): void { $set = new Set(['B', 'A', 2, 1]); @@ -65,21 +65,21 @@ class SetTest extends TestCase $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 @@ class SetTest extends TestCase $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 dd01b8b..7fd2cec 100644 --- a/tests/Phpml/Math/Statistic/CorrelationTest.php +++ b/tests/Phpml/Math/Statistic/CorrelationTest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; 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 @@ class CorrelationTest extends TestCase /** * @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 d2abe7b..3a8c9d3 100644 --- a/tests/Phpml/Math/Statistic/CovarianceTest.php +++ b/tests/Phpml/Math/Statistic/CovarianceTest.php @@ -10,7 +10,7 @@ use PHPUnit\Framework\TestCase; 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 a0c9700..8030f85 100644 --- a/tests/Phpml/Math/Statistic/GaussianTest.php +++ b/tests/Phpml/Math/Statistic/GaussianTest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; 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 8f6a8fe..86553e0 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 @@ class MeanTest extends TestCase /** * @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 @@ class MeanTest extends TestCase /** * @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 35850c4..ead67b5 100644 --- a/tests/Phpml/Math/Statistic/StandardDeviationTest.php +++ b/tests/Phpml/Math/Statistic/StandardDeviationTest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; 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 @@ class StandardDeviationTest extends TestCase /** * @expectedException \Phpml\Exception\InvalidArgumentException */ - public function testThrowExceptionOnEmptyArrayIfNotSample() + public function testThrowExceptionOnEmptyArrayIfNotSample(): void { StandardDeviation::population([], false); } @@ -36,7 +36,7 @@ class StandardDeviationTest extends TestCase /** * @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 02876e0..885893d 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 @@ class AccuracyTest extends TestCase 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 @@ class AccuracyTest extends TestCase $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 @@ class AccuracyTest extends TestCase $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 7b142cf..b7ff02d 100644 --- a/tests/Phpml/Metric/ClassificationReportTest.php +++ b/tests/Phpml/Metric/ClassificationReportTest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; 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 @@ class ClassificationReportTest extends TestCase $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 @@ class ClassificationReportTest extends TestCase $this->assertEquals($average, $report->getAverage(), '', 0.01); } - public function testPreventDivideByZeroWhenTruePositiveAndFalsePositiveSumEqualsZero() + public function testPreventDivideByZeroWhenTruePositiveAndFalsePositiveSumEqualsZero(): void { $labels = [1, 2]; $predicted = [2, 2]; @@ -59,7 +59,7 @@ class ClassificationReportTest extends TestCase $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 @@ class ClassificationReportTest extends TestCase $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 1fa3130..f958bdf 100644 --- a/tests/Phpml/Metric/ConfusionMatrixTest.php +++ b/tests/Phpml/Metric/ConfusionMatrixTest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; 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 @@ class ConfusionMatrixTest extends TestCase $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 @@ class ConfusionMatrixTest extends TestCase $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 400b6d1..e44a32a 100644 --- a/tests/Phpml/ModelManagerTest.php +++ b/tests/Phpml/ModelManagerTest.php @@ -10,7 +10,7 @@ use PHPUnit\Framework\TestCase; 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 @@ class ModelManagerTest extends TestCase /** * @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 a62b1c9..95e95cb 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 19b6cca..f7af7c0 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 b27dfa0..95f437f 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 5e14ce8..873520e 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 6894006..096a376 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 571a197..1800d7b 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 4105b6f..284b1eb 100644 --- a/tests/Phpml/NeuralNetwork/LayerTest.php +++ b/tests/Phpml/NeuralNetwork/LayerTest.php @@ -11,14 +11,14 @@ use PHPUnit\Framework\TestCase; 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 @@ class LayerTest extends TestCase } } - public function testLayerInitializationWithExplicitNodesType() + public function testLayerInitializationWithExplicitNodesType(): void { $layer = new Layer($number = 5, $class = Bias::class); @@ -41,12 +41,12 @@ class LayerTest extends TestCase /** * @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 8b3f0e0..48bee66 100644 --- a/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php +++ b/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php @@ -11,7 +11,7 @@ use PHPUnit\Framework\TestCase; class LayeredNetworkTest extends TestCase { - public function testLayersSettersAndGetters() + public function testLayersSettersAndGetters(): void { $network = $this->getLayeredNetworkMock(); @@ -21,7 +21,7 @@ class LayeredNetworkTest extends TestCase $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 @@ class LayeredNetworkTest extends TestCase $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 8f01d52..ee311f5 100644 --- a/tests/Phpml/NeuralNetwork/Node/BiasTest.php +++ b/tests/Phpml/NeuralNetwork/Node/BiasTest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; 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 31d86c9..2d3be71 100644 --- a/tests/Phpml/NeuralNetwork/Node/InputTest.php +++ b/tests/Phpml/NeuralNetwork/Node/InputTest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; class InputTest extends TestCase { - public function testInputInitialization() + public function testInputInitialization(): void { $input = new Input(); $this->assertEquals(0.0, $input->getOutput()); @@ -18,7 +18,7 @@ class InputTest extends TestCase $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 126d70d..02d6dfa 100644 --- a/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php +++ b/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php @@ -10,7 +10,7 @@ use PHPUnit\Framework\TestCase; class SynapseTest extends TestCase { - public function testSynapseInitialization() + public function testSynapseInitialization(): void { $node = $this->getNodeMock($nodeOutput = 0.5); @@ -25,7 +25,7 @@ class SynapseTest extends TestCase $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 a7bc6fe..89f1ca1 100644 --- a/tests/Phpml/NeuralNetwork/Node/NeuronTest.php +++ b/tests/Phpml/NeuralNetwork/Node/NeuronTest.php @@ -11,7 +11,7 @@ use PHPUnit\Framework\TestCase; class NeuronTest extends TestCase { - public function testNeuronInitialization() + public function testNeuronInitialization(): void { $neuron = new Neuron(); @@ -19,7 +19,7 @@ class NeuronTest extends TestCase $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 @@ class NeuronTest extends TestCase $this->assertEquals($output, $neuron->getOutput()); } - public function testNeuronWithSynapse() + public function testNeuronWithSynapse(): void { $neuron = new Neuron(); $neuron->addSynapse($synapse = $this->getSynapseMock()); @@ -38,7 +38,7 @@ class NeuronTest extends TestCase $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 7fc3545..fc06e56 100644 --- a/tests/Phpml/PipelineTest.php +++ b/tests/Phpml/PipelineTest.php @@ -17,7 +17,7 @@ use PHPUnit\Framework\TestCase; class PipelineTest extends TestCase { - public function testPipelineConstruction() + public function testPipelineConstruction(): void { $transformers = [ new TfIdfTransformer(), @@ -30,7 +30,7 @@ class PipelineTest extends TestCase $this->assertEquals($estimator, $pipeline->getEstimator()); } - public function testPipelineEstimatorSetter() + public function testPipelineEstimatorSetter(): void { $pipeline = new Pipeline([new TfIdfTransformer()], new SVC()); @@ -40,7 +40,7 @@ class PipelineTest extends TestCase $this->assertEquals($estimator, $pipeline->getEstimator()); } - public function testPipelineWorkflow() + public function testPipelineWorkflow(): void { $transformers = [ new Imputer(null, new MostFrequentStrategy()), @@ -68,7 +68,7 @@ class PipelineTest extends TestCase $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 2f5c31b..658b454 100644 --- a/tests/Phpml/Preprocessing/ImputerTest.php +++ b/tests/Phpml/Preprocessing/ImputerTest.php @@ -12,7 +12,7 @@ use PHPUnit\Framework\TestCase; class ImputerTest extends TestCase { - public function testComplementsMissingValuesWithMeanStrategyOnColumnAxis() + public function testComplementsMissingValuesWithMeanStrategyOnColumnAxis(): void { $data = [ [1, null, 3, 4], @@ -34,7 +34,7 @@ class ImputerTest extends TestCase $this->assertEquals($imputeData, $data, '', $delta = 0.01); } - public function testComplementsMissingValuesWithMeanStrategyOnRowAxis() + public function testComplementsMissingValuesWithMeanStrategyOnRowAxis(): void { $data = [ [1, null, 3, 4], @@ -56,7 +56,7 @@ class ImputerTest extends TestCase $this->assertEquals($imputeData, $data, '', $delta = 0.01); } - public function testComplementsMissingValuesWithMediaStrategyOnColumnAxis() + public function testComplementsMissingValuesWithMediaStrategyOnColumnAxis(): void { $data = [ [1, null, 3, 4], @@ -78,7 +78,7 @@ class ImputerTest extends TestCase $this->assertEquals($imputeData, $data, '', $delta = 0.01); } - public function testComplementsMissingValuesWithMediaStrategyOnRowAxis() + public function testComplementsMissingValuesWithMediaStrategyOnRowAxis(): void { $data = [ [1, null, 3, 4], @@ -100,7 +100,7 @@ class ImputerTest extends TestCase $this->assertEquals($imputeData, $data, '', $delta = 0.01); } - public function testComplementsMissingValuesWithMostFrequentStrategyOnColumnAxis() + public function testComplementsMissingValuesWithMostFrequentStrategyOnColumnAxis(): void { $data = [ [1, null, 3, 4], @@ -124,7 +124,7 @@ class ImputerTest extends TestCase $this->assertEquals($imputeData, $data); } - public function testComplementsMissingValuesWithMostFrequentStrategyOnRowAxis() + public function testComplementsMissingValuesWithMostFrequentStrategyOnRowAxis(): void { $data = [ [1, null, 3, 4, 3], @@ -148,7 +148,7 @@ class ImputerTest extends TestCase $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 542eef5..22ed1bd 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 @@ class NormalizerTest extends TestCase $this->assertEquals($normalized, $samples, '', $delta = 0.01); } - public function testNormalizeSamplesWithL1Norm() + public function testNormalizeSamplesWithL1Norm(): void { $samples = [ [1, -1, 2], @@ -57,7 +57,7 @@ class NormalizerTest extends TestCase $this->assertEquals($normalized, $samples, '', $delta = 0.01); } - public function testFitNotChangeNormalizerBehavior() + public function testFitNotChangeNormalizerBehavior(): void { $samples = [ [1, -1, 2], @@ -81,7 +81,7 @@ class NormalizerTest extends TestCase $this->assertEquals($normalized, $samples, '', $delta = 0.01); } - public function testL1NormWithZeroSumCondition() + public function testL1NormWithZeroSumCondition(): void { $samples = [ [0, 0, 0], @@ -101,7 +101,7 @@ class NormalizerTest extends TestCase $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 ac457a9..7d835a2 100644 --- a/tests/Phpml/Regression/LeastSquaresTest.php +++ b/tests/Phpml/Regression/LeastSquaresTest.php @@ -10,7 +10,7 @@ use PHPUnit\Framework\TestCase; class LeastSquaresTest extends TestCase { - public function testPredictSingleFeatureSamples() + public function testPredictSingleFeatureSamples(): void { $delta = 0.01; @@ -37,7 +37,7 @@ class LeastSquaresTest extends TestCase $this->assertEquals(278.66, $regression->predict([153260]), '', $delta); } - public function testPredictSingleFeatureSamplesWithMatrixTargets() + public function testPredictSingleFeatureSamplesWithMatrixTargets(): void { $delta = 0.01; @@ -51,7 +51,7 @@ class LeastSquaresTest extends TestCase $this->assertEquals(4.06, $regression->predict([64]), '', $delta); } - public function testPredictMultiFeaturesSamples() + public function testPredictMultiFeaturesSamples(): void { $delta = 0.01; @@ -68,7 +68,7 @@ class LeastSquaresTest extends TestCase $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 4ea19a3..3cd0ee5 100644 --- a/tests/Phpml/Regression/SVRTest.php +++ b/tests/Phpml/Regression/SVRTest.php @@ -11,7 +11,7 @@ use PHPUnit\Framework\TestCase; class SVRTest extends TestCase { - public function testPredictSingleFeatureSamples() + public function testPredictSingleFeatureSamples(): void { $delta = 0.01; @@ -24,7 +24,7 @@ class SVRTest extends TestCase $this->assertEquals(4.03, $regression->predict([64]), '', $delta); } - public function testPredictMultiFeaturesSamples() + public function testPredictMultiFeaturesSamples(): void { $delta = 0.01; @@ -37,7 +37,7 @@ class SVRTest extends TestCase $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 9aa1558..b1f8522 100644 --- a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php +++ b/tests/Phpml/SupportVectorMachine/DataTransformerTest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; 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 @@ class DataTransformerTest extends TestCase $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 4cc6d57..ebbf99f 100644 --- a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php @@ -11,7 +11,7 @@ use PHPUnit\Framework\TestCase; 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 @@ SV $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 @@ SV $this->assertEquals('b', $predictions[2]); } - public function testPredictSampleFromMultipleClassWithRbfKernel() + public function testPredictSampleFromMultipleClassWithRbfKernel(): void { $samples = [ [1, 3], [1, 4], [1, 4], @@ -85,7 +85,7 @@ SV * @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 @@ SV * @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 @@ SV * @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 97fa833..f9d7c73 100644 --- a/tests/Phpml/Tokenization/WhitespaceTokenizerTest.php +++ b/tests/Phpml/Tokenization/WhitespaceTokenizerTest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; class WhitespaceTokenizerTest extends TestCase { - public function testTokenizationOnAscii() + public function testTokenizationOnAscii(): void { $tokenizer = new WhitespaceTokenizer(); @@ -24,7 +24,7 @@ class WhitespaceTokenizerTest extends TestCase $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 09db222..607c327 100644 --- a/tests/Phpml/Tokenization/WordTokenizerTest.php +++ b/tests/Phpml/Tokenization/WordTokenizerTest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; class WordTokenizerTest extends TestCase { - public function testTokenizationOnAscii() + public function testTokenizationOnAscii(): void { $tokenizer = new WordTokenizer(); @@ -24,7 +24,7 @@ class WordTokenizerTest extends TestCase $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 7829691..a87d3db 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 bac2747..bed47c4 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 48162fd..80bd5d8 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 f3ee24c..69f0eb7 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 8db4934..58e79a6 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 6979497..1384e6e 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 0798c2e..ef3a0e9 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 83c6aaa..c4ede68 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 72a9d1f..6d10b71 100644 --- a/src/Phpml/Math/Distance/Manhattan.php +++ b/src/Phpml/Math/Distance/Manhattan.php @@ -18,13 +18,8 @@ class Manhattan implements Distance 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 9f9c9a9..519dc90 100644 --- a/tests/Phpml/Classification/MLPClassifierTest.php +++ b/tests/Phpml/Classification/MLPClassifierTest.php @@ -112,16 +112,16 @@ class MLPClassifierTest extends TestCase 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 d2f746d..a6b060a 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 9ef3f73..94a8423 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 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, $this->inputLayerFeatures = $inputLayerFeatures; $this->hiddenLayers = $hiddenLayers; $this->activationFunction = $activationFunction; - $this->theta = $theta; + $this->learningRate = $learningRate; $this->initNetwork(); } @@ -87,7 +87,7 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, $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 98683ab..6722bd1 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -10,9 +10,9 @@ use Phpml\NeuralNetwork\Training\Backpropagation\Sigma; 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 @@ class Backpropagation 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 | 1964 +++++++++++++---- easy-coding-standard.neon | 39 + phpunit.xml | 8 +- src/Phpml/Association/Apriori.php | 35 +- src/Phpml/Classification/DecisionTree.php | 276 +-- .../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 | 143 +- 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 | 59 +- 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, 3080 insertions(+), 1514 deletions(-) create mode 100644 easy-coding-standard.neon diff --git a/.gitignore b/.gitignore index bed47c4..38ef5a0 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 80bd5d8..8f8a68b 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 58e79a6..0563f3f 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 1384e6e..e10b2d8 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" + "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" + } + }, "autoload": { "psr-4": { - "GeckoPackages\\PHPUnit\\": "src\\PHPUnit" + "GeckoPackages\\PHPUnit\\": "src/PHPUnit" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Additional PHPUnit tests.", + "description": "Additional PHPUnit asserts and constraints.", "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" - }, - "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" + "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,20 +417,474 @@ "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": { + "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" + }, + { + "name": "nette/finder", + "version": "v2.4.1", + "source": { + "type": "git", + "url": "https://github.com/nette/finder.git", + "reference": "4d43a66d072c57d585bf08a3ef68d3587f7e9547" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/finder/zipball/4d43a66d072c57d585bf08a3ef68d3587f7e9547", + "reference": "4d43a66d072c57d585bf08a3ef68d3587f7e9547", + "shasum": "" + }, + "require": { + "nette/utils": "^2.4 || ~3.0.0", + "php": ">=5.6.0" + }, + "conflict": { + "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 Finder: Files Searching", + "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", + "source": { + "type": "git", + "url": "https://github.com/nette/robot-loader.git", + "reference": "b703b4f5955831b0bcaacbd2f6af76021b056826" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/robot-loader/zipball/b703b4f5955831b0bcaacbd2f6af76021b056826", + "reference": "b703b4f5955831b0bcaacbd2f6af76021b056826", + "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" + }, + "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 RobotLoader: high performance and comfortable autoloader that will search and autoload classes within your application.", + "homepage": "https://nette.org", + "keywords": [ + "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": { @@ -486,81 +919,170 @@ "pseudorandom", "random" ], - "time": "2017-03-13T16:27:32+00:00" + "time": "2017-09-27T21:40:39+00:00" }, { - "name": "php-coveralls/php-coveralls", - "version": "v1.0.2", + "name": "phar-io/manifest", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/php-coveralls/php-coveralls.git", - "reference": "9c07b63acbc9709344948b6fd4f63a32b2ef4127" + "url": "https://github.com/phar-io/manifest.git", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-coveralls/php-coveralls/zipball/9c07b63acbc9709344948b6fd4f63a32b2ef4127", - "reference": "9c07b63acbc9709344948b6fd4f63a32b2ef4127", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", "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-dom": "*", + "ext-phar": "*", + "phar-io/version": "^1.0.1", + "php": "^5.6 || ^7.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/" + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" } }, + "autoload": { + "classmap": [ + "src/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Kitamura Satoshi", - "email": "with.no.parachute@gmail.com", - "homepage": "https://www.facebook.com/satooshi.jp" + "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": "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" + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2017-03-05T18:14:27+00:00" }, { - "name": "phpdocumentor/reflection-common", - "version": "1.0", + "name": "phar-io/version", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" + "url": "https://github.com/phar-io/version.git", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", - "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "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": { @@ -601,26 +1123,26 @@ "reflection", "static analysis" ], - "time": "2015-12-27T11:43:31+00:00" + "time": "2017-09-11T18:02:19+00:00" }, { "name": "phpdocumentor/reflection-docblock", - "version": "3.1.1", + "version": "4.1.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e" + "reference": "2d3d238c433cf69caeb4842e97a3223a116f94b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e", - "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/2d3d238c433cf69caeb4842e97a3223a116f94b2", + "reference": "2d3d238c433cf69caeb4842e97a3223a116f94b2", "shasum": "" }, "require": { - "php": ">=5.5", + "php": "^7.0", "phpdocumentor/reflection-common": "^1.0@dev", - "phpdocumentor/type-resolver": "^0.2.0", + "phpdocumentor/type-resolver": "^0.4.0", "webmozart/assert": "^1.0" }, "require-dev": { @@ -646,24 +1168,24 @@ } ], "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" + "time": "2017-08-30T18:51:59+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "0.2.1", + "version": "0.4.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb" + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", - "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", "shasum": "" }, "require": { - "php": ">=5.5", + "php": "^5.5 || ^7.0", "phpdocumentor/reflection-common": "^1.0" }, "require-dev": { @@ -693,26 +1215,26 @@ "email": "me@mikevanriel.com" } ], - "time": "2016-11-25T06:54:22+00:00" + "time": "2017-07-14T14:27:02+00:00" }, { "name": "phpspec/prophecy", - "version": "v1.7.0", + "version": "v1.7.2", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "93d39f1f7f9326d746203c7c056f300f7f126073" + "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073", - "reference": "93d39f1f7f9326d746203c7c056f300f7f126073", + "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", + "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" }, @@ -723,7 +1245,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.6.x-dev" + "dev-master": "1.7.x-dev" } }, "autoload": { @@ -756,44 +1278,45 @@ "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,27 +2045,27 @@ "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": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.2" + "phpunit/phpunit": "^6.0" }, "suggest": { "ext-uopz": "*" @@ -1499,7 +2073,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -1522,24 +2096,25 @@ "keywords": [ "global state" ], - "time": "2015-10-12T03:26:01+00:00" + "time": "2017-04-27T15:39:26+00:00" }, { "name": "sebastian/object-enumerator", - "version": "3.0.0", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "de6e32f7192dfea2e4bedc892434f4830b5c5794" + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/de6e32f7192dfea2e4bedc892434f4830b5c5794", - "reference": "de6e32f7192dfea2e4bedc892434f4830b5c5794", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", "shasum": "" }, "require": { "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", "sebastian/recursion-context": "^3.0" }, "require-dev": { @@ -1568,7 +2143,52 @@ ], "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" + "time": "2017-08-03T12:35:26+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "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" + } + ], + "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", @@ -1772,20 +2480,20 @@ }, { "name": "symfony/console", - "version": "v3.3.5", + "version": "v3.3.12", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "a97e45d98c59510f085fa05225a1acb74dfe0546" + "reference": "099302cc53e57cbb7414fd9f3ace40e5e2767c0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/a97e45d98c59510f085fa05225a1acb74dfe0546", - "reference": "a97e45d98c59510f085fa05225a1acb74dfe0546", + "url": "https://api.github.com/repos/symfony/console/zipball/099302cc53e57cbb7414fd9f3ace40e5e2767c0b", + "reference": "099302cc53e57cbb7414fd9f3ace40e5e2767c0b", "shasum": "" }, "require": { - "php": ">=5.5.9", + "php": "^5.5.9|>=7.0.8", "symfony/debug": "~2.8|~3.0", "symfony/polyfill-mbstring": "~1.0" }, @@ -1798,7 +2506,6 @@ "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": { @@ -1837,24 +2544,24 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2017-07-03T13:19:36+00:00" + "time": "2017-11-12T16:53:41+00:00" }, { "name": "symfony/debug", - "version": "v3.3.5", + "version": "v3.3.12", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "63b85a968486d95ff9542228dc2e4247f16f9743" + "reference": "74557880e2846b5c84029faa96b834da37e29810" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/63b85a968486d95ff9542228dc2e4247f16f9743", - "reference": "63b85a968486d95ff9542228dc2e4247f16f9743", + "url": "https://api.github.com/repos/symfony/debug/zipball/74557880e2846b5c84029faa96b834da37e29810", + "reference": "74557880e2846b5c84029faa96b834da37e29810", "shasum": "" }, "require": { - "php": ">=5.5.9", + "php": "^5.5.9|>=7.0.8", "psr/log": "~1.0" }, "conflict": { @@ -1893,24 +2600,94 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2017-07-05T13:02:37+00:00" + "time": "2017-11-10T16:38:39+00:00" }, { - "name": "symfony/event-dispatcher", - "version": "v3.3.5", + "name": "symfony/dependency-injection", + "version": "v3.3.12", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "67535f1e3fd662bdc68d7ba317c93eecd973617e" + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "4e84f5af2c2d51ee3dee72df40b7fc08f49b4ab8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/67535f1e3fd662bdc68d7ba317c93eecd973617e", - "reference": "67535f1e3fd662bdc68d7ba317c93eecd973617e", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/4e84f5af2c2d51ee3dee72df40b7fc08f49b4ab8", + "reference": "4e84f5af2c2d51ee3dee72df40b7fc08f49b4ab8", "shasum": "" }, "require": { - "php": ">=5.5.9" + "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/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": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "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 DependencyInjection Component", + "homepage": "https://symfony.com", + "time": "2017-11-13T18:10:32+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v3.3.12", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "271d8c27c3ec5ecee6e2ac06016232e249d638d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/271d8c27c3ec5ecee6e2ac06016232e249d638d9", + "reference": "271d8c27c3ec5ecee6e2ac06016232e249d638d9", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" }, "conflict": { "symfony/dependency-injection": "<3.3" @@ -1956,24 +2733,24 @@ ], "description": "Symfony EventDispatcher 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", + "version": "v3.3.12", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "427987eb4eed764c3b6e38d52a0f87989e010676" + "reference": "77db266766b54db3ee982fe51868328b887ce15c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/427987eb4eed764c3b6e38d52a0f87989e010676", - "reference": "427987eb4eed764c3b6e38d52a0f87989e010676", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/77db266766b54db3ee982fe51868328b887ce15c", + "reference": "77db266766b54db3ee982fe51868328b887ce15c", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^5.5.9|>=7.0.8" }, "type": "library", "extra": { @@ -2005,24 +2782,24 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2017-07-11T07:17:58+00:00" + "time": "2017-11-07T14:12:55+00:00" }, { "name": "symfony/finder", - "version": "v3.3.5", + "version": "v3.3.12", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4" + "reference": "138af5ec075d4b1d1bd19de08c38a34bb2d7d880" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/baea7f66d30854ad32988c11a09d7ffd485810c4", - "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4", + "url": "https://api.github.com/repos/symfony/finder/zipball/138af5ec075d4b1d1bd19de08c38a34bb2d7d880", + "reference": "138af5ec075d4b1d1bd19de08c38a34bb2d7d880", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^5.5.9|>=7.0.8" }, "type": "library", "extra": { @@ -2054,24 +2831,163 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2017-06-01T21:01:25+00:00" + "time": "2017-11-05T15:47:03+00:00" }, { - "name": "symfony/options-resolver", - "version": "v3.3.5", + "name": "symfony/http-foundation", + "version": "v3.3.12", "source": { "type": "git", - "url": "https://github.com/symfony/options-resolver.git", - "reference": "ff48982d295bcac1fd861f934f041ebc73ae40f0" + "url": "https://github.com/symfony/http-foundation.git", + "reference": "5943f0f19817a7e05992d20a90729b0dc93faf36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/ff48982d295bcac1fd861f934f041ebc73ae40f0", - "reference": "ff48982d295bcac1fd861f934f041ebc73ae40f0", + "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": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "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 HttpFoundation Component", + "homepage": "https://symfony.com", + "time": "2017-11-13T18:13:16+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v3.3.12", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "371ed63691c1ee8749613a6b48cf0e0cfa2b01e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/371ed63691c1ee8749613a6b48cf0e0cfa2b01e7", + "reference": "371ed63691c1ee8749613a6b48cf0e0cfa2b01e7", + "shasum": "" + }, + "require": { + "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": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "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 HttpKernel Component", + "homepage": "https://symfony.com", + "time": "2017-11-13T19:37:21+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v3.3.12", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "623d9c210a137205f7e6e98166105625402cbb2f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/623d9c210a137205f7e6e98166105625402cbb2f", + "reference": "623d9c210a137205f7e6e98166105625402cbb2f", + "shasum": "" + }, + "require": { + "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 0000000..14d8770 --- /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 cbf6c18..455f8bb 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 f1398d2..e13f556 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 @@ class Apriori implements Associator * * @return mixed[][] */ - public function getRules() : array + public function getRules(): array { if (!$this->large) { $this->large = $this->apriori(); @@ -83,7 +83,7 @@ class Apriori implements Associator * * @return mixed[][][] */ - public function apriori() : array + public function apriori(): array { $L = []; $L[1] = $this->items(); @@ -102,7 +102,7 @@ class Apriori implements Associator * * @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 @@ class Apriori implements Associator 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 @@ class Apriori implements Associator * * @return mixed[][] */ - private function powerSet(array $sample) : array + private function powerSet(array $sample): array { $results = [[]]; foreach ($sample as $item) { @@ -171,7 +172,7 @@ class Apriori implements Associator * * @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 @@ class Apriori implements Associator * * @return mixed[][] */ - private function items() : array + private function items(): array { $items = []; @@ -210,7 +211,7 @@ class Apriori implements Associator * * @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 @@ class Apriori implements Associator * * @return mixed[][] */ - private function candidates(array $samples) : array + private function candidates(array $samples): array { $candidates = []; @@ -259,7 +260,7 @@ class Apriori implements Associator * @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 @@ class Apriori implements Associator * * @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 @@ class Apriori implements Associator * * @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 @@ class Apriori implements Associator * @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 @@ class Apriori implements Associator * @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 @@ class Apriori implements Associator * @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 653b1bf..5bb730b 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 - */ - protected $columnTypes; - - /** - * @var array - */ - private $labels = []; - /** * @var int */ - private $featureCount = 0; + public $actualDepth = 0; + + /** + * @var array + */ + 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 @@ class DecisionTree implements Classifier } } - public static function getColumnTypes(array $samples) : array + public static function getColumnTypes(array $samples): array { $types = []; $featureCount = count($samples[0]); @@ -113,7 +114,122 @@ class DecisionTree implements Classifier 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 @@ class DecisionTree implements Classifier if ($prevRecord && $prevRecord != $record) { $allSame = false; } + $prevRecord = $record; // According to the split criteron, this record will @@ -163,6 +280,7 @@ class DecisionTree implements Classifier if ($leftRecords) { $split->leftLeaf = $this->getSplitLeaf($leftRecords, $depth + 1); } + if ($rightRecords) { $split->rightLeaf = $this->getSplitLeaf($rightRecords, $depth + 1); } @@ -171,7 +289,7 @@ class DecisionTree implements Classifier 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 @@ class DecisionTree implements Classifier foreach ($samples as $index => $row) { $colValues[$index] = $row[$i]; } + $counts = array_count_values($colValues); arsort($counts); $baseValue = key($counts); @@ -227,7 +346,7 @@ class DecisionTree implements Classifier * 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 @@ class DecisionTree implements Classifier if ($numFeatures > $this->featureCount) { $numFeatures = $this->featureCount; } + shuffle($allFeatures); $selectedFeatures = array_slice($allFeatures, 0, $numFeatures, false); sort($selectedFeatures); @@ -249,39 +369,7 @@ class DecisionTree implements Classifier 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 @@ class DecisionTree implements Classifier } } } + $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 @@ class DecisionTree implements Classifier 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 @@ class DecisionTree implements Classifier $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 2bcc3ac..f3f9449 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 @@ class DecisionTreeLeaf * 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 @@ class DecisionTreeLeaf /** * 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 @@ class DecisionTreeLeaf 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 5bdca1b..67f7198 100644 --- a/src/Phpml/Classification/Ensemble/AdaBoost.php +++ b/src/Phpml/Classification/Ensemble/AdaBoost.php @@ -4,6 +4,7 @@ declare(strict_types=1); 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\Predictable; use Phpml\Helper\Trainable; use Phpml\Math\Statistic\Mean; use Phpml\Math\Statistic\StandardDeviation; +use ReflectionClass; class AdaBoost implements Classifier { @@ -98,11 +100,14 @@ class AdaBoost implements Classifier // 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 @@ class AdaBoost implements Classifier } } + /** + * @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 @@ class AdaBoost implements 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 @@ class AdaBoost implements Classifier 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 @@ class AdaBoost implements Classifier /** * 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 @@ class AdaBoost implements Classifier /** * 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 @@ class AdaBoost implements Classifier $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 8d2bbf9..6fa1ec8 100644 --- a/src/Phpml/Classification/Ensemble/Bagging.php +++ b/src/Phpml/Classification/Ensemble/Bagging.php @@ -4,10 +4,12 @@ declare(strict_types=1); 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 @@ class Bagging implements Classifier 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 @@ class Bagging implements Classifier } } - 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 @@ class Bagging implements Classifier 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 @@ class Bagging implements Classifier 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 4928ea5..59f19c1 100644 --- a/src/Phpml/Classification/Ensemble/RandomForest.php +++ b/src/Phpml/Classification/Ensemble/RandomForest.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Phpml\Classification\Ensemble; +use Exception; +use Phpml\Classification\Classifier; use Phpml\Classification\DecisionTree; class RandomForest extends Bagging @@ -48,11 +50,11 @@ class RandomForest extends Bagging 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 @@ class RandomForest extends Bagging 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 @@ class RandomForest extends Bagging * 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 @@ class RandomForest extends Bagging * * @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 a261631..238fc1d 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 @@ class KNearestNeighbors implements Classifier /** * @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 64d25a4..cda746f 100644 --- a/src/Phpml/Classification/Linear/Adaline.php +++ b/src/Phpml/Classification/Linear/Adaline.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Phpml\Classification\Linear; +use Exception; + class Adaline extends Perceptron { /** @@ -41,7 +43,7 @@ class Adaline extends Perceptron 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 e1486a6..3f6eb58 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -4,6 +4,7 @@ declare(strict_types=1); 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 @@ class DecisionStump extends WeightedClassifier $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 @@ class DecisionStump extends WeightedClassifier 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 @@ class DecisionStump extends WeightedClassifier } $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 @@ class DecisionStump extends WeightedClassifier } } - /** - * 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 @@ class DecisionStump extends WeightedClassifier $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 @@ class DecisionStump extends WeightedClassifier $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 @@ class DecisionStump extends WeightedClassifier 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 @@ class DecisionStump extends WeightedClassifier [$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 @@ class DecisionStump extends WeightedClassifier * 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 @@ class DecisionStump extends WeightedClassifier if (!isset($prob[$predicted][$target])) { $prob[$predicted][$target] = 0; } + ++$prob[$predicted][$target]; } @@ -267,7 +291,7 @@ class DecisionStump extends WeightedClassifier * * @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 @@ class DecisionStump extends WeightedClassifier 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 e8881be..6b8cdd5 100644 --- a/src/Phpml/Classification/Linear/LogisticRegression.php +++ b/src/Phpml/Classification/Linear/LogisticRegression.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Phpml\Classification\Linear; +use Closure; +use Exception; use Phpml\Helper\Optimizer\ConjugateGradient; class LogisticRegression extends Adaline @@ -70,18 +72,18 @@ class LogisticRegression extends Adaline ) { $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 @@ class LogisticRegression extends Adaline 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 @@ class LogisticRegression extends Adaline * * @throws \Exception */ - protected function getCostFunction() : \Closure + protected function getCostFunction(): Closure { $penalty = 0; if ($this->penalty == 'L2') { @@ -183,9 +185,11 @@ class LogisticRegression extends Adaline 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 @@ class LogisticRegression extends Adaline 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 @@ class LogisticRegression extends Adaline /** * 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 @@ class LogisticRegression extends Adaline * * 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 77eb717..0db7496 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -4,6 +4,8 @@ declare(strict_types=1); 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 @@ class Perceptron implements Classifier, IncrementalEstimator } // 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 @@ class Perceptron implements Classifier, IncrementalEstimator $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 @@ class Perceptron implements Classifier, IncrementalEstimator /** * 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 @@ class Perceptron implements Classifier, IncrementalEstimator * 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 @@ class Perceptron implements Classifier, IncrementalEstimator * 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 @@ class Perceptron implements Classifier, IncrementalEstimator /** * 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 @@ class Perceptron implements Classifier, IncrementalEstimator /** * 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 @@ class Perceptron implements Classifier, IncrementalEstimator * * @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 b76091d..b225a64 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 97f734a..a470fd4 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 @@ class NaiveBayes implements Classifier } } + /** + * @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 @@ class NaiveBayes implements Classifier /** * 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 @@ class NaiveBayes implements Classifier 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 @@ class NaiveBayes implements Classifier /** * 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 @@ class NaiveBayes implements Classifier 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 c9b1f97..9834764 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 ad24af2..22e65e6 100644 --- a/src/Phpml/Clustering/Clusterer.php +++ b/src/Phpml/Clustering/Clusterer.php @@ -6,5 +6,5 @@ namespace Phpml\Clustering; 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 1968b83..3546ebf 100644 --- a/src/Phpml/Clustering/DBSCAN.php +++ b/src/Phpml/Clustering/DBSCAN.php @@ -4,6 +4,7 @@ declare(strict_types=1); 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 @@ class DBSCAN implements Clusterer $this->distanceMetric = $distanceMetric; } - public function cluster(array $samples) : array + public function cluster(array $samples): array { $clusters = []; $visited = []; @@ -44,6 +45,7 @@ class DBSCAN implements Clusterer if (isset($visited[$index])) { continue; } + $visited[$index] = true; $regionSamples = $this->getSamplesInRegion($sample, $samples); @@ -55,7 +57,7 @@ class DBSCAN implements Clusterer return $clusters; } - private function getSamplesInRegion(array $localSample, array $samples) : array + private function getSamplesInRegion(array $localSample, array $samples): array { $region = []; @@ -68,7 +70,7 @@ class DBSCAN implements Clusterer return $region; } - private function expandCluster(array $samples, array &$visited) : array + private function expandCluster(array $samples, array &$visited): array { $cluster = []; @@ -84,7 +86,8 @@ class DBSCAN implements Clusterer $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 6eccea0..d3be101 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 @@ class FuzzyCMeans implements Clusterer 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 @@ class FuzzyCMeans implements Clusterer $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 @@ class FuzzyCMeans implements Clusterer } } - 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 @@ class FuzzyCMeans implements Clusterer 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 a4e85bc..78a2e4a 100644 --- a/src/Phpml/Clustering/KMeans.php +++ b/src/Phpml/Clustering/KMeans.php @@ -10,6 +10,7 @@ use Phpml\Exception\InvalidArgumentException; class KMeans implements Clusterer { public const INIT_RANDOM = 1; + public const INIT_KMEANS_PLUS_PLUS = 2; /** @@ -32,7 +33,7 @@ class KMeans implements Clusterer $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 22545b6..fea1ff8 100644 --- a/src/Phpml/Clustering/KMeans/Cluster.php +++ b/src/Phpml/Clustering/KMeans/Cluster.php @@ -28,7 +28,7 @@ class Cluster extends Point implements IteratorAggregate, Countable $this->points = new SplObjectStorage(); } - public function getPoints() : array + public function getPoints(): array { $points = []; foreach ($this->points as $point) { @@ -38,7 +38,7 @@ class Cluster extends Point implements IteratorAggregate, Countable return $points; } - public function toArray() : array + public function toArray(): array { return [ 'centroid' => parent::toArray(), @@ -46,7 +46,7 @@ class Cluster extends Point implements IteratorAggregate, Countable ]; } - 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 @@ class Cluster extends Point implements IteratorAggregate, Countable return $point; } - public function detach(Point $point) : Point + public function detach(Point $point): Point { $this->points->detach($point); @@ -76,7 +76,8 @@ class Cluster extends Point implements IteratorAggregate, Countable 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 f90de8a..6aa40a9 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 @@ class Point implements ArrayAccess $this->coordinates = $coordinates; } - public function toArray() : array + public function toArray(): array { return $this->coordinates; } @@ -66,7 +66,7 @@ class Point implements ArrayAccess 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 4412d53..0d4adf5 100644 --- a/src/Phpml/Clustering/KMeans/Space.php +++ b/src/Phpml/Clustering/KMeans/Space.php @@ -25,7 +25,7 @@ class Space extends SplObjectStorage $this->dimension = $dimension; } - public function toArray() : array + public function toArray(): array { $points = []; foreach ($this as $point) { @@ -35,7 +35,7 @@ class Space extends SplObjectStorage 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 @@ class Space extends SplObjectStorage parent::attach($point, $data); } - public function getDimension() : int + public function getDimension(): int { return $this->dimension; } @@ -92,7 +92,7 @@ class Space extends SplObjectStorage 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 @@ class Space extends SplObjectStorage /** * @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 @@ class Space extends SplObjectStorage /** * @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 @@ class Space extends SplObjectStorage return $clusters; } - protected function iterate($clusters) : bool + protected function iterate($clusters): bool { $convergence = true; @@ -177,19 +177,7 @@ class Space extends SplObjectStorage 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 @@ class Space extends SplObjectStorage 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 e485ffb..96c9019 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 153cb8f..d450842 100644 --- a/src/Phpml/CrossValidation/StratifiedRandomSplit.php +++ b/src/Phpml/CrossValidation/StratifiedRandomSplit.php @@ -21,7 +21,7 @@ class StratifiedRandomSplit extends RandomSplit /** * @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 @@ class StratifiedRandomSplit extends RandomSplit 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 e27b2e3..7d30b0b 100644 --- a/src/Phpml/Dataset/ArrayDataset.php +++ b/src/Phpml/Dataset/ArrayDataset.php @@ -31,12 +31,12 @@ class ArrayDataset implements Dataset $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 ef33e2c..f88fe31 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 @@ class CsvDataset extends ArrayDataset 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 @@ class CsvDataset extends ArrayDataset 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 ce75a8a..f851d85 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 a6352ba..ec64163 100644 --- a/src/Phpml/DimensionReduction/EigenTransformerBase.php +++ b/src/Phpml/DimensionReduction/EigenTransformerBase.php @@ -84,7 +84,7 @@ abstract class EigenTransformerBase /** * 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 d11e1a6..1981cb5 100644 --- a/src/Phpml/DimensionReduction/KernelPCA.php +++ b/src/Phpml/DimensionReduction/KernelPCA.php @@ -4,6 +4,8 @@ declare(strict_types=1); 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 @@ use Phpml\Math\Matrix; 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 @@ class KernelPCA extends PCA { $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 @@ class KernelPCA extends PCA * $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 @@ class KernelPCA extends PCA 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 @@ class KernelPCA extends PCA * * 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 @@ class KernelPCA extends PCA * * @throws \Exception */ - protected function getKernel(): \Closure + protected function getKernel(): Closure { switch ($this->kernel) { case self::KERNEL_LINEAR: @@ -173,11 +199,11 @@ class KernelPCA extends PCA }; 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 @@ class KernelPCA extends PCA 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 @@ class KernelPCA extends PCA // 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 26b2324..6400d14 100644 --- a/src/Phpml/DimensionReduction/LDA.php +++ b/src/Phpml/DimensionReduction/LDA.php @@ -4,6 +4,7 @@ declare(strict_types=1); 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 @@ class LDA extends EigenTransformerBase /** * 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 @@ class LDA extends EigenTransformerBase 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 @@ class LDA extends EigenTransformerBase * 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 @@ class LDA extends EigenTransformerBase if (!isset($means[$label][$col])) { $means[$label][$col] = 0.0; } + $means[$label][$col] += $val; $overallMean[$col] += $val; } @@ -146,7 +170,7 @@ class LDA extends EigenTransformerBase * 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 @@ class LDA extends EigenTransformerBase * 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 @@ class LDA extends EigenTransformerBase /** * 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 @@ class LDA extends EigenTransformerBase 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 25b7186..18879bb 100644 --- a/src/Phpml/DimensionReduction/PCA.php +++ b/src/Phpml/DimensionReduction/PCA.php @@ -4,6 +4,7 @@ declare(strict_types=1); 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 @@ class PCA extends EigenTransformerBase * $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 @@ class PCA extends EigenTransformerBase 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 @@ class PCA extends EigenTransformerBase * 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 @@ class PCA extends EigenTransformerBase 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 5d3e0db..8d6d5da 100644 --- a/src/Phpml/Exception/DatasetException.php +++ b/src/Phpml/Exception/DatasetException.php @@ -4,9 +4,11 @@ declare(strict_types=1); 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 39b9b03..719c2c2 100644 --- a/src/Phpml/Exception/FileException.php +++ b/src/Phpml/Exception/FileException.php @@ -4,19 +4,21 @@ declare(strict_types=1); 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 d96bb33..e02d14d 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -4,39 +4,41 @@ declare(strict_types=1); 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 @@ class InvalidArgumentException extends \Exception /** * @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 a52feaa..b309bff 100644 --- a/src/Phpml/Exception/MatrixException.php +++ b/src/Phpml/Exception/MatrixException.php @@ -4,19 +4,21 @@ declare(strict_types=1); 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 a7604e8..282fa1b 100644 --- a/src/Phpml/Exception/NormalizerException.php +++ b/src/Phpml/Exception/NormalizerException.php @@ -4,9 +4,11 @@ declare(strict_types=1); 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 913667a..6d1abaa 100644 --- a/src/Phpml/Exception/SerializeException.php +++ b/src/Phpml/Exception/SerializeException.php @@ -4,14 +4,16 @@ declare(strict_types=1); 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 b6717b9..fdb985f 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 6efd90f..4b678a4 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 e00fc69..e0bd402 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 @@ class TokenCountVectorizer implements Transformer $this->checkDocumentFrequency($samples); } - public function getVocabulary() : array + public function getVocabulary(): array { return array_flip($this->vocabulary); } @@ -80,7 +77,7 @@ class TokenCountVectorizer implements Transformer 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 @@ class TokenCountVectorizer implements Transformer } } - 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 15d62d8..4f661ba 100644 --- a/src/Phpml/Helper/OneVsRest.php +++ b/src/Phpml/Helper/OneVsRest.php @@ -36,6 +36,18 @@ trait OneVsRest $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 @@ trait OneVsRest } 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 @@ trait OneVsRest } } - /** - * 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 @@ trait OneVsRest 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 @@ trait OneVsRest /** * To be overwritten by OneVsRest classifiers. - * - * @return void */ abstract protected function resetBinary(): void; @@ -174,4 +150,27 @@ trait OneVsRest * @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 c119eae..153ffcb 100644 --- a/src/Phpml/Helper/Optimizer/ConjugateGradient.php +++ b/src/Phpml/Helper/Optimizer/ConjugateGradient.php @@ -4,6 +4,8 @@ declare(strict_types=1); 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 @@ namespace Phpml\Helper\Optimizer; */ 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 @@ class ConjugateGradient extends GD $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 @@ class ConjugateGradient extends GD * 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 @@ class ConjugateGradient extends GD /** * 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 @@ class ConjugateGradient extends GD * 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 @@ class ConjugateGradient extends GD 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 @@ class ConjugateGradient extends GD * * θ(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 @@ class ConjugateGradient extends GD * 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 @@ class ConjugateGradient extends GD * * 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 @@ class ConjugateGradient extends GD * 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 @@ class mp /** * 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 @@ class mp /** * 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 @@ class mp /** * 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 @@ class mp /** * 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 @@ class mp /** * 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 @@ class mp /** * 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 @@ class mp /** * 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 38b4253..4eadf28 100644 --- a/src/Phpml/Helper/Optimizer/GD.php +++ b/src/Phpml/Helper/Optimizer/GD.php @@ -4,6 +4,8 @@ declare(strict_types=1); 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 @@ class GD extends StochasticGD * 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 ee61321..2b25acd 100644 --- a/src/Phpml/Helper/Optimizer/Optimizer.php +++ b/src/Phpml/Helper/Optimizer/Optimizer.php @@ -4,6 +4,9 @@ declare(strict_types=1); 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 @@ abstract class Optimizer // 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 @@ abstract class Optimizer 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 @@ abstract class Optimizer * 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 f1b0979..07ad216 100644 --- a/src/Phpml/Helper/Optimizer/StochasticGD.php +++ b/src/Phpml/Helper/Optimizer/StochasticGD.php @@ -4,6 +4,8 @@ declare(strict_types=1); 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 @@ class StochasticGD extends Optimizer * 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 @@ class StochasticGD extends Optimizer 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 @@ class StochasticGD extends Optimizer 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 696ee4b..9faa8e0 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 40cdfbc..52e969c 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 f6a87cf..4ecc576 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 @@ class Euclidean implements Distance /** * 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 6d10b71..457333c 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 17df39d..5ff7364 100644 --- a/src/Phpml/Math/Distance/Minkowski.php +++ b/src/Phpml/Math/Distance/Minkowski.php @@ -22,7 +22,7 @@ class Minkowski 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/Kernel.php b/src/Phpml/Math/Kernel.php index 6d1461f..9a2cb97 100644 --- a/src/Phpml/Math/Kernel.php +++ b/src/Phpml/Math/Kernel.php @@ -7,10 +7,10 @@ namespace Phpml\Math; 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 e47dbb5..4f9cfaf 100644 --- a/src/Phpml/Math/Kernel/RBF.php +++ b/src/Phpml/Math/Kernel/RBF.php @@ -23,12 +23,11 @@ class RBF implements Kernel * @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 6261d26..4d7f662 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 @@ class EigenvalueDecomposition 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 @@ class EigenvalueDecomposition $g += $this->V[$k][$j] * $this->d[$k]; $this->e[$k] += $this->V[$k][$j] * $f; } + $this->e[$j] = $g; } @@ -185,16 +255,19 @@ class EigenvalueDecomposition 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 @@ class EigenvalueDecomposition 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 @@ class EigenvalueDecomposition 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 @@ class EigenvalueDecomposition 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 @@ class EigenvalueDecomposition 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 @@ class EigenvalueDecomposition 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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $p = $this->d[$j]; } } + if ($k != $i) { $this->d[$k] = $this->d[$i]; $this->d[$i] = $p; @@ -354,6 +438,7 @@ class EigenvalueDecomposition 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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition 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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition 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 @@ class EigenvalueDecomposition } 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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $this->e[$n - 1] = $z; $this->e[$n] = -$z; } + $n = $n - 2; $iter = 0; // No convergence yet @@ -564,16 +668,19 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition 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 @@ class EigenvalueDecomposition 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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition } else { $this->H[$i][$n] = -$r / ($eps * $norm); } + // Solve real equations } else { $x = $this->H[$i][$i + 1]; @@ -732,6 +856,7 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition } } } + // Complex vector } elseif ($q < 0) { $l = $n - 1; @@ -753,6 +879,7 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition 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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition 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; - } - - $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 164a72f..6ebd8cb 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 @@ class LUDecomposition 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 @@ class LUDecomposition 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 @@ class LUDecomposition $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 @@ class LUDecomposition * * @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 @@ class LUDecomposition * * @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 @@ class LUDecomposition * * @return array Pivot vector */ - public function getPivot() : array + public function getPivot(): array { return $this->piv; } @@ -247,7 +255,7 @@ class LUDecomposition * * @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 @@ class LUDecomposition } } } + // 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 @@ class LUDecomposition 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 6145521..7c1ff3e 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 @@ class Matrix $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 @@ class 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 @@ class Matrix /** * @throws MatrixException */ - public function getColumnValues($column) : array + public function getColumnValues($column): array { if ($column >= $this->columns) { throw MatrixException::columnOutOfRange(); @@ -123,7 +123,7 @@ class Matrix 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 @@ class 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 @@ class 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 @@ class 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 @@ class 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,15 +192,74 @@ class 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); } + public function inverse(): self + { + if (!$this->isSquare()) { + throw MatrixException::notSquareMatrix(); + } + + $LU = new LUDecomposition($this); + $identity = $this->getIdentity(); + $inverse = $LU->solve($identity); + + return new self($inverse, false); + } + + public function crossOut(int $row, int $column): self + { + $newMatrix = []; + $r = 0; + for ($i = 0; $i < $this->rows; ++$i) { + $c = 0; + if ($row != $i) { + for ($j = 0; $j < $this->columns; ++$j) { + if ($column != $j) { + $newMatrix[$r][$c] = $this->matrix[$i][$j]; + ++$c; + } + } + + ++$r; + } + } + + return new self($newMatrix, false); + } + + public function isSingular(): bool + { + return $this->getDeterminant() == 0; + } + + /** + * Returns the transpose of given array + */ + public static function transposeArray(array $array): array + { + return (new self($array, false))->transpose()->toArray(); + } + + /** + * Returns the dot product of two arrays
+ * Matrix::dot(x, y) ==> x.y' + */ + 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(Matrix $other, int $sign = 1) : Matrix + protected function _add(self $other, int $sign = 1): self { $a1 = $this->toArray(); $a2 = $other->toArray(); @@ -215,23 +274,10 @@ class Matrix return new self($newMatrix, false); } - public function inverse() : Matrix - { - if (!$this->isSquare()) { - throw MatrixException::notSquareMatrix(); - } - - $LU = new LUDecomposition($this); - $identity = $this->getIdentity(); - $inverse = $LU->solve($identity); - - return new self($inverse, false); - } - /** * Returns diagonal identity matrix of the same size of this matrix */ - protected function getIdentity() : Matrix + protected function getIdentity(): self { $array = array_fill(0, $this->rows, array_fill(0, $this->columns, 0)); for ($i = 0; $i < $this->rows; ++$i) { @@ -240,49 +286,4 @@ class Matrix return new self($array, false); } - - public function crossOut(int $row, int $column) : Matrix - { - $newMatrix = []; - $r = 0; - for ($i = 0; $i < $this->rows; ++$i) { - $c = 0; - if ($row != $i) { - for ($j = 0; $j < $this->columns; ++$j) { - if ($column != $j) { - $newMatrix[$r][$c] = $this->matrix[$i][$j]; - ++$c; - } - } - ++$r; - } - } - - return new self($newMatrix, false); - } - - public function isSingular() : bool - { - return 0 == $this->getDeterminant(); - } - - /** - * Returns the transpose of given array - */ - public static function transposeArray(array $array) : array - { - return (new self($array, false))->transpose()->toArray(); - } - - /** - * Returns the dot product of two arrays
- * Matrix::dot(x, y) ==> x.y' - */ - 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]; - } } diff --git a/src/Phpml/Math/Set.php b/src/Phpml/Math/Set.php index c5c5aa7..a67d5c2 100644 --- a/src/Phpml/Math/Set.php +++ b/src/Phpml/Math/Set.php @@ -4,12 +4,15 @@ declare(strict_types=1); 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 @@ class Set implements \IteratorAggregate /** * 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 @@ class Set implements \IteratorAggregate /** * 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 @@ class Set implements \IteratorAggregate /** * 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 @@ class Set implements \IteratorAggregate * * @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 @@ class Set implements \IteratorAggregate * * @return Set[] */ - public static function power(Set $a) : array + public static function power(self $a): array { $power = [new self()]; @@ -79,24 +82,10 @@ class Set implements \IteratorAggregate 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 @@ class Set implements \IteratorAggregate /** * @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 @@ class Set implements \IteratorAggregate /** * @param string|int|float $element */ - public function remove($element) : Set + public function remove($element): self { return $this->removeAll([$element]); } @@ -122,7 +111,7 @@ class Set implements \IteratorAggregate /** * @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 @@ class Set implements \IteratorAggregate /** * @param string|int|float $element */ - public function contains($element) : bool + public function contains($element): bool { return $this->containsAll([$element]); } @@ -140,7 +129,7 @@ class Set implements \IteratorAggregate /** * @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 @@ class Set implements \IteratorAggregate /** * @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 9bcf271..8803cbf 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 627a8a6..a669a7f 100644 --- a/src/Phpml/Math/Statistic/Covariance.php +++ b/src/Phpml/Math/Statistic/Covariance.php @@ -4,6 +4,7 @@ declare(strict_types=1); 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 @@ class Covariance * @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 @@ class Covariance } 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 @@ class Covariance if ($index == $i) { $val[0] = $col - $meanX; } + if ($index == $k) { $val[1] = $col - $meanY; } } + $sum += $val[0] * $val[1]; } } @@ -112,7 +115,7 @@ class Covariance * * @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 bdf8308..24aaeea 100644 --- a/src/Phpml/Math/Statistic/Gaussian.php +++ b/src/Phpml/Math/Statistic/Gaussian.php @@ -41,7 +41,7 @@ class Gaussian * 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 eb1baef..8791a65 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 @@ class Mean 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 3da8ef5..8a0d241 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 ae4c11a..0f27b06 100644 --- a/src/Phpml/Metric/ClassificationReport.php +++ b/src/Phpml/Metric/ClassificationReport.php @@ -51,27 +51,27 @@ class ClassificationReport $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 @@ class ClassificationReport $this->average[$metric] = 0.0; continue; } + $this->average[$metric] = array_sum($values) / count($values); } } @@ -102,7 +103,8 @@ class ClassificationReport */ 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 @@ class ClassificationReport */ 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 0f0b738..e86a8ed 100644 --- a/src/Phpml/Metric/ConfusionMatrix.php +++ b/src/Phpml/Metric/ConfusionMatrix.php @@ -6,7 +6,7 @@ namespace Phpml\Metric; 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 @@ class ConfusionMatrix return $matrix; } - private static function generateMatrixWithZeros(array $labels) : array + private static function generateMatrixWithZeros(array $labels): array { $count = count($labels); $matrix = []; @@ -43,7 +43,7 @@ class ConfusionMatrix 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 2fa14f5..ebcdbe4 100644 --- a/src/Phpml/ModelManager.php +++ b/src/Phpml/ModelManager.php @@ -26,7 +26,7 @@ class ModelManager } } - 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 65ba7b4..5b91425 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 75b2ff1..764bc4e 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 081b8a5..da428a4 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 5c66fd9..6378606 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/HyperbolicTangent.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/HyperbolicTangent.php @@ -21,7 +21,7 @@ class HyperbolicTangent implements ActivationFunction /** * @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 60ade03..fc7ff62 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php @@ -21,7 +21,7 @@ class PReLU implements ActivationFunction /** * @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 dec45a2..4ae9603 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php @@ -21,7 +21,7 @@ class Sigmoid implements ActivationFunction /** * @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 dbe8ee6..2bb1cc7 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php @@ -21,7 +21,7 @@ class ThresholdedReLU implements ActivationFunction /** * @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 c70bdb3..7424348 100644 --- a/src/Phpml/NeuralNetwork/Layer.php +++ b/src/Phpml/NeuralNetwork/Layer.php @@ -28,20 +28,6 @@ class Layer } } - /** - * @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 @@ class Layer /** * @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 af04f4a..c2248a6 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 3baa5ac..4f05398 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 @@ abstract class LayeredNetwork implements Network /** * @return Layer[] */ - public function getLayers() : array + public function getLayers(): array { return $this->layers; } @@ -39,7 +39,7 @@ abstract class LayeredNetwork implements Network 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 @@ abstract class LayeredNetwork implements Network * * @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 94a8423..a38e952 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -20,41 +20,36 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, { use Predictable; - /** - * @var int - */ - private $inputLayerFeatures; - - /** - * @var array - */ - private $hiddenLayers; - /** * @var array */ protected $classes = []; - /** - * @var int - */ - private $iterations; - /** * @var ActivationFunction */ protected $activationFunction; - /** - * @var float - */ - private $learningRate; - /** * @var Backpropagation */ protected $backpropagation = null; + /** + * @var int + */ + private $inputLayerFeatures; + + /** + * @var array + */ + private $hiddenLayers = []; + + /** + * @var float + */ + private $learningRate; + /** * @throws InvalidArgumentException */ @@ -78,18 +73,6 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, $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 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, $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 6627c02..0b7726f 100644 --- a/src/Phpml/NeuralNetwork/Node.php +++ b/src/Phpml/NeuralNetwork/Node.php @@ -6,5 +6,5 @@ namespace Phpml\NeuralNetwork; 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 4f32884..ac3fb8b 100644 --- a/src/Phpml/NeuralNetwork/Node/Bias.php +++ b/src/Phpml/NeuralNetwork/Node/Bias.php @@ -8,7 +8,7 @@ use Phpml\NeuralNetwork\Node; 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 8ff78ea..ce33439 100644 --- a/src/Phpml/NeuralNetwork/Node/Input.php +++ b/src/Phpml/NeuralNetwork/Node/Input.php @@ -18,7 +18,7 @@ class Input implements Node $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 096d54f..a6c10e6 100644 --- a/src/Phpml/NeuralNetwork/Node/Neuron.php +++ b/src/Phpml/NeuralNetwork/Node/Neuron.php @@ -5,6 +5,7 @@ declare(strict_types=1); 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 @@ class Neuron implements Node 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 0883f4e..08899bf 100644 --- a/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php +++ b/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php @@ -27,12 +27,7 @@ class Synapse $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 @@ class Synapse $this->weight += $delta; } - public function getWeight() : float + public function getWeight(): float { return $this->weight; } @@ -51,4 +46,9 @@ class Synapse { 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 6722bd1..8382a8e 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -47,6 +47,7 @@ class Backpropagation } } } + $this->prevSigmas = $this->sigmas; } @@ -55,7 +56,7 @@ class Backpropagation $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 @@ class Backpropagation if ($targetClass === $key) { $value = 1; } + $sigma *= ($value - $neuronOutput); } else { $sigma *= $this->getPrevSigma($neuron); @@ -75,7 +77,7 @@ class Backpropagation 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 23560fe..f21c7b1 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php @@ -29,12 +29,12 @@ class Sigma 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 a72e634..480a980 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 @@ class Pipeline implements Estimator /** * @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 d2dbfcd..bd40948 100644 --- a/src/Phpml/Preprocessing/Imputer.php +++ b/src/Phpml/Preprocessing/Imputer.php @@ -9,6 +9,7 @@ use Phpml\Preprocessing\Imputer\Strategy; class Imputer implements Preprocessor { public const AXIS_COLUMN = 0; + public const AXIS_ROW = 1; /** @@ -64,9 +65,9 @@ class Imputer implements Preprocessor } } - 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 91badfb..3ad0321 100644 --- a/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php +++ b/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php @@ -9,7 +9,7 @@ use Phpml\Preprocessing\Imputer\Strategy; 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 f010bea..ffd9983 100644 --- a/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php +++ b/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php @@ -9,7 +9,7 @@ use Phpml\Preprocessing\Imputer\Strategy; 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 f038345..b2721de 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -11,7 +11,9 @@ use Phpml\Math\Statistic\StandardDeviation; 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 @@ class Normalizer implements Preprocessor $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 @@ class Normalizer implements Preprocessor $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 @@ class Normalizer implements Preprocessor 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 f8adcb2..6ecfafc 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 @@ class LeastSquares implements Regression 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 @@ class LeastSquares implements Regression /** * Add one dimension for intercept calculation. */ - private function getSamplesMatrix() : Matrix + private function getSamplesMatrix(): Matrix { $samples = []; foreach ($this->samples as $sample) { @@ -90,7 +90,7 @@ class LeastSquares implements Regression 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 b057d01..2ce938e 100644 --- a/src/Phpml/SupportVectorMachine/DataTransformer.php +++ b/src/Phpml/SupportVectorMachine/DataTransformer.php @@ -34,7 +34,7 @@ class DataTransformer 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 @@ class DataTransformer 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 cbee23d..2415eda 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -167,7 +167,7 @@ class SupportVectorMachine } /** - * @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 e1f0f35..f2dffd9 100644 --- a/src/Phpml/Tokenization/Tokenizer.php +++ b/src/Phpml/Tokenization/Tokenizer.php @@ -6,5 +6,5 @@ namespace Phpml\Tokenization; 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 14e7d0a..5b071b8 100644 --- a/src/Phpml/Tokenization/WhitespaceTokenizer.php +++ b/src/Phpml/Tokenization/WhitespaceTokenizer.php @@ -6,7 +6,7 @@ namespace Phpml\Tokenization; 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 03d134b..68a75ea 100644 --- a/src/Phpml/Tokenization/WordTokenizer.php +++ b/src/Phpml/Tokenization/WordTokenizer.php @@ -6,7 +6,7 @@ namespace Phpml\Tokenization; 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 3b47483..7b637c9 100644 --- a/tests/Phpml/Association/AprioriTest.php +++ b/tests/Phpml/Association/AprioriTest.php @@ -7,6 +7,7 @@ namespace tests\Phpml\Classification; use Phpml\Association\Apriori; use Phpml\ModelManager; use PHPUnit\Framework\TestCase; +use ReflectionClass; class AprioriTest extends TestCase { @@ -172,7 +173,6 @@ class AprioriTest extends TestCase /** * 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 @@ class AprioriTest extends TestCase */ 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 @@ class AprioriTest extends TestCase $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 c7d2d2a..8533500 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 @@ class DecisionTreeTest extends TestCase $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 @@ class DecisionTreeTest extends TestCase [$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 2cc8090..e01066c 100644 --- a/tests/Phpml/Classification/Ensemble/AdaBoostTest.php +++ b/tests/Phpml/Classification/Ensemble/AdaBoostTest.php @@ -52,7 +52,7 @@ class AdaBoostTest extends TestCase $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 5bca8de..175b79a 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 @@ class BaggingTest extends TestCase $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 @@ class BaggingTest extends TestCase { return [ DecisionTree::class => ['depth' => 5], - NaiveBayes::class => [] + NaiveBayes::class => [], ]; } @@ -117,6 +117,7 @@ class BaggingTest extends TestCase 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 e4ca9e5..f2871e3 100644 --- a/tests/Phpml/Classification/Ensemble/RandomForestTest.php +++ b/tests/Phpml/Classification/Ensemble/RandomForestTest.php @@ -7,9 +7,20 @@ namespace tests\Phpml\Classification\Ensemble; 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 @@ class RandomForestTest extends BaggingTest { 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 7ef6182..d9114b5 100644 --- a/tests/Phpml/Classification/KNearestNeighborsTest.php +++ b/tests/Phpml/Classification/KNearestNeighborsTest.php @@ -73,7 +73,7 @@ class KNearestNeighborsTest extends TestCase $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 46f1a34..0a90a18 100644 --- a/tests/Phpml/Classification/Linear/AdalineTest.php +++ b/tests/Phpml/Classification/Linear/AdalineTest.php @@ -35,7 +35,7 @@ class AdalineTest extends TestCase $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 @@ class AdalineTest extends TestCase $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 @@ class AdalineTest extends TestCase $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 fa522ad..7fbabec 100644 --- a/tests/Phpml/Classification/Linear/DecisionStumpTest.php +++ b/tests/Phpml/Classification/Linear/DecisionStumpTest.php @@ -40,7 +40,7 @@ class DecisionStumpTest extends TestCase $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 @@ class DecisionStumpTest extends TestCase $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 2721067..17b1db8 100644 --- a/tests/Phpml/Classification/Linear/PerceptronTest.php +++ b/tests/Phpml/Classification/Linear/PerceptronTest.php @@ -37,7 +37,7 @@ class PerceptronTest extends TestCase $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 @@ class PerceptronTest extends TestCase $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 @@ class PerceptronTest extends TestCase $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 519dc90..20bc5e1 100644 --- a/tests/Phpml/Classification/MLPClassifierTest.php +++ b/tests/Phpml/Classification/MLPClassifierTest.php @@ -150,7 +150,7 @@ class MLPClassifierTest extends TestCase $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 @@ class MLPClassifierTest extends TestCase 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 5b14f7e..c423c6d 100644 --- a/tests/Phpml/Classification/NaiveBayesTest.php +++ b/tests/Phpml/Classification/NaiveBayesTest.php @@ -59,7 +59,7 @@ class NaiveBayesTest extends TestCase $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 f734297..0941b34 100644 --- a/tests/Phpml/Classification/SVCTest.php +++ b/tests/Phpml/Classification/SVCTest.php @@ -57,7 +57,7 @@ class SVCTest extends TestCase $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 d8fb0fe..f12fb03 100644 --- a/tests/Phpml/Clustering/DBSCANTest.php +++ b/tests/Phpml/Clustering/DBSCANTest.php @@ -34,10 +34,25 @@ class DBSCANTest extends TestCase 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 5aed678..7b19422 100644 --- a/tests/Phpml/Clustering/FuzzyCMeansTest.php +++ b/tests/Phpml/Clustering/FuzzyCMeansTest.php @@ -20,6 +20,7 @@ class FuzzyCMeansTest extends TestCase unset($samples[$index]); } } + $this->assertCount(0, $samples); return $fcm; @@ -35,6 +36,7 @@ class FuzzyCMeansTest extends TestCase 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 e665c9f..f453340 100644 --- a/tests/Phpml/Clustering/KMeansTest.php +++ b/tests/Phpml/Clustering/KMeansTest.php @@ -23,6 +23,7 @@ class KMeansTest extends TestCase unset($samples[$index]); } } + $this->assertCount(0, $samples); } diff --git a/tests/Phpml/DimensionReduction/KernelPCATest.php b/tests/Phpml/DimensionReduction/KernelPCATest.php index 66b4622..3f37e3c 100644 --- a/tests/Phpml/DimensionReduction/KernelPCATest.php +++ b/tests/Phpml/DimensionReduction/KernelPCATest.php @@ -16,12 +16,12 @@ class KernelPCATest extends TestCase // 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 @@ class KernelPCATest extends TestCase [-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 19124a0..42a0283 100644 --- a/tests/Phpml/DimensionReduction/LDATest.php +++ b/tests/Phpml/DimensionReduction/LDATest.php @@ -28,7 +28,7 @@ class LDATest extends TestCase [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 @@ class LDATest extends TestCase [-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 0a60004..38b4744 100644 --- a/tests/Phpml/DimensionReduction/PCATest.php +++ b/tests/Phpml/DimensionReduction/PCATest.php @@ -26,12 +26,12 @@ class PCATest extends TestCase [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 0e1adbc..a8faacb 100644 --- a/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php +++ b/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php @@ -14,13 +14,41 @@ class TfIdfTransformerTest extends TestCase // 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 3419a29..463570c 100644 --- a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php +++ b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php @@ -33,9 +33,42 @@ class TokenCountVectorizerTest extends TestCase ]; $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 @@ class TokenCountVectorizerTest extends TestCase ]; $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 @@ class TokenCountVectorizerTest extends TestCase ]; $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 @@ class TokenCountVectorizerTest extends TestCase ]; $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 d31b1ae..2c72f5f 100644 --- a/tests/Phpml/Math/ComparisonTest.php +++ b/tests/Phpml/Math/ComparisonTest.php @@ -23,18 +23,15 @@ class ComparisonTest extends TestCase } /** - * @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 688874c..3956830 100644 --- a/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php +++ b/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php @@ -19,7 +19,7 @@ class EigenDecompositionTest extends TestCase // 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 @@ class EigenDecompositionTest extends TestCase 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 cd9fff2..8d0e1be 100644 --- a/tests/Phpml/Math/MatrixTest.php +++ b/tests/Phpml/Math/MatrixTest.php @@ -239,12 +239,12 @@ class MatrixTest extends TestCase { $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 9c3a4b4..bd55d11 100644 --- a/tests/Phpml/Math/ProductTest.php +++ b/tests/Phpml/Math/ProductTest.php @@ -6,6 +6,7 @@ namespace tests\Phpml\Math; use Phpml\Math\Product; use PHPUnit\Framework\TestCase; +use stdClass; class ProductTest extends TestCase { @@ -16,6 +17,6 @@ class ProductTest extends TestCase $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 a572a42..645e607 100644 --- a/tests/Phpml/Math/SetTest.php +++ b/tests/Phpml/Math/SetTest.php @@ -13,7 +13,7 @@ class SetTest extends TestCase { $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 @@ class SetTest extends TestCase { $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 @@ class SetTest extends TestCase { $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 3a8c9d3..97ec194 100644 --- a/tests/Phpml/Math/Statistic/CovarianceTest.php +++ b/tests/Phpml/Math/Statistic/CovarianceTest.php @@ -31,7 +31,7 @@ class CovarianceTest extends TestCase ]; $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 b7ff02d..fb3471a 100644 --- a/tests/Phpml/Metric/ClassificationReportTest.php +++ b/tests/Phpml/Metric/ClassificationReportTest.php @@ -16,11 +16,31 @@ class ClassificationReportTest extends TestCase $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 @@ class ClassificationReportTest extends TestCase $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 @@ class ClassificationReportTest extends TestCase $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 @@ class ClassificationReportTest extends TestCase $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 95e95cb..91fc1c7 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php @@ -19,10 +19,7 @@ class BinaryStepTest extends TestCase $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 f7af7c0..58b4b87 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php @@ -19,10 +19,7 @@ class GaussianTest extends TestCase $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 95f437f..00348d9 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php @@ -19,10 +19,7 @@ class HyperboliTangentTest extends TestCase $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 873520e..7e8e718 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php @@ -19,10 +19,7 @@ class PReLUTest extends TestCase $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 096a376..d5a0ea3 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php @@ -19,10 +19,7 @@ class SigmoidTest extends TestCase $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 1800d7b..19a0312 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php @@ -19,16 +19,13 @@ class ThresholdedReLUTest extends TestCase $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 284b1eb..72d8758 100644 --- a/tests/Phpml/NeuralNetwork/LayerTest.php +++ b/tests/Phpml/NeuralNetwork/LayerTest.php @@ -8,6 +8,7 @@ use Phpml\NeuralNetwork\Layer; use Phpml\NeuralNetwork\Node\Bias; use Phpml\NeuralNetwork\Node\Neuron; use PHPUnit\Framework\TestCase; +use stdClass; class LayerTest extends TestCase { @@ -43,7 +44,7 @@ class LayerTest extends TestCase */ 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 48bee66..c1779b8 100644 --- a/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php +++ b/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php @@ -44,10 +44,7 @@ class LayeredNetworkTest extends TestCase $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 02d6dfa..1c09eae 100644 --- a/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php +++ b/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php @@ -7,6 +7,7 @@ namespace tests\Phpml\NeuralNetwork\Node\Neuron; 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 @@ class SynapseTest extends TestCase } /** - * @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 89f1ca1..a58f2ec 100644 --- a/tests/Phpml/NeuralNetwork/Node/NeuronTest.php +++ b/tests/Phpml/NeuralNetwork/Node/NeuronTest.php @@ -8,6 +8,7 @@ use Phpml\NeuralNetwork\ActivationFunction\BinaryStep; 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 @@ class NeuronTest extends TestCase } /** - * @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 fc06e56..caf1961 100644 --- a/tests/Phpml/PipelineTest.php +++ b/tests/Phpml/PipelineTest.php @@ -72,7 +72,7 @@ class PipelineTest extends TestCase { $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 22ed1bd..3d5940a 100644 --- a/tests/Phpml/Preprocessing/NormalizerTest.php +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -109,8 +109,9 @@ class NormalizerTest extends TestCase 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 7d835a2..7517a9b 100644 --- a/tests/Phpml/Regression/LeastSquaresTest.php +++ b/tests/Phpml/Regression/LeastSquaresTest.php @@ -81,7 +81,7 @@ class LeastSquaresTest extends TestCase $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 3cd0ee5..a220d21 100644 --- a/tests/Phpml/Regression/SVRTest.php +++ b/tests/Phpml/Regression/SVRTest.php @@ -48,7 +48,7 @@ class SVRTest extends TestCase $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 0563f3f..7b774f2 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 bd40948..fdf8796 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 7b637c9..68456ff 100644 --- a/tests/Phpml/Association/AprioriTest.php +++ b/tests/Phpml/Association/AprioriTest.php @@ -178,7 +178,7 @@ class AprioriTest extends TestCase * * @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 20bc5e1..ef62d06 100644 --- a/tests/Phpml/Classification/MLPClassifierTest.php +++ b/tests/Phpml/Classification/MLPClassifierTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); 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 @@ class MLPClassifierTest extends TestCase $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 @@ class MLPClassifierTest extends TestCase ); } - /** - * @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 f453340..d212157 100644 --- a/tests/Phpml/Clustering/KMeansTest.php +++ b/tests/Phpml/Clustering/KMeansTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace tests\Phpml\Clustering; use Phpml\Clustering\KMeans; +use Phpml\Exception\InvalidArgumentException; use PHPUnit\Framework\TestCase; class KMeansTest extends TestCase @@ -51,11 +52,9 @@ class KMeansTest extends TestCase $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 070e36b..8058fd6 100644 --- a/tests/Phpml/CrossValidation/RandomSplitTest.php +++ b/tests/Phpml/CrossValidation/RandomSplitTest.php @@ -6,23 +6,20 @@ namespace tests\Phpml\CrossValidation; 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 41e037b..e0a6b91 100644 --- a/tests/Phpml/Dataset/ArrayDatasetTest.php +++ b/tests/Phpml/Dataset/ArrayDatasetTest.php @@ -5,15 +5,14 @@ declare(strict_types=1); 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 f5cc851..5049253 100644 --- a/tests/Phpml/Dataset/CsvDatasetTest.php +++ b/tests/Phpml/Dataset/CsvDatasetTest.php @@ -5,15 +5,14 @@ declare(strict_types=1); 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 0592d06..ee08395 100644 --- a/tests/Phpml/Dataset/FilesDatasetTest.php +++ b/tests/Phpml/Dataset/FilesDatasetTest.php @@ -5,15 +5,14 @@ declare(strict_types=1); 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 4b715ef..6d97a23 100644 --- a/tests/Phpml/FeatureExtraction/StopWordsTest.php +++ b/tests/Phpml/FeatureExtraction/StopWordsTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace tests\Phpml\FeatureExtraction; +use Phpml\Exception\InvalidArgumentException; use Phpml\FeatureExtraction\StopWords; use PHPUnit\Framework\TestCase; @@ -22,11 +23,9 @@ class StopWordsTest extends TestCase $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 2c72f5f..ecb58c2 100644 --- a/tests/Phpml/Math/ComparisonTest.php +++ b/tests/Phpml/Math/ComparisonTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace tests\Phpml\Math; +use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Comparison; use PHPUnit\Framework\TestCase; @@ -22,12 +23,10 @@ class ComparisonTest extends TestCase $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 893c000..56d6685 100644 --- a/tests/Phpml/Math/Distance/ChebyshevTest.php +++ b/tests/Phpml/Math/Distance/ChebyshevTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace tests\Phpml\Metric; +use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Distance\Chebyshev; use PHPUnit\Framework\TestCase; @@ -19,14 +20,11 @@ class ChebyshevTest extends TestCase $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 03bf7f3..4acb3d4 100644 --- a/tests/Phpml/Math/Distance/EuclideanTest.php +++ b/tests/Phpml/Math/Distance/EuclideanTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace tests\Phpml\Metric; +use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Distance\Euclidean; use PHPUnit\Framework\TestCase; @@ -19,14 +20,11 @@ class EuclideanTest extends TestCase $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 9c20edd..2a05874 100644 --- a/tests/Phpml/Math/Distance/ManhattanTest.php +++ b/tests/Phpml/Math/Distance/ManhattanTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace tests\Phpml\Metric; +use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Distance\Manhattan; use PHPUnit\Framework\TestCase; @@ -19,14 +20,11 @@ class ManhattanTest extends TestCase $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 81ecd97..a8159d7 100644 --- a/tests/Phpml/Math/Distance/MinkowskiTest.php +++ b/tests/Phpml/Math/Distance/MinkowskiTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace tests\Phpml\Metric; +use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Distance\Minkowski; use PHPUnit\Framework\TestCase; @@ -19,14 +20,11 @@ class MinkowskiTest extends TestCase $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 8d0e1be..fce83bf 100644 --- a/tests/Phpml/Math/MatrixTest.php +++ b/tests/Phpml/Math/MatrixTest.php @@ -4,16 +4,16 @@ declare(strict_types=1); 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 @@ class MatrixTest extends TestCase $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 @@ class MatrixTest extends TestCase $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 @@ class MatrixTest extends TestCase $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 7fd2cec..c091bb2 100644 --- a/tests/Phpml/Math/Statistic/CorrelationTest.php +++ b/tests/Phpml/Math/Statistic/CorrelationTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace test\Phpml\Math\StandardDeviation; +use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Statistic\Correlation; use PHPUnit\Framework\TestCase; @@ -29,11 +30,9 @@ class CorrelationTest extends TestCase $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 86553e0..f19479a 100644 --- a/tests/Phpml/Math/Statistic/MeanTest.php +++ b/tests/Phpml/Math/Statistic/MeanTest.php @@ -4,16 +4,15 @@ declare(strict_types=1); 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 @@ class MeanTest extends TestCase $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 @@ class MeanTest extends TestCase $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 ead67b5..4bc4392 100644 --- a/tests/Phpml/Math/Statistic/StandardDeviationTest.php +++ b/tests/Phpml/Math/Statistic/StandardDeviationTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace test\Phpml\Math\StandardDeviation; +use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Statistic\StandardDeviation; use PHPUnit\Framework\TestCase; @@ -25,19 +26,15 @@ class StandardDeviationTest extends TestCase $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 885893d..e876da1 100644 --- a/tests/Phpml/Metric/AccuracyTest.php +++ b/tests/Phpml/Metric/AccuracyTest.php @@ -7,20 +7,18 @@ namespace tests\Phpml\Metric; 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 e44a32a..f47efb7 100644 --- a/tests/Phpml/ModelManagerTest.php +++ b/tests/Phpml/ModelManagerTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace tests; +use Phpml\Exception\FileException; use Phpml\ModelManager; use Phpml\Regression\LeastSquares; use PHPUnit\Framework\TestCase; @@ -23,11 +24,9 @@ class ModelManagerTest extends TestCase $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 72d8758..11f5e6a 100644 --- a/tests/Phpml/NeuralNetwork/LayerTest.php +++ b/tests/Phpml/NeuralNetwork/LayerTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); 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 @@ class LayerTest extends TestCase } } - /** - * @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 3d5940a..5dfc207 100644 --- a/tests/Phpml/Preprocessing/NormalizerTest.php +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -4,16 +4,15 @@ declare(strict_types=1); 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 ebbf99f..59154e3 100644 --- a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace tests\Phpml\SupportVectorMachine; +use Phpml\Exception\InvalidArgumentException; use Phpml\SupportVectorMachine\Kernel; use Phpml\SupportVectorMachine\SupportVectorMachine; use Phpml\SupportVectorMachine\Type; @@ -81,32 +82,26 @@ SV $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 6b8cdd5..3818161 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 @@ class LogisticRegression extends Adaline 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 @@ class LogisticRegression extends Adaline $hX = 1e-10; } + $y = $y < 0 ? 0 : 1; + $error = -$y * log($hX) - (1 - $y) * log(1 - $hX); $gradient = $hX - $y; @@ -213,6 +215,8 @@ class LogisticRegression extends Adaline $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 @@ class LogisticRegression extends Adaline { $output = $this->output($sample); - if (round($output) > 0.5) { + if ($output > 0.5) { return 1; } @@ -260,14 +264,13 @@ class LogisticRegression extends Adaline */ protected function predictProbability(array $sample, $label): float { - $predicted = $this->predictSampleBinary($sample); + $sample = $this->checkNormalizedSample($sample); + $probability = $this->output($sample); - if ((string) $predicted == (string) $label) { - $sample = $this->checkNormalizedSample($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 0000000..85fc159 --- /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 a6b060a..72d0b4b 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 a38e952..5ace597 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -95,6 +95,12 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, } } + 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 8382a8e..df515b2 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 0000000..c244c27 --- /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 69f0eb7..874d0d2 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 9fb8baa..0000000 --- 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 8f8a68b..bf26816 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 7d2f451..b2d5f9e 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. It's as easy as executing `./bin/php-cs-fixer fix` in root directory. +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): + +```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 7b774f2..b08d6f8 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 e10b2d8..4236a2d 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 ef3a0e9..f817b0a 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 14d8770..9155b10 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 0000000..19e3c20 --- /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 e13f556..76d8624 100644 --- a/src/Phpml/Association/Apriori.php +++ b/src/Phpml/Association/Apriori.php @@ -244,6 +244,7 @@ class Apriori implements Associator 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 5bb730b..d660322 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 f3f9449..5164472 100644 --- a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -9,7 +9,7 @@ use Phpml\Math\Comparison; 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 6fa1ec8..a3d8e5e 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 cda746f..de2e152 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 3f6eb58..439ea56 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -224,7 +224,7 @@ class DecisionStump extends WeightedClassifier 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 3818161..b595587 100644 --- a/src/Phpml/Classification/Linear/LogisticRegression.php +++ b/src/Phpml/Classification/Linear/LogisticRegression.php @@ -119,19 +119,25 @@ class LogisticRegression extends Adaline * * @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 @@ class LogisticRegression extends Adaline */ 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 0db7496..6838227 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 @@ class Perceptron implements Classifier, IncrementalEstimator { $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 6aa40a9..8c918a7 100644 --- a/src/Phpml/Clustering/KMeans/Point.php +++ b/src/Phpml/Clustering/KMeans/Point.php @@ -48,12 +48,15 @@ class Point implements ArrayAccess */ 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 0d4adf5..371bbc3 100644 --- a/src/Phpml/Clustering/KMeans/Space.php +++ b/src/Phpml/Clustering/KMeans/Space.php @@ -45,7 +45,7 @@ class Space extends SplObjectStorage } /** - * @param null $data + * @param null $data */ public function addPoint(array $coordinates, $data = null): void { @@ -124,10 +124,12 @@ class Space extends SplObjectStorage 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 @@ class Space extends SplObjectStorage } $clusters[] = new Cluster($this, $point->getCoordinates()); + break; } } diff --git a/src/Phpml/DimensionReduction/KernelPCA.php b/src/Phpml/DimensionReduction/KernelPCA.php index 1981cb5..65bb324 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 e0bd402..e0d4e10 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 4f661ba..1c0ca93 100644 --- a/src/Phpml/Helper/OneVsRest.php +++ b/src/Phpml/Helper/OneVsRest.php @@ -33,7 +33,7 @@ trait OneVsRest // 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 4eadf28..11577c9 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 07ad216..18a5f0c 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 @@ class StochasticGD extends Optimizer * 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 @@ class StochasticGD extends Optimizer // 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 4d7f662..1730309 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 6ebd8cb..151e2cc 100644 --- a/src/Phpml/Math/LinearAlgebra/LUDecomposition.php +++ b/src/Phpml/Math/LinearAlgebra/LUDecomposition.php @@ -212,9 +212,9 @@ class LUDecomposition /** * 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 a67d5c2..dcb02e6 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 @@ class Set implements IteratorAggregate } /** - * @param string|int|float $element + * @param string|int|float|bool $element */ public function add($element): self { @@ -91,7 +91,7 @@ class Set implements IteratorAggregate } /** - * @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 0f27b06..0c3198f 100644 --- a/src/Phpml/Metric/ClassificationReport.php +++ b/src/Phpml/Metric/ClassificationReport.php @@ -91,6 +91,7 @@ class ClassificationReport $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 5ace597..1a997be 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 a6c10e6..2dff600 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 @@ class Neuron implements Node 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 @@ class Neuron implements Node 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 df515b2..4144c30 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 fdf8796..593756c 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 2ce938e..6516cbf 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 2415eda..4565332 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 68456ff..8b95237 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 @@ class AprioriTest extends TestCase /** * 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 626ed64..abcf10d 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 8533500..9478a2a 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 e01066c..7677c31 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 175b79a..69c4d01 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 f2871e3..ea0cce1 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 @@ class RandomForestTest extends BaggingTest $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 d9114b5..d96152a 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 0a90a18..c8ca752 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 7fbabec..93c8595 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 17b1db8..35af855 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 ef62d06..ef40618 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 c423c6d..8312e9c 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 0941b34..0709e04 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 f12fb03..c0d0401 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 7b19422..1c3af15 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 d212157..dedf981 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 8058fd6..75f60ff 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 e953ca7..5309dc6 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 e0a6b91..bca9e43 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 5049253..90eb1d0 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 8feef45..0d873e6 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 faa48c6..4fb2ee2 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 e0324b4..1b7a982 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 ee08395..08b568d 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 3f37e3c..05a4138 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 42a0283..2803a4b 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 38b4744..337b253 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 6d97a23..5780c39 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 a8faacb..30bc0e5 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 463570c..aaba5fc 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 ecb58c2..0118ee4 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 @@ use PHPUnit\Framework\TestCase; 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 56d6685..262927b 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 4acb3d4..734bbd2 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 2a05874..2eb9f06 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 a8159d7..6c7b897 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 3ed7017..08f795d 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 3956830..47c4798 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 fce83bf..da535bf 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 bd55d11..da7450b 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 645e607..2335c73 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 c091bb2..eebb065 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 97ec194..2b64854 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 8030f85..e4627f4 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 f19479a..3b98032 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 4bc4392..8333740 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 e876da1..08d1c44 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 fb3471a..483f769 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 f958bdf..590aff8 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 f47efb7..48ab747 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 91fc1c7..acc7977 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 58b4b87..2b08793 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 00348d9..91e7eba 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 7e8e718..f407060 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 d5a0ea3..30b50f8 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 19a0312..f46ff02 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 11f5e6a..baa3f9a 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 c1779b8..21f5e9c 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 @@ class LayeredNetworkTest extends TestCase $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 ee311f5..e42a737 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 2d3be71..09ca831 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 1c09eae..973d216 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 @@ class SynapseTest extends TestCase /** * @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 a58f2ec..03e309d 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 @@ class NeuronTest extends TestCase 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 caf1961..e45675d 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 658b454..c229c15 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 5dfc207..ea762da 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 7517a9b..71215bc 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 a220d21..d2fdefb 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 b1f8522..1db1fdf 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 59154e3..180b8d3 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 f9d7c73..b9e40c0 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 607c327..d18edb6 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 | 744 +++++++++--------- 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, 457 insertions(+), 454 deletions(-) delete mode 100644 humbug.json.dist diff --git a/.gitignore b/.gitignore index 38ef5a0..3ffb1da 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ /vendor/ -humbuglog.* .php_cs.cache /build diff --git a/.travis.yml b/.travis.yml index bf26816..f415daf 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 b08d6f8..149ce27 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 4236a2d..9318786 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/coding-standard", - "version": "v3.0.0-RC3", + "name": "symplify/better-reflection-docblock", + "version": "v3.1.2", "source": { "type": "git", - "url": "https://github.com/Symplify/CodingStandard.git", - "reference": "0a3958f1cb6ce733def98f3abdf52a4e6c723879" + "url": "https://github.com/Symplify/BetterReflectionDocBlock.git", + "reference": "7746ed526ffedfb4907a7ff83606a9e0f1e55c56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Symplify/CodingStandard/zipball/0a3958f1cb6ce733def98f3abdf52a4e6c723879", - "reference": "0a3958f1cb6ce733def98f3abdf52a4e6c723879", + "url": "https://api.github.com/repos/Symplify/BetterReflectionDocBlock/zipball/7746ed526ffedfb4907a7ff83606a9e0f1e55c56", + "reference": "7746ed526ffedfb4907a7ff83606a9e0f1e55c56", "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" + "phpdocumentor/reflection-docblock": "4.2", + "symplify/package-builder": "^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" }, "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\\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.1.2", + "source": { + "type": "git", + "url": "https://github.com/Symplify/CodingStandard.git", + "reference": "0985870bd373d65c69747c2ae854761497f96aac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Symplify/CodingStandard/zipball/0985870bd373d65c69747c2ae854761497f96aac", + "reference": "0985870bd373d65c69747c2ae854761497f96aac", + "shasum": "" + }, + "require": { + "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/token-runner": "^3.1" + }, + "require-dev": { + "nette/application": "^2.4", + "phpunit/phpunit": "^6.5", + "symplify/easy-coding-standard": "^3.1", + "symplify/package-builder": "^3.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "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,7 +3584,47 @@ "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": "symplify/token-runner", + "version": "v3.1.2", + "source": { + "type": "git", + "url": "https://github.com/Symplify/TokenRunner.git", + "reference": "5c4cc4f24507b6cbdb33026dfad5b46660c5b3ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Symplify/TokenRunner/zipball/5c4cc4f24507b6cbdb33026dfad5b46660c5b3ec", + "reference": "5c4cc4f24507b6cbdb33026dfad5b46660c5b3ec", + "shasum": "" + }, + "require": { + "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": { + "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-01-02T22:35:18+00:00" }, { "name": "theseer/tokenizer", @@ -3589,73 +3666,6 @@ "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", @@ -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 9155b10..abf30af 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 2535633..0000000 --- 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 19e3c20..0366c23 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 d660322..1d5a754 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 @@ class DecisionTree implements Classifier // 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 @@ class DecisionTree implements Classifier $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 5164472..cc53eea 100644 --- a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -122,7 +122,7 @@ class DecisionTreeLeaf 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 @@ class DecisionTreeLeaf } 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 59f19c1..1f2d1f5 100644 --- a/src/Phpml/Classification/Ensemble/RandomForest.php +++ b/src/Phpml/Classification/Ensemble/RandomForest.php @@ -148,7 +148,7 @@ class RandomForest extends Bagging } 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 439ea56..b0f7dfa 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -86,7 +86,7 @@ class DecisionStump extends WeightedClassifier 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 @@ class DecisionStump extends WeightedClassifier $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 @@ class DecisionStump extends WeightedClassifier $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 6838227..c78e788 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 @@ class Perceptron implements Classifier, IncrementalEstimator $this->labels = []; $this->optimizer = null; $this->featureCount = 0; - $this->weights = null; + $this->weights = []; $this->costValues = []; } @@ -174,7 +174,7 @@ class Perceptron implements Classifier, IncrementalEstimator * 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 a470fd4..38c857d 100644 --- a/src/Phpml/Classification/NaiveBayes.php +++ b/src/Phpml/Classification/NaiveBayes.php @@ -147,7 +147,7 @@ class NaiveBayes implements Classifier 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 d3be101..7e177ed 100644 --- a/src/Phpml/Clustering/FuzzyCMeans.php +++ b/src/Phpml/Clustering/FuzzyCMeans.php @@ -78,7 +78,7 @@ class FuzzyCMeans implements Clusterer } /** - * @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 65bb324..5dcc7a0 100644 --- a/src/Phpml/DimensionReduction/KernelPCA.php +++ b/src/Phpml/DimensionReduction/KernelPCA.php @@ -156,9 +156,9 @@ class KernelPCA extends PCA $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 fdb985f..f8fc69e 100644 --- a/src/Phpml/FeatureExtraction/StopWords.php +++ b/src/Phpml/FeatureExtraction/StopWords.php @@ -25,7 +25,7 @@ class StopWords 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 1c0ca93..e68b10d 100644 --- a/src/Phpml/Helper/OneVsRest.php +++ b/src/Phpml/Helper/OneVsRest.php @@ -164,7 +164,7 @@ trait OneVsRest */ 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 153ffcb..a034af0 100644 --- a/src/Phpml/Helper/Optimizer/ConjugateGradient.php +++ b/src/Phpml/Helper/Optimizer/ConjugateGradient.php @@ -171,7 +171,7 @@ class ConjugateGradient extends GD $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 2b25acd..7ef317c 100644 --- a/src/Phpml/Helper/Optimizer/Optimizer.php +++ b/src/Phpml/Helper/Optimizer/Optimizer.php @@ -47,7 +47,7 @@ abstract class Optimizer 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 dcb02e6..fab2923 100644 --- a/src/Phpml/Math/Set.php +++ b/src/Phpml/Math/Set.php @@ -10,7 +10,7 @@ use IteratorAggregate; class Set implements IteratorAggregate { /** - * @var string[]|int[]|float[] + * @var string[]|int[]|float[]|bool[] */ private $elements = []; @@ -135,7 +135,7 @@ class Set implements IteratorAggregate } /** - * @return string[]|int[]|float[] + * @return string[]|int[]|float[]|bool[] */ public function toArray(): array { @@ -160,9 +160,9 @@ class Set implements IteratorAggregate /** * 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 4144c30..fd09d95 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 @@ class Backpropagation } // 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 6ecfafc..d00ebf5 100644 --- a/src/Phpml/Regression/LeastSquares.php +++ b/src/Phpml/Regression/LeastSquares.php @@ -10,6 +10,7 @@ use Phpml\Math\Matrix; 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 85fc159..f60d308 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 @@ class LogisticRegressionTest extends TestCase $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 @@ class LogisticRegressionTest extends TestCase $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 08d1c44..e2260c4 100644 --- a/tests/Phpml/Metric/AccuracyTest.php +++ b/tests/Phpml/Metric/AccuracyTest.php @@ -45,7 +45,7 @@ class AccuracyTest extends TestCase $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 c244c27..885c1e1 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 bd5cb2f..c90077c 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 | 112 ++++++++++++++++---------- tests/Phpml/Clustering/DBSCANTest.php | 34 ++++++++ 2 files changed, 103 insertions(+), 43 deletions(-) diff --git a/src/Phpml/Clustering/DBSCAN.php b/src/Phpml/Clustering/DBSCAN.php index 3546ebf..e96c5ff 100644 --- a/src/Phpml/Clustering/DBSCAN.php +++ b/src/Phpml/Clustering/DBSCAN.php @@ -4,12 +4,13 @@ declare(strict_types=1); 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 @@ class DBSCAN implements Clusterer 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); - $regionSamples = $this->getSamplesInRegion($sample, $samples); - if (count($regionSamples) >= $this->minSamples) { - $clusters[] = $this->expandCluster($regionSamples, $visited); + if (count($neighborIndices) < $this->minSamples) { + $labels[$index] = self::NOISE; + + continue; } + + $labels[$index] = $n; + + $this->expandCluster($samples, $neighborIndices, $labels, $n); + + ++$n; + } + + return $this->groupByCluster($samples, $labels, $n); + } + + private function expandCluster(array $samples, array $seeds, array &$labels, int $n): void + { + 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($center, $sample) < $this->epsilon) { + $indices[] = $index; + } + } + + return $indices; + } + + private function groupByCluster(array $samples, array $labels, int $n): array + { + $clusters = array_fill(0, $n, []); + + foreach ($samples as $index => $sample) { + if ($labels[$index] !== self::NOISE) { + $clusters[$labels[$index]][$index] = $sample; + } + } + + // Reindex (i.e. to 0, 1, 2, ...) integer indices for backword compatibility + foreach ($clusters as $index => $cluster) { + $clusters[$index] = array_merge($cluster, []); } return $clusters; } - - private function getSamplesInRegion(array $localSample, array $samples): array - { - $region = []; - - foreach ($samples as $index => $sample) { - if ($this->distanceMetric->distance($localSample, $sample) < $this->epsilon) { - $region[$index] = $sample; - } - } - - return $region; - } - - private function expandCluster(array $samples, array &$visited): array - { - $cluster = []; - - $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; - } - } - - $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 c0d0401..3c6d08d 100644 --- a/tests/Phpml/Clustering/DBSCANTest.php +++ b/tests/Phpml/Clustering/DBSCANTest.php @@ -59,4 +59,38 @@ class DBSCANTest extends TestCase $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 72d0b4b..5acf093 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 5b91425..30adf4d 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 764bc4e..56ea7eb 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/BinaryStep.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/BinaryStep.php @@ -15,4 +15,17 @@ class BinaryStep implements ActivationFunction { 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 da428a4..8871b58 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php @@ -15,4 +15,13 @@ class Gaussian implements ActivationFunction { 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 6378606..7aa9614 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/HyperbolicTangent.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/HyperbolicTangent.php @@ -25,4 +25,13 @@ class HyperbolicTangent implements ActivationFunction { 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 fc7ff62..88212d1 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php @@ -25,4 +25,13 @@ class PReLU implements ActivationFunction { 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 4ae9603..edad3d6 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php @@ -25,4 +25,13 @@ class Sigmoid implements ActivationFunction { 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 2bb1cc7..f8f8247 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 @@ class ThresholdedReLU implements ActivationFunction { 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 1a997be..bfec929 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -9,6 +9,7 @@ use Phpml\Exception\InvalidArgumentException; 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 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, { $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 2dff600..47d606d 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 @@ class Neuron implements Node 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 fd09d95..6c9af98 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -64,7 +64,7 @@ class Backpropagation 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 ef40618..c46e297 100644 --- a/tests/Phpml/Classification/MLPClassifierTest.php +++ b/tests/Phpml/Classification/MLPClassifierTest.php @@ -7,6 +7,11 @@ namespace Phpml\Tests\Classification; 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 @@ class MLPClassifierTest extends TestCase $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 acc7977..4e85478 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php @@ -27,4 +27,23 @@ class BinaryStepTest extends TestCase [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 2b08793..aace8bc 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php @@ -29,4 +29,27 @@ class GaussianTest extends TestCase [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 91e7eba..629200e 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php @@ -30,4 +30,28 @@ class HyperboliTangentTest extends TestCase [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 f407060..c9f565d 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php @@ -29,4 +29,27 @@ class PReLUTest extends TestCase [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 30b50f8..1028fb3 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php @@ -30,4 +30,28 @@ class SigmoidTest extends TestCase [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 f46ff02..4db0418 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php @@ -28,4 +28,26 @@ class ThresholdedReLUTest extends TestCase [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 a034af0..67210ab 100644 --- a/src/Phpml/Helper/Optimizer/ConjugateGradient.php +++ b/src/Phpml/Helper/Optimizer/ConjugateGradient.php @@ -31,7 +31,7 @@ class ConjugateGradient extends GD 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 @@ class ConjugateGradient extends GD */ 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 @@ class ConjugateGradient extends GD * 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 @@ class ConjugateGradient extends GD $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; + } } - return ($x1[0] - $this->theta[0]) / $d; + if ($d[$imax] == 0) { + return $x1[$imax] - $this->theta[$imax]; + } + + return ($x1[$imax] - $this->theta[$imax]) / $d[$imax]; } /** @@ -139,22 +163,7 @@ class ConjugateGradient extends GD */ 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 @@ class ConjugateGradient extends GD */ 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 0000000..b05f998 --- /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 0000000..c68e318 --- /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 0000000..6f6e469 --- /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 e45675d..86ff2a9 100644 --- a/tests/Phpml/PipelineTest.php +++ b/tests/Phpml/PipelineTest.php @@ -7,6 +7,7 @@ namespace Phpml\Tests; 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 @@ class PipelineTest extends TestCase $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 0000000..01d8079 --- /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 @@ class SupportVectorMachine /** * @return array|string + * + * @throws LibsvmCommandException */ public function predict(array $samples) { @@ -178,7 +185,11 @@ class SupportVectorMachine $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 01d8079..a9d11e3 100644 --- a/src/Phpml/Exception/LibsvmCommandException.php +++ b/src/Phpml/Exception/LibsvmCommandException.php @@ -8,8 +8,8 @@ use Exception; 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 f66d5d7..ce7a7ba 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -153,16 +153,17 @@ class SupportVectorMachine $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 @@ class SupportVectorMachine $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 180b8d3..466c962 100644 --- a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); 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 @@ SV $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 | 141 ++++++++++++++---- .../Phpml/Metric/ClassificationReportTest.php | 86 ++++++++++- 3 files changed, 201 insertions(+), 33 deletions(-) diff --git a/docs/machine-learning/metric/classification-report.md b/docs/machine-learning/metric/classification-report.md index 53490b2..a0a6acc 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 0c3198f..755d78b 100644 --- a/src/Phpml/Metric/ClassificationReport.php +++ b/src/Phpml/Metric/ClassificationReport.php @@ -4,8 +4,36 @@ declare(strict_types=1); 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 $truePositive = []; + + /** + * @var array + */ + private $falsePositive = []; + + /** + * @var array + */ + private $falseNegative = []; + + /** + * @var array + */ + private $support = []; + /** * @var array */ @@ -21,34 +49,21 @@ class ClassificationReport */ private $f1score = []; - /** - * @var array - */ - private $support = []; - /** * @var array */ private $average = []; - public function __construct(array $actualLabels, array $predictedLabels) + public function __construct(array $actualLabels, array $predictedLabels, int $average = self::MACRO_AVERAGE) { - $truePositive = $falsePositive = $falseNegative = $this->support = self::getLabelIndexedArray($actualLabels, $predictedLabels); - - foreach ($actualLabels as $index => $actual) { - $predicted = $predictedLabels[$index]; - ++$this->support[$actual]; - - if ($actual === $predicted) { - ++$truePositive[$actual]; - } else { - ++$falsePositive[$predicted]; - ++$falseNegative[$actual]; - } + $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 @@ class ClassificationReport 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 @@ class ClassificationReport } } + 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 483f769..4c4f01f 100644 --- a/tests/Phpml/Metric/ClassificationReportTest.php +++ b/tests/Phpml/Metric/ClassificationReportTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Phpml\Tests\Metric; +use Phpml\Exception\InvalidArgumentException; use Phpml\Metric\ClassificationReport; use PHPUnit\Framework\TestCase; @@ -36,10 +37,12 @@ class ClassificationReportTest extends TestCase '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 @@ class ClassificationReportTest extends TestCase 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 @@ class ClassificationReportTest extends TestCase $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 @@ class ClassificationReportTest extends TestCase '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 455f8bb..fd86a5c 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 a0a6acc..53f125b 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 149ce27..0e97bba 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 9318786..bab8a71 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 fd86a5c..75520cc 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 75520cc..4a74eb6 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 f415daf..7a0f3af 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 0e97bba..acb34dd 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 38c857d..8f09257 100644 --- a/src/Phpml/Classification/NaiveBayes.php +++ b/src/Phpml/Classification/NaiveBayes.php @@ -66,8 +66,7 @@ class NaiveBayes implements Classifier $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 8312e9c..7db8645 100644 --- a/tests/Phpml/Classification/NaiveBayesTest.php +++ b/tests/Phpml/Classification/NaiveBayesTest.php @@ -68,4 +68,63 @@ class NaiveBayesTest extends TestCase $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 7a0f3af..fd84163 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 acb34dd..865d4b5 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 5acf093..7365a71 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 bfec929..a6d3be0 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -142,10 +142,17 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, $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 21f5e9c..2a7e696 100644 --- a/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php +++ b/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); 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 @@ class LayeredNetworkTest extends TestCase $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 @@ class LayeredNetworkTest extends TestCase { 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 885c1e1..006733f 100644 --- a/tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php +++ b/tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php @@ -4,8 +4,12 @@ declare(strict_types=1); 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 @@ class MultilayerPerceptronTest extends TestCase $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 865d4b5..3571788 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 bab8a71..1545cc2 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 e6685af..6f597be 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 62da509..da0511c 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 0000000..0eba973 --- /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 ce7a7ba..ddd843f 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Phpml\SupportVectorMachine; use Phpml\Exception\InvalidArgumentException; +use Phpml\Exception\InvalidOperationException; use Phpml\Exception\LibsvmCommandException; use Phpml\Helper\Trainable; @@ -178,13 +179,61 @@ class SupportVectorMachine * @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 @@ class SupportVectorMachine 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 @@ class SupportVectorMachine ); } + 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 1db1fdf..79dcb49 100644 --- a/tests/Phpml/SupportVectorMachine/DataTransformerTest.php +++ b/tests/Phpml/SupportVectorMachine/DataTransformerTest.php @@ -37,4 +37,45 @@ class DataTransformerTest extends TestCase $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 466c962..899fa40 100644 --- a/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/Phpml/SupportVectorMachine/SupportVectorMachineTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); 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 @@ SV $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 @@ SV $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 @@ SV $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 76d8624..b6079cc 100644 --- a/src/Phpml/Association/Apriori.php +++ b/src/Phpml/Association/Apriori.php @@ -86,12 +86,11 @@ class Apriori implements Associator 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 8b95237..3ba6901 100644 --- a/tests/Phpml/Association/AprioriTest.php +++ b/tests/Phpml/Association/AprioriTest.php @@ -64,7 +64,6 @@ class AprioriTest extends TestCase $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 @@ class AprioriTest extends TestCase $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 3571788..b39a39e 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 abf30af..028fe9e 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 8f7d56d..a8a4379 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 0bc96d8..d70dffb 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 c65b08c..e7666aa 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 ddd843f..3ec3ed8 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/SupportVectorMachine/SupportVectorMachine.php @@ -120,7 +120,7 @@ class SupportVectorMachine $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 874d0d2..595714d 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 f817b0a..eb50563 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 0000000..9c942e7 --- /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 8c9c10c..f794320 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 0000000..6a3d639 --- /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 8a0d241..426e4fd 100644 --- a/src/Math/Statistic/StandardDeviation.php +++ b/src/Math/Statistic/StandardDeviation.php @@ -9,27 +9,24 @@ use Phpml\Exception\InvalidArgumentException; 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 @@ class StandardDeviation 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 0000000..641cf00 --- /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 8333740..51c2770 100644 --- a/tests/Math/Statistic/StandardDeviationTest.php +++ b/tests/Math/Statistic/StandardDeviationTest.php @@ -37,4 +37,29 @@ class StandardDeviationTest extends TestCase $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 0000000..19b2cd8 --- /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 | 309 ++++++++++++++++-------------- 2 files changed, 170 insertions(+), 141 deletions(-) diff --git a/src/Association/Apriori.php b/src/Association/Apriori.php index b6079cc..1d7f99f 100644 --- a/src/Association/Apriori.php +++ b/src/Association/Apriori.php @@ -138,7 +138,7 @@ class Apriori implements Associator $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 3ba6901..5ed4f8c 100644 --- a/tests/Association/AprioriTest.php +++ b/tests/Association/AprioriTest.php @@ -46,15 +46,18 @@ class AprioriTest extends TestCase $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,143 +68,12 @@ class AprioriTest extends TestCase $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]])); - } - - public function testGetRules(): void - { - $apriori = new Apriori(0.4, 0.8); - $apriori->train($this->sampleChars, []); - - $this->assertCount(19, $apriori->getRules()); - } - - public function testAntecedents(): void - { - $apriori = new Apriori(); - - $this->assertCount(6, $this->invoke($apriori, 'antecedents', [['a', 'b', 'c']])); - } - - public function testItems(): void - { - $apriori = new Apriori(); - $apriori->train($this->sampleGreek, []); - $this->assertCount(4, $this->invoke($apriori, 'items', [])); - } - - 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']]])); - } - - 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']]])); - } - - 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']])); - } - - 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']])); - } - - 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']])); - } - - 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']])); - } - - 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']])); - } - - 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); - } - - public function testSaveAndRestore(): void - { - $classifier = new Apriori(0.5, 0.5); - $classifier->train($this->sampleGreek, []); - - $testSamples = [['alpha', 'epsilon'], ['beta', 'theta']]; - $predicted = $classifier->predict($testSamples); - - $filename = 'apriori-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)); + $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 @@ -228,4 +100,161 @@ class AprioriTest extends TestCase $this->assertEquals([1], array_keys($L)); $this->assertEquals([['a']], $L[1]); } + + public function testGetRules(): void + { + $apriori = new Apriori(0.4, 0.8); + $apriori->train($this->sampleChars, []); + + $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, 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', [])); + } + + 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']]])); + } + + public function testCandidates(): void + { + $apriori = new Apriori(); + $apriori->train($this->sampleGreek, []); + + $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 + { + $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']])); + } + + 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']])); + } + + 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']])); + } + + 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']])); + } + + 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']])); + } + + 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']])); + } + + public function testSaveAndRestore(): void + { + $classifier = new Apriori(0.5, 0.5); + $classifier->train($this->sampleGreek, []); + + $testSamples = [['alpha', 'epsilon'], ['beta', 'theta']]; + $predicted = $classifier->predict($testSamples); + + $filename = 'apriori-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)); + } + + /** + * 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 = []) + { + $reflection = new ReflectionClass(get_class($object)); + $method = $reflection->getMethod($method); + $method->setAccessible(true); + + 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 4a74eb6..e0a91da 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 a669a7f..ac24a53 100644 --- a/src/Math/Statistic/Covariance.php +++ b/src/Math/Statistic/Covariance.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Phpml\Math\Statistic; -use Exception; use Phpml\Exception\InvalidArgumentException; class Covariance @@ -64,7 +63,7 @@ class Covariance } 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 2b64854..43b775a 100644 --- a/tests/Math/Statistic/CovarianceTest.php +++ b/tests/Math/Statistic/CovarianceTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); 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 @@ class CovarianceTest extends TestCase $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 4b678a4..30f0203 100644 --- a/src/FeatureExtraction/TfIdfTransformer.php +++ b/src/FeatureExtraction/TfIdfTransformer.php @@ -20,7 +20,7 @@ class TfIdfTransformer implements Transformer } } - 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 e0d4e10..8c757c0 100644 --- a/src/FeatureExtraction/TokenCountVectorizer.php +++ b/src/FeatureExtraction/TokenCountVectorizer.php @@ -41,7 +41,7 @@ class TokenCountVectorizer implements Transformer $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 0000000..4c925f6 --- /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 0000000..8f4b273 --- /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 6a3d639..5ca2332 100644 --- a/src/FeatureSelection/VarianceThreshold.php +++ b/src/FeatureSelection/VarianceThreshold.php @@ -33,11 +33,9 @@ final class VarianceThreshold implements Transformer } $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 0000000..b0d0d37 --- /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 593756c..fdce666 100644 --- a/src/Preprocessing/Imputer.php +++ b/src/Preprocessing/Imputer.php @@ -43,7 +43,7 @@ class Imputer implements Preprocessor $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 b2721de..39b4fbc 100644 --- a/src/Preprocessing/Normalizer.php +++ b/src/Preprocessing/Normalizer.php @@ -48,7 +48,7 @@ class Normalizer implements Preprocessor $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 c36e5ca..7350e2c 100644 --- a/src/Transformer.php +++ b/src/Transformer.php @@ -7,12 +7,9 @@ namespace Phpml; 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 c46e297..c4c45c4 100644 --- a/tests/Classification/MLPClassifierTest.php +++ b/tests/Classification/MLPClassifierTest.php @@ -59,7 +59,7 @@ class MLPClassifierTest extends TestCase 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 @@ class MLPClassifierTest extends TestCase 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 @@ class MLPClassifierTest extends TestCase 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 @@ class MLPClassifierTest extends TestCase */ 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 @@ class MLPClassifierTest extends TestCase // 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 0000000..7a601db --- /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 0000000..a035560 --- /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 0000000..81717f8 --- /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 0000000..9a819c5 --- /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 7c1ff3e..e7bc92e 100644 --- a/src/Math/Matrix.php +++ b/src/Math/Matrix.php @@ -236,6 +236,29 @@ class Matrix 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 @@ class Matrix /** * 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 @@ class Matrix /** * 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 0000000..0047e5f --- /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 a035560..df17c08 100644 --- a/tests/FeatureSelection/SelectKBestTest.php +++ b/tests/FeatureSelection/SelectKBestTest.php @@ -8,6 +8,7 @@ use Phpml\Dataset\Demo\IrisDataset; 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 @@ final class SelectKBestTest extends TestCase 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 da535bf..50cabac 100644 --- a/tests/Math/MatrixTest.php +++ b/tests/Math/MatrixTest.php @@ -251,4 +251,55 @@ class MatrixTest extends TestCase $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 8f4b273..bd84e73 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 df17c08..ebf119b 100644 --- a/tests/FeatureSelection/SelectKBestTest.php +++ b/tests/FeatureSelection/SelectKBestTest.php @@ -18,7 +18,7 @@ final class SelectKBestTest extends TestCase { $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 @@ final class SelectKBestTest extends TestCase { $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 @@ final class SelectKBestTest extends TestCase 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 @@ final class SelectKBestTest extends TestCase $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 @@ final class SelectKBestTest extends TestCase 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 480a980..d57da87 100644 --- a/src/Pipeline.php +++ b/src/Pipeline.php @@ -54,7 +54,7 @@ class Pipeline implements 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 86ff2a9..6f1562c 100644 --- a/tests/PipelineTest.php +++ b/tests/PipelineTest.php @@ -7,6 +7,8 @@ namespace Phpml\Tests; 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 @@ class PipelineTest extends TestCase $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 fd84163..d4df191 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 595714d..c5d788e 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 eb50563..14cfba5 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 0000000..2d8024c --- /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 f794320..92f3837 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 9a819c5..968c474 100644 --- a/src/FeatureSelection/ScoringFunction/UnivariateLinearRegression.php +++ b/src/FeatureSelection/ScoringFunction/UnivariateLinearRegression.php @@ -16,7 +16,7 @@ use Phpml\Math\Statistic\Mean; * * 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 6f1562c..d72e0c8 100644 --- a/tests/PipelineTest.php +++ b/tests/PipelineTest.php @@ -7,7 +7,6 @@ namespace Phpml\Tests; 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 @@ class PipelineTest extends TestCase $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 e02d14d..8aebbe0 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -23,7 +23,7 @@ class InvalidArgumentException extends Exception 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 b0d0d37..65548a1 100644 --- a/src/Math/Statistic/ANOVA.php +++ b/src/Math/Statistic/ANOVA.php @@ -25,7 +25,7 @@ final class ANOVA { $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 ac24a53..3dfce4b 100644 --- a/src/Math/Statistic/Covariance.php +++ b/src/Math/Statistic/Covariance.php @@ -21,7 +21,7 @@ class Covariance $n = count($x); if ($sample && $n === 1) { - throw InvalidArgumentException::arraySizeToSmall(2); + throw InvalidArgumentException::arraySizeTooSmall(2); } if ($meanX === null) { @@ -59,7 +59,7 @@ class Covariance $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 426e4fd..5bf4940 100644 --- a/src/Math/Statistic/StandardDeviation.php +++ b/src/Math/Statistic/StandardDeviation.php @@ -20,7 +20,7 @@ class StandardDeviation $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 75f60ff..65e8d8b 100644 --- a/tests/CrossValidation/RandomSplitTest.php +++ b/tests/CrossValidation/RandomSplitTest.php @@ -11,7 +11,7 @@ use PHPUnit\Framework\TestCase; 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 81717f8..2203bf1 100644 --- a/tests/Math/Statistic/ANOVATest.php +++ b/tests/Math/Statistic/ANOVATest.php @@ -32,7 +32,7 @@ final class ANOVATest extends TestCase 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 43b775a..fd7187a 100644 --- a/tests/Math/Statistic/CovarianceTest.php +++ b/tests/Math/Statistic/CovarianceTest.php @@ -73,7 +73,7 @@ class CovarianceTest extends TestCase 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 @@ class CovarianceTest extends TestCase 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 51c2770..c6fd47e 100644 --- a/tests/Math/Statistic/StandardDeviationTest.php +++ b/tests/Math/Statistic/StandardDeviationTest.php @@ -32,7 +32,7 @@ class StandardDeviationTest extends TestCase 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 d4df191..bc647cb 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 b39a39e..c8652d9 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 1545cc2..0462e94 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 0366c23..7eaee1c 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 1d7f99f..c3f9c91 100644 --- a/src/Association/Apriori.php +++ b/src/Association/Apriori.php @@ -63,11 +63,11 @@ class Apriori implements Associator */ 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 1d5a754..690f79c 100644 --- a/src/Classification/DecisionTree.php +++ b/src/Classification/DecisionTree.php @@ -273,15 +273,15 @@ class DecisionTree implements Classifier } 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 @@ class DecisionTree implements Classifier $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 @@ class DecisionTree implements Classifier 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 @@ class DecisionTree implements Classifier } shuffle($allFeatures); - $selectedFeatures = array_slice($allFeatures, 0, $numFeatures, false); + $selectedFeatures = array_slice($allFeatures, 0, $numFeatures); sort($selectedFeatures); return $selectedFeatures; @@ -406,7 +406,7 @@ class DecisionTree implements Classifier // 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 @@ class DecisionTree implements Classifier */ protected function getSplitNodesByColumn(int $column, DecisionTreeLeaf $node): array { - if (!$node || $node->isTerminal) { + if ($node->isTerminal) { return []; } @@ -444,11 +444,11 @@ class DecisionTree implements Classifier $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 @@ class DecisionTree implements Classifier } } 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 cc53eea..5a106ab 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 @@ class DecisionTreeLeaf $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 @@ class DecisionTreeLeaf $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 67f7198..b314c81 100644 --- a/src/Classification/Ensemble/AdaBoost.php +++ b/src/Classification/Ensemble/AdaBoost.php @@ -158,7 +158,7 @@ class AdaBoost implements Classifier 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 a3d8e5e..a1d51c2 100644 --- a/src/Classification/Ensemble/Bagging.php +++ b/src/Classification/Ensemble/Bagging.php @@ -145,7 +145,7 @@ class Bagging implements Classifier $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 de2e152..3b6309d 100644 --- a/src/Classification/Linear/Adaline.php +++ b/src/Classification/Linear/Adaline.php @@ -42,7 +42,7 @@ class Adaline extends Perceptron 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 b0f7dfa..1903712 100644 --- a/src/Classification/Linear/DecisionStump.php +++ b/src/Classification/Linear/DecisionStump.php @@ -118,7 +118,7 @@ class DecisionStump extends WeightedClassifier // 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 b595587..13f4b8a 100644 --- a/src/Classification/Linear/LogisticRegression.php +++ b/src/Classification/Linear/LogisticRegression.php @@ -71,13 +71,13 @@ class LogisticRegression extends Adaline 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 c78e788..5fffc01 100644 --- a/src/Classification/Linear/Perceptron.php +++ b/src/Classification/Linear/Perceptron.php @@ -97,7 +97,7 @@ class Perceptron implements Classifier, IncrementalEstimator 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 @@ class Perceptron implements Classifier, IncrementalEstimator */ 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 b225a64..432582b 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 7e177ed..a5408c0 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 @@ class FuzzyCMeans implements Clusterer 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 fea1ff8..2011b87 100644 --- a/src/Clustering/KMeans/Cluster.php +++ b/src/Clustering/KMeans/Cluster.php @@ -76,8 +76,7 @@ class Cluster extends Point implements IteratorAggregate, Countable public function updateCentroid(): void { - $count = count($this->points); - if (!$count) { + if (empty($this->points)) { return; } @@ -89,6 +88,7 @@ class Cluster extends Point implements IteratorAggregate, Countable } } + $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 371bbc3..3c9f134 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 @@ class Space extends SplObjectStorage */ public function getBoundaries() { - if (!count($this)) { + if (empty($this)) { return false; } @@ -153,8 +153,8 @@ class Space extends SplObjectStorage $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 5dcc7a0..b962b3d 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 6400d14..d188205 100644 --- a/src/DimensionReduction/LDA.php +++ b/src/DimensionReduction/LDA.php @@ -130,7 +130,7 @@ class LDA extends EigenTransformerBase $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 @@ class LDA extends EigenTransformerBase $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 30f0203..4a478c3 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 8c757c0..c73836c 100644 --- a/src/FeatureExtraction/TokenCountVectorizer.php +++ b/src/FeatureExtraction/TokenCountVectorizer.php @@ -123,7 +123,7 @@ class TokenCountVectorizer implements Transformer 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 1730309..8aac90b 100644 --- a/src/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -274,6 +274,7 @@ class EigenvalueDecomposition } // 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 e7bc92e..908ec4d 100644 --- a/src/Math/Matrix.php +++ b/src/Math/Matrix.php @@ -105,7 +105,7 @@ class Matrix */ 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 8791a65..8e761df 100644 --- a/src/Math/Statistic/Mean.php +++ b/src/Math/Statistic/Mean.php @@ -50,7 +50,7 @@ class Mean $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 755d78b..4409474 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 e86a8ed..a1f49ce 100644 --- a/src/Metric/ConfusionMatrix.php +++ b/src/Metric/ConfusionMatrix.php @@ -6,9 +6,9 @@ namespace Phpml\Metric; 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 7424348..a604a53 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 39b4fbc..9654e48 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 a412323..0a99aa3 100644 --- a/src/SupportVectorMachine/DataTransformer.php +++ b/src/SupportVectorMachine/DataTransformer.php @@ -42,7 +42,7 @@ class DataTransformer $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 @@ class DataTransformer $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 3ec3ed8..561f65b 100644 --- a/src/SupportVectorMachine/SupportVectorMachine.php +++ b/src/SupportVectorMachine/SupportVectorMachine.php @@ -149,7 +149,7 @@ class SupportVectorMachine $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 @@ class SupportVectorMachine { $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 @@ class SupportVectorMachine $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 1c3af15..7245481 100644 --- a/tests/Clustering/FuzzyCMeansTest.php +++ b/tests/Clustering/FuzzyCMeansTest.php @@ -16,7 +16,7 @@ class FuzzyCMeansTest extends TestCase $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 dedf981..032c804 100644 --- a/tests/Clustering/KMeansTest.php +++ b/tests/Clustering/KMeansTest.php @@ -20,7 +20,7 @@ class KMeansTest extends TestCase $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 47c4798..a08ddc1 100644 --- a/tests/Math/LinearAlgebra/EigenDecompositionTest.php +++ b/tests/Math/LinearAlgebra/EigenvalueDecompositionTest.php @@ -8,7 +8,7 @@ use Phpml\Math\LinearAlgebra\EigenvalueDecomposition; 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 a87d3db..5491e03 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 2011b87..a4462fe 100644 --- a/src/Clustering/KMeans/Cluster.php +++ b/src/Clustering/KMeans/Cluster.php @@ -76,7 +76,8 @@ class Cluster extends Point implements IteratorAggregate, Countable public function updateCentroid(): void { - if (empty($this->points)) { + $count = count($this->points); + if ($count === 0) { return; } @@ -88,7 +89,6 @@ class Cluster extends Point implements IteratorAggregate, Countable } } - $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 3c9f134..b85b329 100644 --- a/src/Clustering/KMeans/Space.php +++ b/src/Clustering/KMeans/Space.php @@ -75,7 +75,7 @@ class Space extends SplObjectStorage */ 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 8aac90b..19f3c43 100644 --- a/src/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -99,7 +99,7 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition } $f = 0.0; - if ($h === 0 || $h < 1e-32) { + + if ($h == 0.0) { $h = 1e-32; } @@ -274,7 +275,6 @@ class EigenvalueDecomposition } // 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 @@ class EigenvalueDecomposition } $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 a08ddc1..73018d0 100644 --- a/tests/Math/LinearAlgebra/EigenvalueDecompositionTest.php +++ b/tests/Math/LinearAlgebra/EigenvalueDecompositionTest.php @@ -10,34 +10,72 @@ use PHPUnit\Framework\TestCase; 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 @@ class EigenvalueDecompositionTest extends TestCase $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 5491e03..ac42d01 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 c3f9c91..abbdf47 100644 --- a/src/Association/Apriori.php +++ b/src/Association/Apriori.php @@ -212,9 +212,9 @@ class Apriori implements Associator */ 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 @@ class Apriori implements Associator 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 5ed4f8c..81a6ce6 100644 --- a/tests/Association/AprioriTest.php +++ b/tests/Association/AprioriTest.php @@ -101,6 +101,18 @@ class AprioriTest extends TestCase $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 ac42d01..61ecd63 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 7ef317c..9bac3be 100644 --- a/src/Helper/Optimizer/Optimizer.php +++ b/src/Helper/Optimizer/Optimizer.php @@ -5,10 +5,12 @@ declare(strict_types=1); 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 @@ abstract class Optimizer // 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 18a5f0c..e1cbeea 100644 --- a/src/Helper/Optimizer/StochasticGD.php +++ b/src/Helper/Optimizer/StochasticGD.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Phpml\Helper\Optimizer; use Closure; +use Phpml\Exception\InvalidArgumentException; /** * Stochastic Gradient Descent optimization method @@ -88,6 +89,17 @@ class StochasticGD extends Optimizer $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 151e2cc..1594837 100644 --- a/src/Math/LinearAlgebra/LUDecomposition.php +++ b/src/Math/LinearAlgebra/LUDecomposition.php @@ -225,25 +225,14 @@ class LUDecomposition 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 7245481..b2005a1 100644 --- a/tests/Clustering/FuzzyCMeansTest.php +++ b/tests/Clustering/FuzzyCMeansTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Phpml\Tests\Clustering; use Phpml\Clustering\FuzzyCMeans; +use Phpml\Exception\InvalidArgumentException; use PHPUnit\Framework\TestCase; class FuzzyCMeansTest extends TestCase @@ -45,4 +46,19 @@ class FuzzyCMeansTest extends TestCase $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 b05f998..78eb718 100644 --- a/tests/Helper/Optimizer/ConjugateGradientTest.php +++ b/tests/Helper/Optimizer/ConjugateGradientTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Phpml\Tests\Helper\Optimizer; +use Phpml\Exception\InvalidArgumentException; use Phpml\Helper\Optimizer\ConjugateGradient; use PHPUnit\Framework\TestCase; @@ -35,6 +36,34 @@ class ConjugateGradientTest extends TestCase $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 @@ class ConjugateGradientTest extends TestCase $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 0000000..b678673 --- /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 c5d788e..ddca60a 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 14cfba5..2e204e8 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 0000000..8ac1c26 --- /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 92f3837..490e5dc 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 0000000..8bd172b --- /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 8d6d5da..1cb0bfc 100644 --- a/src/Exception/DatasetException.php +++ b/src/Exception/DatasetException.php @@ -12,4 +12,19 @@ class DatasetException extends Exception { 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 0000000..fdd6c1f --- /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 0000000..d817c96 --- /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 0000000..5f6d015 --- /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 0000000..7cf6fc4 --- /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 0000000..e69de29 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 0000000..289e2b5 --- /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 0000000..56c20f8 --- /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 0000000..f57b6c5 --- /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 0000000..77ff868 --- /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 0000000..b358890 --- /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 0000000..789be38 --- /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 0000000..25cb296 --- /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 0000000..8cc16f7 --- /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 0000000..23d7485 --- /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 0000000..bf8757f --- /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 0000000..884da3a --- /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 0a99aa3..e1aeb7d 100644 --- a/src/SupportVectorMachine/DataTransformer.php +++ b/src/SupportVectorMachine/DataTransformer.php @@ -4,6 +4,8 @@ declare(strict_types=1); 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 @@ class DataTransformer 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 79dcb49..75c23d6 100644 --- a/tests/SupportVectorMachine/DataTransformerTest.php +++ b/tests/SupportVectorMachine/DataTransformerTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Phpml\Tests\SupportVectorMachine; +use Phpml\Exception\InvalidArgumentException; use Phpml\SupportVectorMachine\DataTransformer; use PHPUnit\Framework\TestCase; @@ -78,4 +79,12 @@ class DataTransformerTest extends TestCase $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 c4c45c4..d3680b6 100644 --- a/tests/Classification/MLPClassifierTest.php +++ b/tests/Classification/MLPClassifierTest.php @@ -193,6 +193,35 @@ class MLPClassifierTest extends TestCase $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 6f597be..bbf829b 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 f60d308..ed9b878 100644 --- a/tests/Classification/Linear/LogisticRegressionTest.php +++ b/tests/Classification/Linear/LogisticRegressionTest.php @@ -8,9 +8,49 @@ use Phpml\Classification\Linear\LogisticRegression; 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 @@ class LogisticRegressionTest extends TestCase $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 432582b..13963f3 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 a5408c0..5e6fa0c 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 78a2e4a..86ad754 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 96c9019..bffb59a 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 7d30b0b..618814a 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 f88fe31..631c6a6 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 ca04a3e..a159753 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 8bd172b..c1e261b 100644 --- a/src/Dataset/SvmDataset.php +++ b/src/Dataset/SvmDataset.php @@ -41,12 +41,12 @@ class SvmDataset extends ArrayDataset 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 @@ class SvmDataset extends ArrayDataset 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 @@ class SvmDataset extends ArrayDataset { $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 @@ class SvmDataset extends ArrayDataset 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 @@ class SvmDataset extends ArrayDataset 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 1cb0bfc..d6f2219 100644 --- a/src/Exception/DatasetException.php +++ b/src/Exception/DatasetException.php @@ -8,23 +8,4 @@ use Exception; 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 719c2c2..e00acff 100644 --- a/src/Exception/FileException.php +++ b/src/Exception/FileException.php @@ -8,18 +8,4 @@ use Exception; 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 8aebbe0..a25599b 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -8,96 +8,4 @@ use Exception; 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 a9d11e3..d4d353e 100644 --- a/src/Exception/LibsvmCommandException.php +++ b/src/Exception/LibsvmCommandException.php @@ -8,8 +8,4 @@ use Exception; 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 b309bff..eb6e9d3 100644 --- a/src/Exception/MatrixException.php +++ b/src/Exception/MatrixException.php @@ -8,18 +8,4 @@ use Exception; 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 282fa1b..197876e 100644 --- a/src/Exception/NormalizerException.php +++ b/src/Exception/NormalizerException.php @@ -8,8 +8,4 @@ use Exception; 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 6d1abaa..27cd744 100644 --- a/src/Exception/SerializeException.php +++ b/src/Exception/SerializeException.php @@ -8,13 +8,4 @@ use Exception; 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 f8fc69e..f5622fd 100644 --- a/src/FeatureExtraction/StopWords.php +++ b/src/FeatureExtraction/StopWords.php @@ -28,7 +28,7 @@ class StopWords $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 bd84e73..b0ff644 100644 --- a/src/FeatureSelection/SelectKBest.php +++ b/src/FeatureSelection/SelectKBest.php @@ -44,7 +44,7 @@ final class SelectKBest implements Transformer 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 de7414d..d9ad00c 100644 --- a/src/Math/Comparison.php +++ b/src/Math/Comparison.php @@ -33,7 +33,7 @@ class Comparison 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 52e969c..0ccd29a 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 4ecc576..4f437dc 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 457333c..459a5ec 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 5ff7364..36edf9b 100644 --- a/src/Math/Distance/Minkowski.php +++ b/src/Math/Distance/Minkowski.php @@ -25,7 +25,7 @@ class Minkowski 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/LinearAlgebra/LUDecomposition.php b/src/Math/LinearAlgebra/LUDecomposition.php index 1594837..44e8a26 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 @@ class LUDecomposition 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 908ec4d..f69d8e6 100644 --- a/src/Math/Matrix.php +++ b/src/Math/Matrix.php @@ -48,7 +48,7 @@ class Matrix 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 @@ class Matrix 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 @@ class Matrix } if (!$this->isSquare()) { - throw MatrixException::notSquareMatrix(); + throw new MatrixException('Matrix is not square matrix'); } $lu = new LUDecomposition($this); @@ -139,7 +139,7 @@ class Matrix 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 @@ class Matrix 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 65548a1..2732ba7 100644 --- a/src/Math/Statistic/ANOVA.php +++ b/src/Math/Statistic/ANOVA.php @@ -25,7 +25,7 @@ final class ANOVA { $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 8803cbf..ce52f3b 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 3dfce4b..4ed0776 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 @@ class Covariance 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 8e761df..6b6d555 100644 --- a/src/Math/Statistic/Mean.php +++ b/src/Math/Statistic/Mean.php @@ -59,7 +59,7 @@ class Mean 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 5bf4940..f1eae8a 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 @@ class StandardDeviation 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 3fd545c..92f6860 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 ebcdbe4..36e8e2c 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 a604a53..1c681f8 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 a6d3be0..3626063 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 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, { 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 9654e48..95927ea 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 e1aeb7d..06272e2 100644 --- a/src/SupportVectorMachine/DataTransformer.php +++ b/src/SupportVectorMachine/DataTransformer.php @@ -27,7 +27,7 @@ class DataTransformer 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 561f65b..be16ff4 100644 --- a/src/SupportVectorMachine/SupportVectorMachine.php +++ b/src/SupportVectorMachine/SupportVectorMachine.php @@ -137,7 +137,7 @@ class SupportVectorMachine 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 @@ class SupportVectorMachine 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 @@ class SupportVectorMachine 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 @@ class SupportVectorMachine 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 de49556..8bbcc37 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 618814a..e0e7822 100644 --- a/src/Dataset/ArrayDataset.php +++ b/src/Dataset/ArrayDataset.php @@ -40,4 +40,23 @@ class ArrayDataset implements Dataset { 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 bca9e43..0431959 100644 --- a/tests/Dataset/ArrayDatasetTest.php +++ b/tests/Dataset/ArrayDatasetTest.php @@ -26,4 +26,15 @@ class ArrayDatasetTest extends TestCase $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 5a106ab..649c743 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 1f2d1f5..d871fe2 100644 --- a/src/Classification/Ensemble/RandomForest.php +++ b/src/Classification/Ensemble/RandomForest.php @@ -4,9 +4,9 @@ declare(strict_types=1); 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 @@ class RandomForest extends Bagging * 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 @@ class RandomForest extends Bagging * 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 @@ class RandomForest extends Bagging { 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 ea0cce1..93353a3 100644 --- a/tests/Classification/Ensemble/RandomForestTest.php +++ b/tests/Classification/Ensemble/RandomForestTest.php @@ -7,19 +7,44 @@ namespace Phpml\Tests\Classification\Ensemble; 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 b678673..ea96f77 100644 --- a/tests/Math/LinearAlgebra/LUDecompositionTest.php +++ b/tests/Math/LinearAlgebra/LUDecompositionTest.php @@ -28,4 +28,22 @@ final class LUDecompositionTest extends TestCase $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 a4462fe..8936926 100644 --- a/src/Clustering/KMeans/Cluster.php +++ b/src/Clustering/KMeans/Cluster.php @@ -49,7 +49,7 @@ class Cluster extends Point implements IteratorAggregate, Countable 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 0000000..80b9837 --- /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 9bac3be..dba0cd0 100644 --- a/src/Helper/Optimizer/Optimizer.php +++ b/src/Helper/Optimizer/Optimizer.php @@ -9,8 +9,6 @@ use Phpml\Exception\InvalidArgumentException; abstract class Optimizer { - public $initialTheta; - /** * Unknown variables to be found * @@ -37,11 +35,9 @@ abstract class Optimizer 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 e1cbeea..c4fabd3 100644 --- a/src/Helper/Optimizer/StochasticGD.php +++ b/src/Helper/Optimizer/StochasticGD.php @@ -89,7 +89,7 @@ class StochasticGD extends Optimizer $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 78eb718..09c250c 100644 --- a/tests/Helper/Optimizer/ConjugateGradientTest.php +++ b/tests/Helper/Optimizer/ConjugateGradientTest.php @@ -57,7 +57,7 @@ class ConjugateGradientTest extends TestCase $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 @@ class ConjugateGradientTest extends TestCase $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 0000000..22efdd2 --- /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 b314c81..c7b5e75 100644 --- a/src/Classification/Ensemble/AdaBoost.php +++ b/src/Classification/Ensemble/AdaBoost.php @@ -4,10 +4,10 @@ declare(strict_types=1); 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 @@ class AdaBoost implements Classifier } /** - * @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 a1d51c2..02e958b 100644 --- a/src/Classification/Ensemble/Bagging.php +++ b/src/Classification/Ensemble/Bagging.php @@ -4,9 +4,9 @@ declare(strict_types=1); 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 @@ class Bagging implements Classifier * * @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 3b6309d..a133732 100644 --- a/src/Classification/Linear/Adaline.php +++ b/src/Classification/Linear/Adaline.php @@ -4,7 +4,7 @@ declare(strict_types=1); 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 @@ class Adaline extends Perceptron 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 1903712..ef6a458 100644 --- a/src/Classification/Linear/DecisionStump.php +++ b/src/Classification/Linear/DecisionStump.php @@ -4,9 +4,9 @@ declare(strict_types=1); 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 @@ class DecisionStump extends WeightedClassifier } /** - * @throws \Exception + * @throws InvalidArgumentException */ protected function trainBinary(array $samples, array $targets, array $labels): void { @@ -121,7 +121,7 @@ class DecisionStump extends WeightedClassifier 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 13f4b8a..0414d59 100644 --- a/src/Classification/Linear/LogisticRegression.php +++ b/src/Classification/Linear/LogisticRegression.php @@ -6,6 +6,7 @@ namespace Phpml\Classification\Linear; 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 @@ class LogisticRegression extends Adaline ) { $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 @@ class LogisticRegression extends Adaline 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 @@ class LogisticRegression extends Adaline 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 5fffc01..038b4c8 100644 --- a/src/Classification/Linear/Perceptron.php +++ b/src/Classification/Linear/Perceptron.php @@ -5,8 +5,8 @@ declare(strict_types=1); 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 b962b3d..29deb4c 100644 --- a/src/DimensionReduction/KernelPCA.php +++ b/src/DimensionReduction/KernelPCA.php @@ -6,6 +6,8 @@ namespace Phpml\DimensionReduction; 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 @@ class KernelPCA extends PCA * 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 @@ class KernelPCA extends PCA }; 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 d188205..68ab0cd 100644 --- a/src/DimensionReduction/LDA.php +++ b/src/DimensionReduction/LDA.php @@ -4,7 +4,8 @@ declare(strict_types=1); 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 @@ class LDA extends EigenTransformerBase * 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 18879bb..a3d8a4d 100644 --- a/src/DimensionReduction/PCA.php +++ b/src/DimensionReduction/PCA.php @@ -4,7 +4,8 @@ declare(strict_types=1); 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 @@ class PCA extends EigenTransformerBase * 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 7677c31..095cde0 100644 --- a/tests/Classification/Ensemble/AdaBoostTest.php +++ b/tests/Classification/Ensemble/AdaBoostTest.php @@ -5,12 +5,32 @@ declare(strict_types=1); 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 @@ class AdaBoostTest extends TestCase $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 69c4d01..5b2e47b 100644 --- a/tests/Classification/Ensemble/BaggingTest.php +++ b/tests/Classification/Ensemble/BaggingTest.php @@ -7,6 +7,7 @@ namespace Phpml\Tests\Classification\Ensemble; 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 @@ class BaggingTest extends TestCase $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 c8ca752..62224e8 100644 --- a/tests/Classification/Linear/AdalineTest.php +++ b/tests/Classification/Linear/AdalineTest.php @@ -5,11 +5,23 @@ declare(strict_types=1); 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 93c8595..1295ac5 100644 --- a/tests/Classification/Linear/DecisionStumpTest.php +++ b/tests/Classification/Linear/DecisionStumpTest.php @@ -5,11 +5,24 @@ declare(strict_types=1); 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 ed9b878..37b5182 100644 --- a/tests/Classification/Linear/LogisticRegressionTest.php +++ b/tests/Classification/Linear/LogisticRegressionTest.php @@ -5,16 +5,16 @@ declare(strict_types=1); 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 @@ class LogisticRegressionTest extends TestCase public function testConstructorThrowWhenInvalidCost(): void { - $this->expectException(Throwable::class); + $this->expectException(InvalidArgumentException::class); $classifier = new LogisticRegression( 500, @@ -40,7 +40,7 @@ class LogisticRegressionTest extends TestCase 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 35af855..731eac1 100644 --- a/tests/Classification/Linear/PerceptronTest.php +++ b/tests/Classification/Linear/PerceptronTest.php @@ -5,11 +5,24 @@ declare(strict_types=1); 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 05a4138..dfd8bb9 100644 --- a/tests/DimensionReduction/KernelPCATest.php +++ b/tests/DimensionReduction/KernelPCATest.php @@ -5,6 +5,8 @@ declare(strict_types=1); 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 @@ class KernelPCATest extends TestCase $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 2803a4b..641c226 100644 --- a/tests/DimensionReduction/LDATest.php +++ b/tests/DimensionReduction/LDATest.php @@ -6,6 +6,8 @@ namespace Phpml\Tests\DimensionReduction; 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 @@ class LDATest extends TestCase 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 337b253..5fca211 100644 --- a/tests/DimensionReduction/PCATest.php +++ b/tests/DimensionReduction/PCATest.php @@ -5,6 +5,8 @@ declare(strict_types=1); 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 @@ class PCATest extends TestCase }, $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 abcf10d..e694878 100644 --- a/tests/Classification/DecisionTree/DecisionTreeLeafTest.php +++ b/tests/Classification/DecisionTree/DecisionTreeLeafTest.php @@ -23,4 +23,28 @@ class DecisionTreeLeafTest extends TestCase $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 f69d8e6..ae89d20 100644 --- a/src/Math/Matrix.php +++ b/src/Math/Matrix.php @@ -142,15 +142,24 @@ class Matrix 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 296feb1..661f717 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 86ad754..1aff1c4 100644 --- a/src/Clustering/KMeans.php +++ b/src/Clustering/KMeans.php @@ -35,9 +35,9 @@ class KMeans implements Clusterer 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 8936926..731d79c 100644 --- a/src/Clustering/KMeans/Cluster.php +++ b/src/Clustering/KMeans/Cluster.php @@ -32,7 +32,11 @@ class Cluster extends Point implements IteratorAggregate, Countable { $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 8c918a7..7d41093 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 b85b329..8d80dc0 100644 --- a/src/Clustering/KMeans/Space.php +++ b/src/Clustering/KMeans/Space.php @@ -35,21 +35,21 @@ class Space extends SplObjectStorage 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 032c804..ba36bc6 100644 --- a/tests/Clustering/KMeansTest.php +++ b/tests/Clustering/KMeansTest.php @@ -28,6 +28,32 @@ class KMeansTest extends TestCase $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 3ffb1da..5fb4f2b 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 b2d5f9e..68dd849 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 ddca60a..192195e 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 c8652d9..c809822 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 0462e94..0eeb693 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 2e204e8..4ba9d56 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 0000000..ea802f7 --- /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 0000000..e69de29 diff --git a/tests/Performance/Regression/LeastSquaresBench.php b/tests/Performance/Regression/LeastSquaresBench.php new file mode 100644 index 0000000..d6dba2f --- /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 0000000..a358903 --- /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 ba6bd74..1678f5f 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 61ecd63..ec7b3aa 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 bc647cb..58ccbc6 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 ec7b3aa..efd2174 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 3e501e0..0000000 --- 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 93303ee..e185bd3 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 | 1339 +++++++++-------- 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, 821 insertions(+), 765 deletions(-) delete mode 100644 easy-coding-standard.neon create mode 100644 ecs.yml diff --git a/composer.json b/composer.json index c809822..664eeeb 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 0eeb693..5a88697 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", - "keywords": [ - "extension", - "filesystem", - "phpunit" + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } ], - "time": "2017-08-23T07:46:41+00:00" + "description": "A wrapper for ocramius/package-versions to get pretty versions strings", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], + "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": "paragonie/random_compat", - "version": "v2.0.11", + "name": "ocramius/package-versions", + "version": "1.3.0", "source": { "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8" + "url": "https://github.com/Ocramius/PackageVersions.git", + "reference": "4489d5002c49d55576fa0ba786f42dbb009be46f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/5da4d3c796c275c55f057af5a643ae297d96b4d8", - "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8", + "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.15", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "10bcb46e8f3d365170f6de9d05245aa066b81f09" + }, + "dist": { + "type": "zip", + "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/config", - "version": "v4.0.3", + "name": "symfony/cache", + "version": "v4.1.0", "source": { "type": "git", - "url": "https://github.com/symfony/config.git", - "reference": "0e86d267db0851cf55f339c97df00d693fe8592f" + "url": "https://github.com/symfony/cache.git", + "reference": "4986efce97c002e58380e8c0474acbf72eda9339" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/0e86d267db0851cf55f339c97df00d693fe8592f", - "reference": "0e86d267db0851cf55f339c97df00d693fe8592f", + "url": "https://api.github.com/repos/symfony/cache/zipball/4986efce97c002e58380e8c0474acbf72eda9339", + "reference": "4986efce97c002e58380e8c0474acbf72eda9339", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/filesystem": "~3.4|~4.0" + "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.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "5ceefc256caecc3e25147c4e5b933de71d0020c4" + }, + "dist": { + "type": "zip", + "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/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-mbstring", - "version": "v1.6.0", + "name": "symfony/polyfill-ctype", + "version": "v1.8.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296" + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", - "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", + "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.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "3296adf6a6454a050679cde90f95350ad604b171" + }, + "dist": { + "type": "zip", + "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 028fe9e..0000000 --- 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 0000000..405ef36 --- /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 690f79c..0f428c5 100644 --- a/src/Classification/DecisionTree.php +++ b/src/Classification/DecisionTree.php @@ -452,9 +452,7 @@ class DecisionTree implements Classifier $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 038b4c8..ea49eeb 100644 --- a/src/Classification/Linear/Perceptron.php +++ b/src/Classification/Linear/Perceptron.php @@ -9,6 +9,7 @@ use Phpml\Classification\Classifier; 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 8f09257..9042547 100644 --- a/src/Classification/NaiveBayes.php +++ b/src/Classification/NaiveBayes.php @@ -155,7 +155,7 @@ class NaiveBayes implements Classifier // 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 8d80dc0..aa60eb3 100644 --- a/src/Clustering/KMeans/Space.php +++ b/src/Clustering/KMeans/Space.php @@ -197,7 +197,9 @@ class Space extends SplObjectStorage $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 d450842..85dd5d1 100644 --- a/src/CrossValidation/StratifiedRandomSplit.php +++ b/src/CrossValidation/StratifiedRandomSplit.php @@ -33,9 +33,7 @@ class StratifiedRandomSplit extends RandomSplit $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 f851d85..0c775a9 100644 --- a/src/Dataset/Dataset.php +++ b/src/Dataset/Dataset.php @@ -6,13 +6,7 @@ namespace Phpml\Dataset; 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 c1e261b..824fcff 100644 --- a/src/Dataset/SvmDataset.php +++ b/src/Dataset/SvmDataset.php @@ -79,9 +79,7 @@ class SvmDataset extends ArrayDataset $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 8b98bb6..b426889 100644 --- a/src/Estimator.php +++ b/src/Estimator.php @@ -6,15 +6,9 @@ namespace Phpml; 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 2ef9017..74d1cc0 100644 --- a/src/Helper/Predictable.php +++ b/src/Helper/Predictable.php @@ -7,8 +7,6 @@ namespace Phpml\Helper; trait Predictable { /** - * @param array $samples - * * @return mixed */ public function predict(array $samples) @@ -26,8 +24,6 @@ trait Predictable } /** - * @param array $sample - * * @return mixed */ abstract protected function predictSample(array $sample); diff --git a/src/Helper/Trainable.php b/src/Helper/Trainable.php index 86ffaf1..1388760 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 4a0d1cc..e356be0 100644 --- a/src/IncrementalEstimator.php +++ b/src/IncrementalEstimator.php @@ -6,10 +6,5 @@ namespace Phpml; 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 9faa8e0..f49bd33 100644 --- a/src/Math/Distance.php +++ b/src/Math/Distance.php @@ -6,9 +6,5 @@ namespace Phpml\Math; 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 19f3c43..56e5b8a 100644 --- a/src/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -18,7 +18,7 @@ declare(strict_types=1); * 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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 ce52f3b..7039e39 100644 --- a/src/Math/Statistic/Correlation.php +++ b/src/Math/Statistic/Correlation.php @@ -36,8 +36,6 @@ class Correlation $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 24aaeea..ff8470c 100644 --- a/src/Math/Statistic/Gaussian.php +++ b/src/Math/Statistic/Gaussian.php @@ -34,7 +34,7 @@ class Gaussian $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 4409474..969dcc6 100644 --- a/src/Metric/ClassificationReport.php +++ b/src/Metric/ClassificationReport.php @@ -226,8 +226,7 @@ class ClassificationReport { $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 a1f49ce..5fd3ac5 100644 --- a/src/Metric/ConfusionMatrix.php +++ b/src/Metric/ConfusionMatrix.php @@ -25,7 +25,7 @@ class ConfusionMatrix $column = $labels[$predicted]; } - $matrix[$row][$column] += 1; + ++$matrix[$row][$column]; } return $matrix; @@ -47,8 +47,7 @@ class ConfusionMatrix { $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 fcb6d73..e699c47 100644 --- a/src/NeuralNetwork/Training.php +++ b/src/NeuralNetwork/Training.php @@ -6,9 +6,5 @@ namespace Phpml\NeuralNetwork; 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 9125e06..96397c1 100644 --- a/src/Preprocessing/Imputer/Strategy.php +++ b/src/Preprocessing/Imputer/Strategy.php @@ -7,8 +7,6 @@ namespace Phpml\Preprocessing\Imputer; 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 262927b..1a43731 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 734bbd2..4be96d3 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 2eb9f06..1dd5e46 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 6c7b897..558c31f 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 50cabac..7adde6c 100644 --- a/tests/Math/MatrixTest.php +++ b/tests/Math/MatrixTest.php @@ -136,10 +136,10 @@ class MatrixTest extends TestCase { $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 b9e40c0..03e8f7e 100644 --- a/tests/Tokenization/WhitespaceTokenizerTest.php +++ b/tests/Tokenization/WhitespaceTokenizerTest.php @@ -18,8 +18,8 @@ class WhitespaceTokenizerTest extends TestCase 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 @@ class WhitespaceTokenizerTest extends TestCase 殍涾烰 齞齝囃 蹅輶 鄜, 孻憵 擙樲橚 藒襓謥 岯岪弨 蒮 廞徲 孻憵懥 趡趛踠 槏'; $tokens = ['鋍鞎', '鳼', '鞮鞢騉', '袟袘觕,', '炟砏', '蒮', '謺貙蹖', '偢偣唲', '蒛', '箷箯緷', '鑴鱱爧', '覮轀,', - '剆坲', '煘煓瑐', '鬐鶤鶐', '飹勫嫢', '銪', '餀', '枲柊氠', '鍎鞚韕', '焲犈,', - '殍涾烰', '齞齝囃', '蹅輶', '鄜,', '孻憵', '擙樲橚', '藒襓謥', '岯岪弨', '蒮', '廞徲', '孻憵懥', '趡趛踠', '槏', ]; + '剆坲', '煘煓瑐', '鬐鶤鶐', '飹勫嫢', '銪', '餀', '枲柊氠', '鍎鞚韕', '焲犈,', + '殍涾烰', '齞齝囃', '蹅輶', '鄜,', '孻憵', '擙樲橚', '藒襓謥', '岯岪弨', '蒮', '廞徲', '孻憵懥', '趡趛踠', '槏', ]; $this->assertEquals($tokens, $tokenizer->tokenize($text)); } diff --git a/tests/Tokenization/WordTokenizerTest.php b/tests/Tokenization/WordTokenizerTest.php index d18edb6..387e0f8 100644 --- a/tests/Tokenization/WordTokenizerTest.php +++ b/tests/Tokenization/WordTokenizerTest.php @@ -18,8 +18,8 @@ class WordTokenizerTest extends TestCase 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 @@ class WordTokenizerTest extends TestCase 殍涾烰 齞齝囃 蹅輶 鄜, 孻憵 擙樲橚 藒襓謥 岯岪弨 蒮 廞徲 孻憵懥 趡趛踠 槏'; $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 efd2174..13bae10 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 da0511c..99b4da0 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 de71bbe..fbc47ba 100644 --- a/src/Classification/SVC.php +++ b/src/Classification/SVC.php @@ -11,7 +11,7 @@ use Phpml\SupportVectorMachine\Type; 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 0000000..93f6619 --- /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 13bae10..5990242 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 06272e2..fcc18f6 100644 --- a/src/SupportVectorMachine/DataTransformer.php +++ b/src/SupportVectorMachine/DataTransformer.php @@ -104,7 +104,7 @@ class DataTransformer { $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 be16ff4..4c2f87b 100644 --- a/src/SupportVectorMachine/SupportVectorMachine.php +++ b/src/SupportVectorMachine/SupportVectorMachine.php @@ -269,7 +269,7 @@ class SupportVectorMachine 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 0709e04..1ec1541 100644 --- a/tests/Classification/SVCTest.php +++ b/tests/Classification/SVCTest.php @@ -57,13 +57,32 @@ class SVCTest extends TestCase $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 75c23d6..df29806 100644 --- a/tests/SupportVectorMachine/DataTransformerTest.php +++ b/tests/SupportVectorMachine/DataTransformerTest.php @@ -16,10 +16,10 @@ class DataTransformerTest extends TestCase $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 @@ class DataTransformerTest extends TestCase $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 192195e..d93996f 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 4ba9d56..12cbbd5 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 5990242..3c44b94 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 48a5b3a..219db22 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 fdce666..e5b5af8 100644 --- a/src/Preprocessing/Imputer.php +++ b/src/Preprocessing/Imputer.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Phpml\Preprocessing; +use Phpml\Exception\InvalidOperationException; use Phpml\Preprocessing\Imputer\Strategy; class Imputer implements Preprocessor @@ -50,6 +51,10 @@ class Imputer implements Preprocessor 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 c229c15..1078e54 100644 --- a/tests/Preprocessing/ImputerTest.php +++ b/tests/Preprocessing/ImputerTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); 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 @@ class ImputerTest extends TestCase $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 13963f3..35678d5 100644 --- a/src/Classification/MLPClassifier.php +++ b/src/Classification/MLPClassifier.php @@ -41,7 +41,7 @@ class MLPClassifier extends MultilayerPerceptron implements Classifier } } - return $this->classes[$predictedClass]; + return $predictedClass; } /** @@ -49,9 +49,8 @@ class MLPClassifier extends MultilayerPerceptron implements Classifier */ protected function trainSample(array $sample, $target): void { - // Feed-forward. - $this->setInput($sample)->getOutput(); + $this->setInput($sample); // Back-propagate. $this->backpropagation->backpropagate($this->getLayers(), $this->getTargetClass($target)); diff --git a/src/NeuralNetwork/Layer.php b/src/NeuralNetwork/Layer.php index 1c681f8..1c67c04 100644 --- a/src/NeuralNetwork/Layer.php +++ b/src/NeuralNetwork/Layer.php @@ -41,12 +41,9 @@ class Layer return $this->nodes; } - /** - * @return Neuron - */ private function createNode(string $nodeClass, ?ActivationFunction $activationFunction = null): Node { - if ($nodeClass == Neuron::class) { + if ($nodeClass === Neuron::class) { return new Neuron($activationFunction); } diff --git a/src/NeuralNetwork/Network/LayeredNetwork.php b/src/NeuralNetwork/Network/LayeredNetwork.php index 4f05398..03bfef5 100644 --- a/src/NeuralNetwork/Network/LayeredNetwork.php +++ b/src/NeuralNetwork/Network/LayeredNetwork.php @@ -51,8 +51,6 @@ abstract class LayeredNetwork implements Network /** * @param mixed $input - * - * @return $this */ public function setInput($input): Network { diff --git a/src/NeuralNetwork/Network/MultilayerPerceptron.php b/src/NeuralNetwork/Network/MultilayerPerceptron.php index 3626063..8ff49bc 100644 --- a/src/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/NeuralNetwork/Network/MultilayerPerceptron.php @@ -69,6 +69,10 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, throw new InvalidArgumentException('Provide at least 2 different classes'); } + if (count($classes) !== count(array_unique($classes))) { + throw new InvalidArgumentException('Classes must be unique'); + } + $this->classes = array_values($classes); $this->iterations = $iterations; $this->inputLayerFeatures = $inputLayerFeatures; @@ -109,6 +113,16 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, $this->backpropagation->setLearningRate($this->learningRate); } + public function getOutput(): array + { + $result = []; + foreach ($this->getOutputLayer()->getNodes() as $i => $neuron) { + $result[$this->classes[$i]] = $neuron->getOutput(); + } + + return $result; + } + /** * @param mixed $target */ diff --git a/src/NeuralNetwork/Node/Neuron.php b/src/NeuralNetwork/Node/Neuron.php index 47d606d..c537606 100644 --- a/src/NeuralNetwork/Node/Neuron.php +++ b/src/NeuralNetwork/Node/Neuron.php @@ -44,7 +44,7 @@ class Neuron implements Node /** * @return Synapse[] */ - public function getSynapses() + public function getSynapses(): array { return $this->synapses; } diff --git a/tests/Classification/MLPClassifierTest.php b/tests/Classification/MLPClassifierTest.php index d3680b6..ae0871d 100644 --- a/tests/Classification/MLPClassifierTest.php +++ b/tests/Classification/MLPClassifierTest.php @@ -183,7 +183,7 @@ class MLPClassifierTest extends TestCase $testSamples = [[0, 0], [1, 0], [0, 1], [1, 1]]; $predicted = $classifier->predict($testSamples); - $filename = 'perceptron-test-'.random_int(100, 999).'-'.uniqid(); + $filename = 'perceptron-test-'.random_int(100, 999).'-'.uniqid('', false); $filepath = tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); @@ -204,7 +204,7 @@ class MLPClassifierTest extends TestCase $this->assertEquals('a', $network->predict([1, 0])); $this->assertEquals('b', $network->predict([0, 1])); - $filename = 'perceptron-test-'.random_int(100, 999).'-'.uniqid(); + $filename = 'perceptron-test-'.random_int(100, 999).'-'.uniqid('', false); $filepath = tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($network, $filepath); @@ -245,6 +245,13 @@ class MLPClassifierTest extends TestCase new MLPClassifier(2, [2], [0]); } + public function testOutputWithLabels(): void + { + $output = (new MLPClassifier(2, [2, 2], ['T', 'F']))->getOutput(); + + $this->assertEquals(['T', 'F'], array_keys($output)); + } + private function getSynapsesNodes(array $synapses): array { $nodes = []; diff --git a/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php b/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php index 006733f..cea2a2f 100644 --- a/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php +++ b/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Phpml\Tests\NeuralNetwork\Network; +use Phpml\Exception\InvalidArgumentException; use Phpml\NeuralNetwork\ActivationFunction; use Phpml\NeuralNetwork\Layer; use Phpml\NeuralNetwork\Network\MultilayerPerceptron; @@ -13,6 +14,39 @@ use PHPUnit_Framework_MockObject_MockObject; class MultilayerPerceptronTest extends TestCase { + public function testThrowExceptionWhenHiddenLayersAreEmpty(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Provide at least 1 hidden layer'); + + $this->getMockForAbstractClass( + MultilayerPerceptron::class, + [5, [], [0, 1], 1000, null, 0.42] + ); + } + + public function testThrowExceptionWhenThereIsOnlyOneClass(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Provide at least 2 different classes'); + + $this->getMockForAbstractClass( + MultilayerPerceptron::class, + [5, [3], [0], 1000, null, 0.42] + ); + } + + public function testThrowExceptionWhenClassesAreNotUnique(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Classes must be unique'); + + $this->getMockForAbstractClass( + MultilayerPerceptron::class, + [5, [3], [0, 1, 2, 3, 1], 1000, null, 0.42] + ); + } + public function testLearningRateSetter(): void { /** @var MultilayerPerceptron $mlp */ 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 3c44b94..12a9c3a 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 | 669 +++++++++++------- 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, 593 insertions(+), 382 deletions(-) diff --git a/composer.json b/composer.json index 664eeeb..7b1cbb6 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 5a88697..7a2beb9 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", @@ -2811,22 +2819,69 @@ "time": "2018-01-24T12:46:19+00:00" }, { - "name": "slevomat/coding-standard", - "version": "4.6.2", + "name": "slam/php-cs-fixer-extensions", + "version": "v1.17.0", "source": { "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "d43b9a627cdcb7ce837a1d85a79b52645cdf44bc" + "url": "https://github.com/Slamdunk/php-cs-fixer-extensions.git", + "reference": "f2d819ff70cc098f68cd2eefd3279c2f54e0ccad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/d43b9a627cdcb7ce837a1d85a79b52645cdf44bc", - "reference": "d43b9a627cdcb7ce837a1d85a79b52645cdf44bc", + "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.8.5", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "057f3f154cf4888b60eb4cdffadc509a3ae9dccd" + }, + "dist": { + "type": "zip", + "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 abbdf47..2d09dd7 100644 --- a/src/Association/Apriori.php +++ b/src/Association/Apriori.php @@ -9,7 +9,8 @@ use Phpml\Helper\Trainable; 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 0f428c5..13a7975 100644 --- a/src/Classification/DecisionTree.php +++ b/src/Classification/DecisionTree.php @@ -12,7 +12,8 @@ use Phpml\Math\Statistic\Mean; 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 @@ class DecisionTree implements Classifier // 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 c7b5e75..3859295 100644 --- a/src/Classification/Ensemble/AdaBoost.php +++ b/src/Classification/Ensemble/AdaBoost.php @@ -16,7 +16,8 @@ use ReflectionClass; 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 02e958b..b73a1d3 100644 --- a/src/Classification/Ensemble/Bagging.php +++ b/src/Classification/Ensemble/Bagging.php @@ -13,7 +13,8 @@ use ReflectionClass; 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 d871fe2..c0f0dd8 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 @@ class RandomForest extends Bagging 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 @@ class RandomForest extends Bagging */ 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 @@ class RandomForest extends Bagging // 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 @@ class RandomForest extends Bagging { 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 238fc1d..cac5416 100644 --- a/src/Classification/KNearestNeighbors.php +++ b/src/Classification/KNearestNeighbors.php @@ -11,7 +11,8 @@ use Phpml\Math\Distance\Euclidean; class KNearestNeighbors implements Classifier { - use Trainable, Predictable; + use Trainable; + use Predictable; /** * @var int @@ -47,7 +48,7 @@ class KNearestNeighbors implements Classifier $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 ef6a458..c83d339 100644 --- a/src/Classification/Linear/DecisionStump.php +++ b/src/Classification/Linear/DecisionStump.php @@ -13,7 +13,8 @@ use Phpml\Math\Comparison; 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 0414d59..8ab6939 100644 --- a/src/Classification/Linear/LogisticRegression.php +++ b/src/Classification/Linear/LogisticRegression.php @@ -87,10 +87,8 @@ class LogisticRegression extends Adaline ); } - 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 @@ class LogisticRegression extends Adaline protected function getCostFunction(): Closure { $penalty = 0; - if ($this->penalty == 'L2') { + if ($this->penalty === 'L2') { $penalty = $this->lambda; } @@ -190,7 +188,7 @@ class LogisticRegression extends Adaline * 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 @@ class LogisticRegression extends Adaline return [$error, $gradient, $penalty]; }; - - return $callback; - case 'sse': /* * Sum of squared errors or least squared errors cost function: @@ -225,7 +220,7 @@ class LogisticRegression extends Adaline * 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 @@ class LogisticRegression extends Adaline 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 ea49eeb..adc6b36 100644 --- a/src/Classification/Linear/Perceptron.php +++ b/src/Classification/Linear/Perceptron.php @@ -17,7 +17,8 @@ use Phpml\Preprocessing\Normalizer; 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 9042547..f14ada7 100644 --- a/src/Classification/NaiveBayes.php +++ b/src/Classification/NaiveBayes.php @@ -11,7 +11,8 @@ use Phpml\Math\Statistic\StandardDeviation; 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 e0e7822..095c597 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 824fcff..e5f2a86 100644 --- a/src/Dataset/SvmDataset.php +++ b/src/Dataset/SvmDataset.php @@ -94,7 +94,7 @@ class SvmDataset extends ArrayDataset 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 a3d8a4d..fa651da 100644 --- a/src/DimensionReduction/PCA.php +++ b/src/DimensionReduction/PCA.php @@ -120,7 +120,7 @@ class PCA extends EigenTransformerBase } // 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 c73836c..a1e38f4 100644 --- a/src/FeatureExtraction/TokenCountVectorizer.php +++ b/src/FeatureExtraction/TokenCountVectorizer.php @@ -48,9 +48,9 @@ class TokenCountVectorizer implements Transformer 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 @@ class TokenCountVectorizer implements Transformer 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 968c474..18d0ba9 100644 --- a/src/FeatureSelection/ScoringFunction/UnivariateLinearRegression.php +++ b/src/FeatureSelection/ScoringFunction/UnivariateLinearRegression.php @@ -43,7 +43,7 @@ final class UnivariateLinearRegression implements ScoringFunction } $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 @@ final class UnivariateLinearRegression implements ScoringFunction }, $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 56e5b8a..3d7484d 100644 --- a/src/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -179,7 +179,7 @@ class EigenvalueDecomposition 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 @@ class EigenvalueDecomposition } $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 @@ class EigenvalueDecomposition } 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 @@ class EigenvalueDecomposition // 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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition } 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 @@ class EigenvalueDecomposition // 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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $this->e[$n] = -$z; } - $n = $n - 2; + $n -= 2; $iter = 0; // No convergence yet } else { @@ -687,7 +687,7 @@ class EigenvalueDecomposition // 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 @@ class EigenvalueDecomposition } // 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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition 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 44e8a26..f9e7300 100644 --- a/src/Math/LinearAlgebra/LUDecomposition.php +++ b/src/Math/LinearAlgebra/LUDecomposition.php @@ -133,7 +133,7 @@ class LUDecomposition $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 ae89d20..840d746 100644 --- a/src/Math/Matrix.php +++ b/src/Math/Matrix.php @@ -261,7 +261,7 @@ class Matrix $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 2732ba7..0118347 100644 --- a/src/Math/Statistic/ANOVA.php +++ b/src/Math/Statistic/ANOVA.php @@ -102,7 +102,7 @@ final class ANOVA { 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 95927ea..fb14ada 100644 --- a/src/Preprocessing/Normalizer.php +++ b/src/Preprocessing/Normalizer.php @@ -54,7 +54,7 @@ class Normalizer implements Preprocessor 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 @@ class Normalizer implements Preprocessor $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 @@ class Normalizer implements Preprocessor 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 d96152a..110d49d 100644 --- a/tests/Classification/KNearestNeighborsTest.php +++ b/tests/Classification/KNearestNeighborsTest.php @@ -66,14 +66,13 @@ class KNearestNeighborsTest extends TestCase $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 62224e8..08ad78a 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 @@ class AdalineTest extends TestCase $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 37b5182..075e1ea 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 @@ class LogisticRegressionTest extends TestCase 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 @@ class LogisticRegressionTest extends TestCase 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 731eac1..a3958b8 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 @@ class PerceptronTest extends TestCase $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 7db8645..434c920 100644 --- a/tests/Classification/NaiveBayesTest.php +++ b/tests/Classification/NaiveBayesTest.php @@ -53,13 +53,12 @@ class NaiveBayesTest extends TestCase $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 @@ class NaiveBayesTest extends TestCase $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 884da3a..700055c 100644 --- a/tests/Dataset/SvmDatasetTest.php +++ b/tests/Dataset/SvmDatasetTest.php @@ -136,77 +136,77 @@ class SvmDatasetTest extends TestCase 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 dfd8bb9..cca0d53 100644 --- a/tests/DimensionReduction/KernelPCATest.php +++ b/tests/DimensionReduction/KernelPCATest.php @@ -54,16 +54,18 @@ class KernelPCATest extends TestCase 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 @@ class KernelPCATest extends TestCase [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 641c226..79e925f 100644 --- a/tests/DimensionReduction/LDATest.php +++ b/tests/DimensionReduction/LDATest.php @@ -68,25 +68,29 @@ class LDATest extends TestCase 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 @@ class LDATest extends TestCase $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 5fca211..ba25268 100644 --- a/tests/DimensionReduction/PCATest.php +++ b/tests/DimensionReduction/PCATest.php @@ -60,25 +60,29 @@ class PCATest extends TestCase 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 @@ class PCATest extends TestCase $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 4c4f01f..fa9080b 100644 --- a/tests/Metric/ClassificationReportTest.php +++ b/tests/Metric/ClassificationReportTest.php @@ -98,7 +98,7 @@ class ClassificationReportTest extends TestCase $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 899fa40..088d37b 100644 --- a/tests/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/SupportVectorMachine/SupportVectorMachineTest.php @@ -197,7 +197,7 @@ SV $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 7a2beb9..fa22470 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 | 135 +++++++++--------- 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 | 26 ++-- 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 | 30 ++-- 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, 1010 insertions(+), 974 deletions(-) delete mode 100644 src/NeuralNetwork/Training.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 12a9c3a..1c1e097 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 7b1cbb6..1604bbf 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 fa22470..70324ee 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 7eaee1c..7a676fa 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 2d09dd7..201bfbf 100644 --- a/src/Association/Apriori.php +++ b/src/Association/Apriori.php @@ -64,11 +64,11 @@ class Apriori implements Associator */ 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 @@ class Apriori implements Associator $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 @@ class Apriori implements Associator */ 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 @@ class Apriori implements Associator continue; } - foreach ((array) $this->samples as $sample) { + foreach ($this->samples as $sample) { if ($this->subset($sample, $candidate)) { $candidates[] = $candidate; @@ -316,7 +316,7 @@ class Apriori implements Associator */ 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 13a7975..d8010f0 100644 --- a/src/Classification/DecisionTree.php +++ b/src/Classification/DecisionTree.php @@ -249,7 +249,7 @@ class DecisionTree implements Classifier 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 @@ class DecisionTree implements Classifier 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 @@ class DecisionTree implements Classifier 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 @@ class DecisionTree implements Classifier $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 @@ class DecisionTree implements Classifier 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 @@ class DecisionTree implements Classifier // 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 @@ class DecisionTree implements Classifier $node = $this->tree; do { if ($node->isTerminal) { - break; + return $node->classValue; } if ($node->evaluate($sample)) { @@ -473,6 +479,6 @@ class DecisionTree implements Classifier } } 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 649c743..04af3d6 100644 --- a/src/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Classification/DecisionTree/DecisionTreeLeaf.php @@ -119,7 +119,7 @@ class DecisionTreeLeaf /** * 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 @@ class DecisionTreeLeaf $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 3859295..fdaeb63 100644 --- a/src/Classification/Ensemble/AdaBoost.php +++ b/src/Classification/Ensemble/AdaBoost.php @@ -100,7 +100,7 @@ class AdaBoost implements Classifier { // 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 @@ class AdaBoost implements Classifier 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 b73a1d3..26cc7a6 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 @@ class Bagging implements Classifier $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 cac5416..9b78baa 100644 --- a/src/Classification/KNearestNeighbors.php +++ b/src/Classification/KNearestNeighbors.php @@ -45,8 +45,7 @@ class KNearestNeighbors implements Classifier 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 a133732..797cdc9 100644 --- a/src/Classification/Linear/Adaline.php +++ b/src/Classification/Linear/Adaline.php @@ -55,7 +55,7 @@ class Adaline extends Perceptron * 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 @@ class Adaline extends Perceptron $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 c83d339..258939e 100644 --- a/src/Classification/Linear/DecisionStump.php +++ b/src/Classification/Linear/DecisionStump.php @@ -119,13 +119,13 @@ class DecisionStump extends WeightedClassifier // 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 @@ class DecisionStump extends WeightedClassifier // 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 @@ class DecisionStump extends WeightedClassifier // 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 @@ class DecisionStump extends WeightedClassifier 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 adc6b36..36cd4d1 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 @@ class Perceptron implements Classifier, IncrementalEstimator * 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 @@ class Perceptron implements Classifier, IncrementalEstimator * 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 5e6fa0c..db42fe9 100644 --- a/src/Clustering/FuzzyCMeans.php +++ b/src/Clustering/FuzzyCMeans.php @@ -108,8 +108,7 @@ class FuzzyCMeans implements Clusterer $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 @@ class FuzzyCMeans implements Clusterer 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 @@ class FuzzyCMeans implements Clusterer } } - 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 @@ class FuzzyCMeans implements Clusterer $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 @@ class FuzzyCMeans implements Clusterer * 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 731d79c..fa73e4b 100644 --- a/src/Clustering/KMeans/Cluster.php +++ b/src/Clustering/KMeans/Cluster.php @@ -4,12 +4,11 @@ declare(strict_types=1); 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 @@ class Cluster extends Point implements IteratorAggregate, Countable { $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 @@ class Cluster extends Point implements IteratorAggregate, Countable 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 7d41093..f6ad3f5 100644 --- a/src/Clustering/KMeans/Point.php +++ b/src/Clustering/KMeans/Point.php @@ -6,7 +6,7 @@ namespace Phpml\Clustering\KMeans; 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 @@ class Point implements ArrayAccess } /** - * @return int|mixed + * @return float|int */ public function getDistanceWith(self $point, bool $precise = true) { @@ -50,9 +53,9 @@ class Point implements ArrayAccess } /** - * @return mixed + * @param Point[] $points */ - public function getClosest(array $points) + public function getClosest(array $points): ?self { $minPoint = null; @@ -114,4 +117,9 @@ class Point implements ArrayAccess { 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 aa60eb3..566d691 100644 --- a/src/Clustering/KMeans/Space.php +++ b/src/Clustering/KMeans/Space.php @@ -28,6 +28,8 @@ class Space extends SplObjectStorage public function toArray(): array { $points = []; + + /** @var Point $point */ foreach ($this as $point) { $points[] = $point->toArray(); } @@ -35,9 +37,12 @@ class Space extends SplObjectStorage 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 @@ class Space extends SplObjectStorage } /** - * @param null $data + * @param mixed $label + * @param mixed $data */ public function addPoint(array $coordinates, $label = null, $data = null): void { @@ -53,8 +59,8 @@ class Space extends SplObjectStorage } /** - * @param Point $point - * @param null $data + * @param object $point + * @param mixed $data */ public function attach($point, $data = null): void { @@ -82,10 +88,16 @@ class Space extends SplObjectStorage $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 @@ class Space extends SplObjectStorage return $clusters; } - protected function iterate($clusters): bool + /** + * @param Cluster[] $clusters + */ + protected function iterate(array $clusters): bool { $convergence = true; @@ -164,10 +179,12 @@ class Space extends SplObjectStorage } } + /** @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 @@ class Space extends SplObjectStorage 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 @@ class Space extends SplObjectStorage return $clusters; } + /** + * @return Cluster[] + */ private function initializeRandomClusters(int $clustersNumber): array { $clusters = []; diff --git a/src/CrossValidation/Split.php b/src/CrossValidation/Split.php index bffb59a..e9d401c 100644 --- a/src/CrossValidation/Split.php +++ b/src/CrossValidation/Split.php @@ -60,7 +60,7 @@ abstract class Split 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 85dd5d1..4974d4c 100644 --- a/src/CrossValidation/StratifiedRandomSplit.php +++ b/src/CrossValidation/StratifiedRandomSplit.php @@ -27,6 +27,7 @@ class StratifiedRandomSplit extends RandomSplit $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 631c6a6..cdd387f 100644 --- a/src/Dataset/CsvDataset.php +++ b/src/Dataset/CsvDataset.php @@ -29,14 +29,14 @@ class CsvDataset extends ArrayDataset 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 e5f2a86..334ec6c 100644 --- a/src/Dataset/SvmDataset.php +++ b/src/Dataset/SvmDataset.php @@ -23,8 +23,8 @@ class SvmDataset extends ArrayDataset $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 @@ class SvmDataset extends ArrayDataset 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 29deb4c..41c7340 100644 --- a/src/DimensionReduction/KernelPCA.php +++ b/src/DimensionReduction/KernelPCA.php @@ -5,7 +5,6 @@ declare(strict_types=1); 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 @@ class KernelPCA extends PCA 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 @@ class KernelPCA extends PCA 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 fa651da..5556558 100644 --- a/src/DimensionReduction/PCA.php +++ b/src/DimensionReduction/PCA.php @@ -115,7 +115,7 @@ class PCA extends EigenTransformerBase */ 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 b426889..a054108 100644 --- a/src/Estimator.php +++ b/src/Estimator.php @@ -6,7 +6,7 @@ namespace Phpml; 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 4a478c3..d1ac35d 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 b0ff644..36b4245 100644 --- a/src/FeatureSelection/SelectKBest.php +++ b/src/FeatureSelection/SelectKBest.php @@ -43,7 +43,7 @@ final class SelectKBest implements Transformer 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 e68b10d..691fb64 100644 --- a/src/Helper/OneVsRest.php +++ b/src/Helper/OneVsRest.php @@ -51,18 +51,13 @@ trait OneVsRest 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 @@ trait OneVsRest 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 @@ trait OneVsRest /** * 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 @@ trait OneVsRest */ 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 67210ab..d7c064f 100644 --- a/src/Helper/Optimizer/ConjugateGradient.php +++ b/src/Helper/Optimizer/ConjugateGradient.php @@ -91,7 +91,7 @@ class ConjugateGradient extends GD { [$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 11577c9..2832032 100644 --- a/src/Helper/Optimizer/GD.php +++ b/src/Helper/Optimizer/GD.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Phpml\Helper\Optimizer; use Closure; +use Phpml\Exception\InvalidOperationException; /** * Batch version of Gradient Descent to optimize the weights @@ -59,6 +60,10 @@ class GD extends StochasticGD $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 dba0cd0..99a82ab 100644 --- a/src/Helper/Optimizer/Optimizer.php +++ b/src/Helper/Optimizer/Optimizer.php @@ -37,9 +37,9 @@ abstract class Optimizer } } - 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 @@ abstract class Optimizer * 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 c4fabd3..9927c3f 100644 --- a/src/Helper/Optimizer/StochasticGD.php +++ b/src/Helper/Optimizer/StochasticGD.php @@ -6,6 +6,7 @@ namespace Phpml\Helper\Optimizer; 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 @@ class StochasticGD extends Optimizer $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 @@ class StochasticGD extends Optimizer * 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 @@ class StochasticGD extends Optimizer // 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 @@ class StochasticGD extends Optimizer $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 @@ class StochasticGD extends Optimizer // 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 e356be0..600bfbb 100644 --- a/src/IncrementalEstimator.php +++ b/src/IncrementalEstimator.php @@ -6,5 +6,5 @@ namespace Phpml; 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 d9ad00c..d1330a9 100644 --- a/src/Math/Comparison.php +++ b/src/Math/Comparison.php @@ -9,6 +9,9 @@ use Phpml\Exception\InvalidArgumentException; 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 3d7484d..e0f1639 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 @@ use Phpml\Math\Matrix; 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 - */ - private $symmetric; - - /** - * Arrays for internal storage of eigenvalues. - * - * @var array + * @var array */ private $d = []; + /** + * @var array + */ 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 @@ class EigenvalueDecomposition } /** - * 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 @@ class EigenvalueDecomposition } /** - * 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 @@ class EigenvalueDecomposition } /** - * Return the block diagonal eigenvalue matrix + * Return the block diagonal eigenvalue matrix */ public function getDiagonalEigenvalues(): array { @@ -187,7 +183,7 @@ class EigenvalueDecomposition } /** - * Symmetric Householder reduction to tridiagonal form. + * Symmetric Householder reduction to tridiagonal form. */ private function tred2(): void { @@ -308,12 +304,12 @@ class EigenvalueDecomposition } /** - * 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 @@ class EigenvalueDecomposition // 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 @@ class EigenvalueDecomposition } /** - * 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 @@ class EigenvalueDecomposition } /** - * 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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition // 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 f9e7300..61f7c3f 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 @@ class LUDecomposition // 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 @@ class LUDecomposition * * @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 840d746..a511f55 100644 --- a/src/Math/Matrix.php +++ b/src/Math/Matrix.php @@ -89,7 +89,7 @@ class Matrix /** * @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 @@ class Matrix 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 @@ class Matrix 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 @@ class Matrix return new self($product, false); } + /** + * @param float|int $value + */ public function divideByScalar($value): self { $newMatrix = []; @@ -178,6 +181,9 @@ class Matrix 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 ab1e75a..78f3693 100644 --- a/src/Math/Product.php +++ b/src/Math/Product.php @@ -14,7 +14,7 @@ class Product $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 fab2923..b22d2f8 100644 --- a/src/Math/Set.php +++ b/src/Math/Set.php @@ -131,7 +131,7 @@ class Set implements IteratorAggregate */ public function containsAll(array $elements): bool { - return !array_diff($elements, $this->elements); + return count(array_diff($elements, $this->elements)) === 0; } /** @@ -149,7 +149,7 @@ class Set implements IteratorAggregate 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 0118347..f7b01c7 100644 --- a/src/Math/Statistic/ANOVA.php +++ b/src/Math/Statistic/ANOVA.php @@ -31,7 +31,7 @@ final class ANOVA $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 4ed0776..52cac5e 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 @@ class Covariance */ 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 @@ class Covariance // 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 6b6d555..2ae55aa 100644 --- a/src/Math/Statistic/Mean.php +++ b/src/Math/Statistic/Mean.php @@ -58,7 +58,7 @@ class Mean */ 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 f1eae8a..170a9ee 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 @@ class StandardDeviation --$n; } - return sqrt((float) ($carry / $n)); + return sqrt($carry / $n); } /** @@ -44,7 +43,7 @@ class StandardDeviation */ 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 969dcc6..6263a52 100644 --- a/src/Metric/ClassificationReport.php +++ b/src/Metric/ClassificationReport.php @@ -142,9 +142,9 @@ class ClassificationReport 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 @@ class ClassificationReport $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 5fd3ac5..5b8021c 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 36e8e2c..057e0ea 100644 --- a/src/ModelManager.php +++ b/src/ModelManager.php @@ -16,7 +16,7 @@ class ModelManager } $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 @@ class ModelManager 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 c2248a6..0b0ce65 100644 --- a/src/NeuralNetwork/Network.php +++ b/src/NeuralNetwork/Network.php @@ -13,7 +13,7 @@ interface Network 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 8ff49bc..7fe08e1 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 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, */ 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 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, /** * @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 08899bf..0a6e0f8 100644 --- a/src/NeuralNetwork/Node/Neuron/Synapse.php +++ b/src/NeuralNetwork/Node/Neuron/Synapse.php @@ -49,6 +49,6 @@ class Synapse 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 e699c47..0000000 --- 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 @@ class SupportVectorMachine 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 5b071b8..4c0ae60 100644 --- a/src/Tokenization/WhitespaceTokenizer.php +++ b/src/Tokenization/WhitespaceTokenizer.php @@ -4,10 +4,17 @@ declare(strict_types=1); 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 81a6ce6..bec58f9 100644 --- a/tests/Association/AprioriTest.php +++ b/tests/Association/AprioriTest.php @@ -11,6 +11,9 @@ use ReflectionClass; 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 @@ class AprioriTest extends TestCase $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 @@ class AprioriTest extends TestCase $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 @@ class AprioriTest extends TestCase $L = $apriori->apriori(); - $this->assertEmpty($L); + self::assertEmpty($L); } public function testAprioriSingleItem(): void @@ -97,8 +106,8 @@ class AprioriTest extends TestCase $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 @@ class AprioriTest extends TestCase $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 @@ class AprioriTest extends TestCase $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 @@ class AprioriTest extends TestCase $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 @@ class AprioriTest extends TestCase { $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 @@ class AprioriTest extends TestCase $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 @@ class AprioriTest extends TestCase $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 @@ class AprioriTest extends TestCase $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 @@ class AprioriTest extends TestCase $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 @@ class AprioriTest extends TestCase $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 @@ class AprioriTest extends TestCase $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 @@ class AprioriTest extends TestCase * * @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 e694878..05139ee 100644 --- a/tests/Classification/DecisionTree/DecisionTreeLeafTest.php +++ b/tests/Classification/DecisionTree/DecisionTreeLeafTest.php @@ -21,7 +21,10 @@ class DecisionTreeLeafTest extends TestCase $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 @@ class DecisionTreeLeafTest extends TestCase $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 @@ class DecisionTreeLeafTest extends TestCase $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 9478a2a..3f0a763 100644 --- a/tests/Classification/DecisionTreeTest.php +++ b/tests/Classification/DecisionTreeTest.php @@ -10,6 +10,9 @@ use PHPUnit\Framework\TestCase; 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 @@ class DecisionTreeTest extends TestCase $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 @@ class DecisionTreeTest extends TestCase [$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 095cde0..173df6c 100644 --- a/tests/Classification/Ensemble/AdaBoostTest.php +++ b/tests/Classification/Ensemble/AdaBoostTest.php @@ -37,27 +37,27 @@ class AdaBoostTest extends TestCase $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 @@ class AdaBoostTest extends TestCase $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 5b2e47b..9738ce7 100644 --- a/tests/Classification/Ensemble/BaggingTest.php +++ b/tests/Classification/Ensemble/BaggingTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); 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 @@ use PHPUnit\Framework\TestCase; 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 @@ class BaggingTest extends TestCase $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 @@ class BaggingTest extends TestCase $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 @@ class BaggingTest extends TestCase 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 @@ class BaggingTest extends TestCase return $classifier; } - protected function getAvailableBaseClassifiers() + protected function getAvailableBaseClassifiers(): array { return [ DecisionTree::class => ['depth' => 5], @@ -116,7 +126,7 @@ class BaggingTest extends TestCase ]; } - 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 93353a3..2f21c5c 100644 --- a/tests/Classification/Ensemble/RandomForestTest.php +++ b/tests/Classification/Ensemble/RandomForestTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); 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 @@ class RandomForestTest extends BaggingTest $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 @@ class RandomForestTest extends BaggingTest 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 110d49d..5be9a3d 100644 --- a/tests/Classification/KNearestNeighborsTest.php +++ b/tests/Classification/KNearestNeighborsTest.php @@ -19,15 +19,15 @@ class KNearestNeighborsTest extends TestCase $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])); + 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])); - $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('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 @@ class KNearestNeighborsTest extends TestCase $classifier->train($trainSamples, $trainLabels); $predicted = $classifier->predict($testSamples); - $this->assertEquals($testLabels, $predicted); + self::assertEquals($testLabels, $predicted); } public function testPredictArrayOfSamplesUsingChebyshevDistanceMetric(): void @@ -57,7 +57,7 @@ class KNearestNeighborsTest extends TestCase $classifier->train($trainSamples, $trainLabels); $predicted = $classifier->predict($testSamples); - $this->assertEquals($testLabels, $predicted); + self::assertEquals($testLabels, $predicted); } public function testSaveAndRestore(): void @@ -73,12 +73,12 @@ class KNearestNeighborsTest extends TestCase $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 08ad78a..7bc8f9d 100644 --- a/tests/Classification/Linear/AdalineTest.php +++ b/tests/Classification/Linear/AdalineTest.php @@ -31,18 +31,18 @@ class AdalineTest extends TestCase $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 @@ class AdalineTest extends TestCase $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 @@ class AdalineTest extends TestCase ]; $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 @@ class AdalineTest extends TestCase $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 1295ac5..7cd8250 100644 --- a/tests/Classification/Linear/DecisionStumpTest.php +++ b/tests/Classification/Linear/DecisionStumpTest.php @@ -23,7 +23,7 @@ class DecisionStumpTest extends TestCase $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 @@ class DecisionStumpTest extends TestCase $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 @@ class DecisionStumpTest extends TestCase $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 @@ class DecisionStumpTest extends TestCase $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 075e1ea..9573531 100644 --- a/tests/Classification/Linear/LogisticRegressionTest.php +++ b/tests/Classification/Linear/LogisticRegressionTest.php @@ -64,8 +64,8 @@ class LogisticRegressionTest extends TestCase $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 @@ class LogisticRegressionTest extends TestCase '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 @@ class LogisticRegressionTest extends TestCase '' ); $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 @@ class LogisticRegressionTest extends TestCase '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 @@ class LogisticRegressionTest extends TestCase '' ); $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 @@ class LogisticRegressionTest extends TestCase $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 @@ class LogisticRegressionTest extends TestCase $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 @@ class LogisticRegressionTest extends TestCase $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 a3958b8..fa118f3 100644 --- a/tests/Classification/Linear/PerceptronTest.php +++ b/tests/Classification/Linear/PerceptronTest.php @@ -33,9 +33,9 @@ class PerceptronTest extends TestCase $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 @@ class PerceptronTest extends TestCase $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 @@ class PerceptronTest extends TestCase $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 @@ class PerceptronTest extends TestCase ]; $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 @@ class PerceptronTest extends TestCase $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 ae0871d..2998ac9 100644 --- a/tests/Classification/MLPClassifierTest.php +++ b/tests/Classification/MLPClassifierTest.php @@ -21,21 +21,21 @@ class MLPClassifierTest extends TestCase { $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 @@ class MLPClassifierTest extends TestCase 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 @@ class MLPClassifierTest extends TestCase ['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 @@ class MLPClassifierTest extends TestCase ['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 @@ class MLPClassifierTest extends TestCase ['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 @@ class MLPClassifierTest extends TestCase ['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 @@ class MLPClassifierTest extends TestCase ['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 @@ class MLPClassifierTest extends TestCase ['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 @@ class MLPClassifierTest extends TestCase $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 @@ class MLPClassifierTest extends TestCase ['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 @@ class MLPClassifierTest extends TestCase ['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 @@ class MLPClassifierTest extends TestCase { $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 434c920..4e27261 100644 --- a/tests/Classification/NaiveBayesTest.php +++ b/tests/Classification/NaiveBayesTest.php @@ -18,9 +18,9 @@ class NaiveBayesTest extends TestCase $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 @@ class NaiveBayesTest extends TestCase $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 @@ class NaiveBayesTest extends TestCase $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 @@ class NaiveBayesTest extends TestCase $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 @@ class NaiveBayesTest extends TestCase $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 @@ class NaiveBayesTest extends TestCase $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 @@ class NaiveBayesTest extends TestCase $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 @@ class NaiveBayesTest extends TestCase $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 1ec1541..5fbcff8 100644 --- a/tests/Classification/SVCTest.php +++ b/tests/Classification/SVCTest.php @@ -19,15 +19,15 @@ class SVCTest extends TestCase $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])); + 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])); - $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('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 @@ class SVCTest extends TestCase $classifier->train($trainSamples, $trainLabels); $predictions = $classifier->predict($testSamples); - $this->assertEquals($testLabels, $predictions); + self::assertEquals($testLabels, $predictions); } public function testSaveAndRestore(): void @@ -57,14 +57,14 @@ class SVCTest extends TestCase $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 @@ class SVCTest extends TestCase $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 3c6d08d..1132203 100644 --- a/tests/Clustering/DBSCANTest.php +++ b/tests/Clustering/DBSCANTest.php @@ -19,7 +19,7 @@ class DBSCANTest extends TestCase $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 @@ class DBSCANTest extends TestCase $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 @@ class DBSCANTest extends TestCase $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 @@ class DBSCANTest extends TestCase $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 @@ class DBSCANTest extends TestCase $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 @@ class DBSCANTest extends TestCase $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 b2005a1..638e99c 100644 --- a/tests/Clustering/FuzzyCMeansTest.php +++ b/tests/Clustering/FuzzyCMeansTest.php @@ -10,40 +10,40 @@ use PHPUnit\Framework\TestCase; 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 80b9837..2b57d0b 100644 --- a/tests/Clustering/KMeans/ClusterTest.php +++ b/tests/Clustering/KMeans/ClusterTest.php @@ -26,7 +26,7 @@ class ClusterTest extends TestCase $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 @@ class ClusterTest extends TestCase $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 ba36bc6..0265f7d 100644 --- a/tests/Clustering/KMeansTest.php +++ b/tests/Clustering/KMeansTest.php @@ -17,7 +17,7 @@ class KMeansTest extends TestCase $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 @@ class KMeansTest extends TestCase } } - $this->assertCount(0, $samples); + self::assertCount(0, $samples); } public function testKMeansSamplesLabeledClustering(): void @@ -42,16 +42,16 @@ class KMeansTest extends TestCase $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 @@ class KMeansTest extends TestCase $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 65e8d8b..88928cc 100644 --- a/tests/CrossValidation/RandomSplitTest.php +++ b/tests/CrossValidation/RandomSplitTest.php @@ -32,13 +32,13 @@ class RandomSplitTest extends TestCase $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 @@ class RandomSplitTest extends TestCase $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 @@ class RandomSplitTest extends TestCase $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 @@ class RandomSplitTest extends TestCase $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 5309dc6..909f15f 100644 --- a/tests/CrossValidation/StratifiedRandomSplitTest.php +++ b/tests/CrossValidation/StratifiedRandomSplitTest.php @@ -19,13 +19,13 @@ class StratifiedRandomSplitTest extends TestCase $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 @@ class StratifiedRandomSplitTest extends TestCase $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 0431959..98792c7 100644 --- a/tests/Dataset/ArrayDatasetTest.php +++ b/tests/Dataset/ArrayDatasetTest.php @@ -23,8 +23,8 @@ class ArrayDatasetTest extends TestCase $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 @@ class ArrayDatasetTest extends TestCase ); $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 90eb1d0..a178726 100644 --- a/tests/Dataset/CsvDatasetTest.php +++ b/tests/Dataset/CsvDatasetTest.php @@ -22,8 +22,8 @@ class CsvDatasetTest extends TestCase $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 @@ class CsvDatasetTest extends TestCase $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 @@ class CsvDatasetTest extends TestCase $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 0d873e6..3ef182a 100644 --- a/tests/Dataset/Demo/GlassDatasetTest.php +++ b/tests/Dataset/Demo/GlassDatasetTest.php @@ -14,10 +14,10 @@ class GlassDatasetTest extends TestCase $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 4fb2ee2..171bc38 100644 --- a/tests/Dataset/Demo/IrisDatasetTest.php +++ b/tests/Dataset/Demo/IrisDatasetTest.php @@ -14,10 +14,10 @@ class IrisDatasetTest extends TestCase $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 1b7a982..0119294 100644 --- a/tests/Dataset/Demo/WineDatasetTest.php +++ b/tests/Dataset/Demo/WineDatasetTest.php @@ -14,10 +14,10 @@ class WineDatasetTest extends TestCase $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 08b568d..ec30f91 100644 --- a/tests/Dataset/FilesDatasetTest.php +++ b/tests/Dataset/FilesDatasetTest.php @@ -22,22 +22,22 @@ class FilesDatasetTest extends TestCase $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 700055c..437e3d2 100644 --- a/tests/Dataset/SvmDatasetTest.php +++ b/tests/Dataset/SvmDatasetTest.php @@ -21,8 +21,8 @@ class SvmDatasetTest extends TestCase $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 @@ class SvmDatasetTest extends TestCase 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 @@ class SvmDatasetTest extends TestCase 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 @@ class SvmDatasetTest extends TestCase 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 @@ class SvmDatasetTest extends TestCase 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 @@ class SvmDatasetTest extends TestCase 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 @@ class SvmDatasetTest extends TestCase 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 cca0d53..cd04260 100644 --- a/tests/DimensionReduction/KernelPCATest.php +++ b/tests/DimensionReduction/KernelPCATest.php @@ -40,7 +40,7 @@ class KernelPCATest extends TestCase // 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 @@ class KernelPCATest extends TestCase $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 79e925f..101d2f9 100644 --- a/tests/DimensionReduction/LDATest.php +++ b/tests/DimensionReduction/LDATest.php @@ -51,7 +51,7 @@ class LDATest extends TestCase // 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 ba25268..ebb5b01 100644 --- a/tests/DimensionReduction/PCATest.php +++ b/tests/DimensionReduction/PCATest.php @@ -42,7 +42,7 @@ class PCATest extends TestCase // 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 @@ class PCATest extends TestCase $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 5780c39..112fa0f 100644 --- a/tests/FeatureExtraction/StopWordsTest.php +++ b/tests/FeatureExtraction/StopWordsTest.php @@ -14,13 +14,13 @@ class StopWordsTest extends TestCase { $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 @@ class StopWordsTest extends TestCase { $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 30bc0e5..b0cf9f6 100644 --- a/tests/FeatureExtraction/TfIdfTransformerTest.php +++ b/tests/FeatureExtraction/TfIdfTransformerTest.php @@ -54,6 +54,6 @@ class TfIdfTransformerTest extends TestCase $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 aaba5fc..dff9436 100644 --- a/tests/FeatureExtraction/TokenCountVectorizerTest.php +++ b/tests/FeatureExtraction/TokenCountVectorizerTest.php @@ -74,10 +74,10 @@ class TokenCountVectorizerTest extends TestCase $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 @@ class TokenCountVectorizerTest extends TestCase $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 @@ class TokenCountVectorizerTest extends TestCase $vectorizer->fit($samples); $vectorizer->transform($samples); - $this->assertSame($tokensCounts, $samples); + self::assertSame($tokensCounts, $samples); } public function testTransformationWithStopWords(): void @@ -246,9 +246,9 @@ class TokenCountVectorizerTest extends TestCase $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 09c250c..2080019 100644 --- a/tests/Helper/Optimizer/ConjugateGradientTest.php +++ b/tests/Helper/Optimizer/ConjugateGradientTest.php @@ -33,7 +33,7 @@ class ConjugateGradientTest extends TestCase $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 @@ class ConjugateGradientTest extends TestCase $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 @@ class ConjugateGradientTest extends TestCase $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 c68e318..ecbd515 100644 --- a/tests/Helper/Optimizer/GDTest.php +++ b/tests/Helper/Optimizer/GDTest.php @@ -32,7 +32,7 @@ class GDTest extends TestCase $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 @@ class GDTest extends TestCase $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 22efdd2..97af2d2 100644 --- a/tests/Helper/Optimizer/OptimizerTest.php +++ b/tests/Helper/Optimizer/OptimizerTest.php @@ -26,9 +26,9 @@ class OptimizerTest extends TestCase $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 6f6e469..ba3430b 100644 --- a/tests/Helper/Optimizer/StochasticGDTest.php +++ b/tests/Helper/Optimizer/StochasticGDTest.php @@ -32,7 +32,7 @@ class StochasticGDTest extends TestCase $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 @@ class StochasticGDTest extends TestCase $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 0118ee4..338055b 100644 --- a/tests/Math/ComparisonTest.php +++ b/tests/Math/ComparisonTest.php @@ -20,7 +20,7 @@ class ComparisonTest extends TestCase { $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 1a43731..a59edbf 100644 --- a/tests/Math/Distance/ChebyshevTest.php +++ b/tests/Math/Distance/ChebyshevTest.php @@ -36,7 +36,7 @@ class ChebyshevTest extends TestCase $expectedDistance = 2; $actualDistance = $this->distanceMetric->distance($a, $b); - $this->assertEquals($expectedDistance, $actualDistance); + self::assertEquals($expectedDistance, $actualDistance); } public function testCalculateDistanceForTwoDimensions(): void @@ -47,7 +47,7 @@ class ChebyshevTest extends TestCase $expectedDistance = 2; $actualDistance = $this->distanceMetric->distance($a, $b); - $this->assertEquals($expectedDistance, $actualDistance); + self::assertEquals($expectedDistance, $actualDistance); } public function testCalculateDistanceForThreeDimensions(): void @@ -58,6 +58,6 @@ class ChebyshevTest extends TestCase $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 4be96d3..979a378 100644 --- a/tests/Math/Distance/EuclideanTest.php +++ b/tests/Math/Distance/EuclideanTest.php @@ -36,7 +36,7 @@ class EuclideanTest extends TestCase $expectedDistance = 2; $actualDistance = $this->distanceMetric->distance($a, $b); - $this->assertEquals($expectedDistance, $actualDistance); + self::assertEquals($expectedDistance, $actualDistance); } public function testCalculateDistanceForTwoDimensions(): void @@ -47,7 +47,7 @@ class EuclideanTest extends TestCase $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 @@ class EuclideanTest extends TestCase $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 1dd5e46..c189f26 100644 --- a/tests/Math/Distance/ManhattanTest.php +++ b/tests/Math/Distance/ManhattanTest.php @@ -36,7 +36,7 @@ class ManhattanTest extends TestCase $expectedDistance = 2; $actualDistance = $this->distanceMetric->distance($a, $b); - $this->assertEquals($expectedDistance, $actualDistance); + self::assertEquals($expectedDistance, $actualDistance); } public function testCalculateDistanceForTwoDimensions(): void @@ -47,7 +47,7 @@ class ManhattanTest extends TestCase $expectedDistance = 3; $actualDistance = $this->distanceMetric->distance($a, $b); - $this->assertEquals($expectedDistance, $actualDistance); + self::assertEquals($expectedDistance, $actualDistance); } public function testCalculateDistanceForThreeDimensions(): void @@ -58,6 +58,6 @@ class ManhattanTest extends TestCase $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 558c31f..770bf15 100644 --- a/tests/Math/Distance/MinkowskiTest.php +++ b/tests/Math/Distance/MinkowskiTest.php @@ -36,7 +36,7 @@ class MinkowskiTest extends TestCase $expectedDistance = 2; $actualDistance = $this->distanceMetric->distance($a, $b); - $this->assertEquals($expectedDistance, $actualDistance); + self::assertEquals($expectedDistance, $actualDistance); } public function testCalculateDistanceForTwoDimensions(): void @@ -47,7 +47,7 @@ class MinkowskiTest extends TestCase $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 @@ class MinkowskiTest extends TestCase $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 @@ class MinkowskiTest extends TestCase $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 08f795d..bf24aab 100644 --- a/tests/Math/Kernel/RBFTest.php +++ b/tests/Math/Kernel/RBFTest.php @@ -13,14 +13,14 @@ class RBFTest extends TestCase { $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 ea96f77..bd81e2e 100644 --- a/tests/Math/LinearAlgebra/LUDecompositionTest.php +++ b/tests/Math/LinearAlgebra/LUDecompositionTest.php @@ -34,8 +34,7 @@ final class LUDecompositionTest extends TestCase $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 @@ final class LUDecompositionTest extends TestCase $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 7adde6c..94d47e2 100644 --- a/tests/Math/MatrixTest.php +++ b/tests/Math/MatrixTest.php @@ -22,11 +22,10 @@ class MatrixTest extends TestCase $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 @@ class MatrixTest extends TestCase [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 @@ class MatrixTest extends TestCase [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 @@ class MatrixTest extends TestCase [3, 1, 7], ]; - $this->assertEquals($transposedMatrix, $matrix->transpose()->toArray()); + self::assertEquals($transposedMatrix, $matrix->transpose()->toArray()); } public function testThrowExceptionOnMultiplyWhenInconsistentMatrixSupplied(): void @@ -107,7 +106,7 @@ class MatrixTest extends TestCase [139, 154], ]; - $this->assertEquals($product, $matrix1->multiply($matrix2)->toArray()); + self::assertEquals($product, $matrix1->multiply($matrix2)->toArray()); } public function testDivideByScalar(): void @@ -122,7 +121,7 @@ class MatrixTest extends TestCase [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 @@ class MatrixTest extends TestCase [-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 @@ class MatrixTest extends TestCase [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 @@ class MatrixTest extends TestCase [-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 @@ class MatrixTest extends TestCase $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 @@ class MatrixTest extends TestCase $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 @@ class MatrixTest extends TestCase [1, 2], ]; - $this->assertEquals($transposed, Matrix::transposeArray($array)); + self::assertEquals($transposed, Matrix::transposeArray($array)); } public function testDot(): void @@ -244,12 +243,12 @@ class MatrixTest extends TestCase $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 @@ class MatrixTest extends TestCase */ 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 da7450b..4eaff58 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 2335c73..1f31156 100644 --- a/tests/Math/SetTest.php +++ b/tests/Math/SetTest.php @@ -13,84 +13,79 @@ class SetTest extends TestCase { $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 eebb065..2d0334d 100644 --- a/tests/Math/Statistic/CorrelationTest.php +++ b/tests/Math/Statistic/CorrelationTest.php @@ -16,18 +16,18 @@ class CorrelationTest extends TestCase $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 fd7187a..dd98aea 100644 --- a/tests/Math/Statistic/CovarianceTest.php +++ b/tests/Math/Statistic/CovarianceTest.php @@ -38,18 +38,18 @@ class CovarianceTest extends TestCase // 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 @@ class CovarianceTest extends TestCase $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 e4627f4..b19c8db 100644 --- a/tests/Math/Statistic/GaussianTest.php +++ b/tests/Math/Statistic/GaussianTest.php @@ -20,9 +20,9 @@ class GaussianTest extends TestCase $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 3b98032..6e5d8d7 100644 --- a/tests/Math/Statistic/MeanTest.php +++ b/tests/Math/Statistic/MeanTest.php @@ -19,9 +19,9 @@ class MeanTest extends TestCase 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 @@ class MeanTest extends TestCase { $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 @@ class MeanTest extends TestCase { $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 c6fd47e..e18c374 100644 --- a/tests/Math/Statistic/StandardDeviationTest.php +++ b/tests/Math/Statistic/StandardDeviationTest.php @@ -15,15 +15,15 @@ class StandardDeviationTest extends TestCase //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 19b2cd8..310acb6 100644 --- a/tests/Math/Statistic/VarianceTest.php +++ b/tests/Math/Statistic/VarianceTest.php @@ -17,7 +17,7 @@ final class VarianceTest extends TestCase 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 e2260c4..792dd2f 100644 --- a/tests/Metric/AccuracyTest.php +++ b/tests/Metric/AccuracyTest.php @@ -27,7 +27,7 @@ class AccuracyTest extends TestCase $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 @@ class AccuracyTest extends TestCase $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 @@ class AccuracyTest extends TestCase $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 fa9080b..3258bc1 100644 --- a/tests/Metric/ClassificationReportTest.php +++ b/tests/Metric/ClassificationReportTest.php @@ -45,11 +45,11 @@ class ClassificationReportTest extends TestCase '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 @@ class ClassificationReportTest extends TestCase '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 @@ class ClassificationReportTest extends TestCase '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 @@ class ClassificationReportTest extends TestCase '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 @@ class ClassificationReportTest extends TestCase '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 @@ class ClassificationReportTest extends TestCase $report = new ClassificationReport($labels, $predicted); - $this->assertEquals([ + self::assertEquals([ 1 => 0.0, 2 => 0.5, ], $report->getPrecision(), '', 0.01); @@ -169,7 +169,7 @@ class ClassificationReportTest extends TestCase $report = new ClassificationReport($labels, $predicted); - $this->assertEquals([ + self::assertEquals([ 1 => 0.0, 2 => 1, 3 => 0, @@ -183,7 +183,7 @@ class ClassificationReportTest extends TestCase $report = new ClassificationReport($labels, $predicted); - $this->assertEquals([ + self::assertEquals([ 'precision' => 0, 'recall' => 0, 'f1score' => 0, @@ -197,7 +197,7 @@ class ClassificationReportTest extends TestCase $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 590aff8..36518a3 100644 --- a/tests/Metric/ConfusionMatrixTest.php +++ b/tests/Metric/ConfusionMatrixTest.php @@ -20,7 +20,7 @@ class ConfusionMatrixTest extends TestCase [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 @@ class ConfusionMatrixTest extends TestCase [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 @@ class ConfusionMatrixTest extends TestCase [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 @@ class ConfusionMatrixTest extends TestCase [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 48ab747..fab34d7 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 @@ class ModelManagerTest extends TestCase $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 4e85478..699c708 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 @@ class BinaryStepTest extends TestCase /** * @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 aace8bc..6876fd8 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 @@ class GaussianTest extends TestCase /** * @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 629200e..a6a244f 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 @@ class HyperboliTangentTest extends TestCase /** * @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 c9f565d..a659204 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 @@ class PReLUTest extends TestCase /** * @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 1028fb3..d98b39c 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 @@ class SigmoidTest extends TestCase /** * @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 4db0418..19b0039 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 @@ class ThresholdedReLUTest extends TestCase /** * @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 baa3f9a..87809b8 100644 --- a/tests/NeuralNetwork/LayerTest.php +++ b/tests/NeuralNetwork/LayerTest.php @@ -17,16 +17,16 @@ class LayerTest extends TestCase { $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 @@ class LayerTest extends TestCase { $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 @@ class LayerTest extends TestCase $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 2a7e696..a865831 100644 --- a/tests/NeuralNetwork/Network/LayeredNetworkTest.php +++ b/tests/NeuralNetwork/Network/LayeredNetworkTest.php @@ -20,7 +20,7 @@ class LayeredNetworkTest extends TestCase $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 @@ class LayeredNetworkTest extends TestCase $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 @@ class LayeredNetworkTest extends TestCase $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 @@ class LayeredNetworkTest extends TestCase $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 cea2a2f..d7bf7e5 100644 --- a/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php +++ b/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php @@ -55,14 +55,14 @@ class MultilayerPerceptronTest extends TestCase [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 @@ class MultilayerPerceptronTest extends TestCase [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 @@ class MultilayerPerceptronTest extends TestCase [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 e42a737..bb9650c 100644 --- a/tests/NeuralNetwork/Node/BiasTest.php +++ b/tests/NeuralNetwork/Node/BiasTest.php @@ -13,6 +13,6 @@ class BiasTest extends TestCase { $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 09ca831..8304d3e 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 @@ class InputTest extends TestCase $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 973d216..1e33f34 100644 --- a/tests/NeuralNetwork/Node/Neuron/SynapseTest.php +++ b/tests/NeuralNetwork/Node/Neuron/SynapseTest.php @@ -17,13 +17,14 @@ class SynapseTest extends TestCase $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 @@ class SynapseTest extends TestCase $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 03e309d..b1a77a8 100644 --- a/tests/NeuralNetwork/Node/NeuronTest.php +++ b/tests/NeuralNetwork/Node/NeuronTest.php @@ -16,8 +16,8 @@ class NeuronTest extends TestCase { $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 @@ class NeuronTest extends TestCase $neuron = new Neuron($activationFunction); - $this->assertEquals($output, $neuron->getOutput()); + self::assertEquals($output, $neuron->getOutput()); } public function testNeuronWithSynapse(): void @@ -36,8 +36,8 @@ class NeuronTest extends TestCase $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 @@ class NeuronTest extends TestCase $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 d72e0c8..0ba91c6 100644 --- a/tests/PipelineTest.php +++ b/tests/PipelineTest.php @@ -28,8 +28,8 @@ class PipelineTest extends TestCase $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 @@ class PipelineTest extends TestCase $estimator = new SVR(); $pipeline->setEstimator($estimator); - $this->assertEquals($estimator, $pipeline->getEstimator()); + self::assertEquals($estimator, $pipeline->getEstimator()); } public function testPipelineWorkflow(): void @@ -67,7 +67,7 @@ class PipelineTest extends TestCase $predicted = $pipeline->predict([[0, 0, 0]]); - $this->assertEquals(4, $predicted[0]); + self::assertEquals(4, $predicted[0]); } public function testPipelineTransformers(): void @@ -104,7 +104,7 @@ class PipelineTest extends TestCase $predicted = $pipeline->predict(['Hello Max', 'Goodbye Mark']); - $this->assertEquals($expected, $predicted); + self::assertEquals($expected, $predicted); } public function testPipelineTransformersWithTargets(): void @@ -145,13 +145,13 @@ class PipelineTest extends TestCase $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 1078e54..dcbb807 100644 --- a/tests/Preprocessing/ImputerTest.php +++ b/tests/Preprocessing/ImputerTest.php @@ -32,7 +32,7 @@ class ImputerTest extends TestCase $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 @@ class ImputerTest extends TestCase $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 @@ class ImputerTest extends TestCase $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 @@ class ImputerTest extends TestCase $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 @@ class ImputerTest extends TestCase $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 @@ class ImputerTest extends TestCase $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 @@ class ImputerTest extends TestCase $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 ea762da..53b07d8 100644 --- a/tests/Preprocessing/NormalizerTest.php +++ b/tests/Preprocessing/NormalizerTest.php @@ -33,7 +33,7 @@ class NormalizerTest extends TestCase $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 @@ class NormalizerTest extends TestCase $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 @@ class NormalizerTest extends TestCase $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 @@ class NormalizerTest extends TestCase $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 @@ class NormalizerTest extends TestCase $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 @@ class NormalizerTest extends TestCase 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 71215bc..1142a37 100644 --- a/tests/Regression/LeastSquaresTest.php +++ b/tests/Regression/LeastSquaresTest.php @@ -21,7 +21,7 @@ class LeastSquaresTest extends TestCase $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 @@ class LeastSquaresTest extends TestCase $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 @@ class LeastSquaresTest extends TestCase $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 @@ class LeastSquaresTest extends TestCase $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 @@ class LeastSquaresTest extends TestCase $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 d2fdefb..89099c0 100644 --- a/tests/Regression/SVRTest.php +++ b/tests/Regression/SVRTest.php @@ -21,7 +21,7 @@ class SVRTest extends TestCase $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 @@ class SVRTest extends TestCase $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 @@ class SVRTest extends TestCase $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 df29806..32d7d32 100644 --- a/tests/SupportVectorMachine/DataTransformerTest.php +++ b/tests/SupportVectorMachine/DataTransformerTest.php @@ -22,7 +22,7 @@ class DataTransformerTest extends TestCase '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 @@ class DataTransformerTest extends TestCase '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 @@ class DataTransformerTest extends TestCase $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 @@ class DataTransformerTest extends TestCase ], ]; - $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 088d37b..e3bbc85 100644 --- a/tests/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/SupportVectorMachine/SupportVectorMachineTest.php @@ -35,7 +35,7 @@ SV $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 @@ SV ); $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 @@ SV [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 @@ SV [-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 @@ SV [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 03e8f7e..d6dd4cf 100644 --- a/tests/Tokenization/WhitespaceTokenizerTest.php +++ b/tests/Tokenization/WhitespaceTokenizerTest.php @@ -21,7 +21,7 @@ class WhitespaceTokenizerTest extends TestCase '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 @@ class WhitespaceTokenizerTest extends TestCase '剆坲', '煘煓瑐', '鬐鶤鶐', '飹勫嫢', '銪', '餀', '枲柊氠', '鍎鞚韕', '焲犈,', '殍涾烰', '齞齝囃', '蹅輶', '鄜,', '孻憵', '擙樲橚', '藒襓謥', '岯岪弨', '蒮', '廞徲', '孻憵懥', '趡趛踠', '槏', ]; - $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 387e0f8..39448b7 100644 --- a/tests/Tokenization/WordTokenizerTest.php +++ b/tests/Tokenization/WordTokenizerTest.php @@ -21,7 +21,7 @@ class WordTokenizerTest extends TestCase '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 @@ class WordTokenizerTest extends TestCase '剆坲', '煘煓瑐', '鬐鶤鶐', '飹勫嫢', '枲柊氠', '鍎鞚韕', '焲犈', '殍涾烰', '齞齝囃', '蹅輶', '孻憵', '擙樲橚', '藒襓謥', '岯岪弨', '廞徲', '孻憵懥', '趡趛踠', ]; - $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 fa73e4b..f4c3d3e 100644 --- a/src/Clustering/KMeans/Cluster.php +++ b/src/Clustering/KMeans/Cluster.php @@ -31,7 +31,7 @@ class Cluster extends Point implements IteratorAggregate { $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 58ccbc6..77f956c 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 d93996f..f518fd0 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 12cbbd5..3c6ede2 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 0000000..1ed5081 --- /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 490e5dc..451d6e9 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 7a676fa..0ee43c4 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 0000000..59a3a26 --- /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 0000000..5fc7374 --- /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 f14ada7..45075a4 100644 --- a/src/Classification/NaiveBayes.php +++ b/src/Classification/NaiveBayes.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Phpml\Classification; +use Phpml\Exception\InvalidArgumentException; use Phpml\Helper\Predictable; use Phpml\Helper\Trainable; use Phpml\Math\Statistic\Mean; @@ -137,6 +138,10 @@ class NaiveBayes implements Classifier */ 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 4e27261..076a70d 100644 --- a/tests/Classification/NaiveBayesTest.php +++ b/tests/Classification/NaiveBayesTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Phpml\Tests\Classification; use Phpml\Classification\NaiveBayes; +use Phpml\Exception\InvalidArgumentException; use Phpml\ModelManager; use PHPUnit\Framework\TestCase; @@ -125,4 +126,19 @@ class NaiveBayesTest extends TestCase 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 70324ee..e0a85c6 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 e0a91da..e0669d3 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 1c1e097..7e00fb1 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 | 508 +++++++++++------- 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, 342 insertions(+), 198 deletions(-) diff --git a/composer.lock b/composer.lock index e0a85c6..e5eee3c 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/debug", - "version": "v4.1.7", + "name": "symfony/contracts", + "version": "v1.0.2", "source": { "type": "git", - "url": "https://github.com/symfony/debug.git", - "reference": "19090917b848a799cbae4800abf740fe4eb71c1d" + "url": "https://github.com/symfony/contracts.git", + "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/19090917b848a799cbae4800abf740fe4eb71c1d", - "reference": "19090917b848a799cbae4800abf740fe4eb71c1d", + "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.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "e0a2b92ee0b5b934f973d90c2f58e18af109d276" + }, + "dist": { + "type": "zip", + "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/yaml", - "version": "v4.1.7", + "name": "symfony/var-exporter", + "version": "v4.2.1", "source": { "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "367e689b2fdc19965be435337b50bc8adf2746c9" + "url": "https://github.com/symfony/var-exporter.git", + "reference": "a39222e357362424b61dcde50e2f7b5a7d3306db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/367e689b2fdc19965be435337b50bc8adf2746c9", - "reference": "367e689b2fdc19965be435337b50bc8adf2746c9", + "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.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "c41175c801e3edfda90f32e292619d10c27103d7" + }, + "dist": { + "type": "zip", + "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 26cc7a6..2c9010d 100644 --- a/src/Classification/Ensemble/Bagging.php +++ b/src/Classification/Ensemble/Bagging.php @@ -157,7 +157,7 @@ class Bagging implements Classifier { $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 c0f0dd8..ac1304c 100644 --- a/src/Classification/Ensemble/RandomForest.php +++ b/src/Classification/Ensemble/RandomForest.php @@ -86,7 +86,7 @@ class RandomForest extends Bagging // 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 db42fe9..3637ef6 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 566d691..f9f57f5 100644 --- a/src/Clustering/KMeans/Space.php +++ b/src/Clustering/KMeans/Space.php @@ -116,7 +116,7 @@ class Space extends SplObjectStorage } /** - * @return array|Cluster[] + * @return Cluster[] */ public function cluster(int $clustersNumber, int $initMethod = KMeans::INIT_RANDOM): array { @@ -129,7 +129,7 @@ class Space extends SplObjectStorage } /** - * @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 4974d4c..3b3acc4 100644 --- a/src/CrossValidation/StratifiedRandomSplit.php +++ b/src/CrossValidation/StratifiedRandomSplit.php @@ -19,7 +19,7 @@ class StratifiedRandomSplit extends RandomSplit } /** - * @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 f7b01c7..d233f84 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 7039e39..6878388 100644 --- a/src/Math/Statistic/Correlation.php +++ b/src/Math/Statistic/Correlation.php @@ -9,8 +9,8 @@ use Phpml\Exception\InvalidArgumentException; 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 170a9ee..a9724d1 100644 --- a/src/Math/Statistic/StandardDeviation.php +++ b/src/Math/Statistic/StandardDeviation.php @@ -9,7 +9,7 @@ use Phpml\Exception\InvalidArgumentException; 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 @@ class StandardDeviation * 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 d57da87..41188f3 100644 --- a/src/Pipeline.php +++ b/src/Pipeline.php @@ -7,7 +7,7 @@ namespace Phpml; 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 @@ class Pipeline implements Estimator } /** - * @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 77f956c..68ef44b 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 e185bd3..0000000 --- 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 e5eee3c..fd930b9 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 410fd45..af3b357 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 0ccd29a..3c7dbc2 100644 --- a/src/Math/Distance/Chebyshev.php +++ b/src/Math/Distance/Chebyshev.php @@ -4,27 +4,16 @@ declare(strict_types=1); 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 0000000..ad9cdb9 --- /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 4f437dc..4b7abc4 100644 --- a/src/Math/Distance/Euclidean.php +++ b/src/Math/Distance/Euclidean.php @@ -4,31 +4,25 @@ declare(strict_types=1); 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 459a5ec..21ddee2 100644 --- a/src/Math/Distance/Manhattan.php +++ b/src/Math/Distance/Manhattan.php @@ -4,22 +4,18 @@ declare(strict_types=1); 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 36edf9b..0ed5829 100644 --- a/src/Math/Distance/Minkowski.php +++ b/src/Math/Distance/Minkowski.php @@ -4,37 +4,11 @@ declare(strict_types=1); 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 d8010f0..04cde56 100644 --- a/src/Classification/DecisionTree.php +++ b/src/Classification/DecisionTree.php @@ -137,7 +137,7 @@ class DecisionTree implements Classifier $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 ac1304c..b75d7ae 100644 --- a/src/Classification/Ensemble/RandomForest.php +++ b/src/Classification/Ensemble/RandomForest.php @@ -131,7 +131,7 @@ class RandomForest extends Bagging 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 45075a4..079b6f7 100644 --- a/src/Classification/NaiveBayes.php +++ b/src/Classification/NaiveBayes.php @@ -162,7 +162,7 @@ class NaiveBayes implements Classifier // 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 f6ad3f5..a3f195d 100644 --- a/src/Clustering/KMeans/Point.php +++ b/src/Clustering/KMeans/Point.php @@ -49,7 +49,7 @@ class Point implements ArrayAccess, \Countable $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 e0f1639..cc68864 100644 --- a/src/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -128,12 +128,13 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition // 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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $h += $this->ort[$i] * $this->ort[$i]; } - $g = sqrt($h); + $g = $h ** .5; if ($this->ort[$m] > 0) { $g *= -1; } @@ -548,7 +549,7 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition $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 @@ class EigenvalueDecomposition 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 a511f55..db6be42 100644 --- a/src/Math/Matrix.php +++ b/src/Math/Matrix.php @@ -271,7 +271,7 @@ class Matrix } } - return sqrt($squareSum); + return $squareSum ** .5; } /** diff --git a/src/Math/Statistic/Correlation.php b/src/Math/Statistic/Correlation.php index 6878388..c730c47 100644 --- a/src/Math/Statistic/Correlation.php +++ b/src/Math/Statistic/Correlation.php @@ -32,10 +32,10 @@ class Correlation $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 ff8470c..649063d 100644 --- a/src/Math/Statistic/Gaussian.php +++ b/src/Math/Statistic/Gaussian.php @@ -34,7 +34,7 @@ class Gaussian $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 a9724d1..50effab 100644 --- a/src/Math/Statistic/StandardDeviation.php +++ b/src/Math/Statistic/StandardDeviation.php @@ -32,7 +32,7 @@ class StandardDeviation --$n; } - return sqrt($carry / $n); + return ($carry / $n) ** .5; } /** diff --git a/src/NeuralNetwork/ActivationFunction/Gaussian.php b/src/NeuralNetwork/ActivationFunction/Gaussian.php index 8871b58..29cfef2 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 7aa9614..c230aff 100644 --- a/src/NeuralNetwork/ActivationFunction/HyperbolicTangent.php +++ b/src/NeuralNetwork/ActivationFunction/HyperbolicTangent.php @@ -32,6 +32,6 @@ class HyperbolicTangent implements ActivationFunction */ 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 fb14ada..9888e0e 100644 --- a/src/Preprocessing/Normalizer.php +++ b/src/Preprocessing/Normalizer.php @@ -106,7 +106,7 @@ class Normalizer implements Preprocessor $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 0000000..173228f --- /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 f518fd0..4df5730 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 c4ede68..8e2e9fd 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 0000000..59e6f25 --- /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 0000000..f99128d --- /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 0000000..2df9531 --- /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 0000000..5d0833c --- /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 39448b7..9c55dd6 100644 --- a/tests/Tokenization/WordTokenizerTest.php +++ b/tests/Tokenization/WordTokenizerTest.php @@ -5,37 +5,28 @@ declare(strict_types=1); 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 7e00fb1..63507f0 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 a159753..daa7192 100644 --- a/src/Dataset/FilesDataset.php +++ b/src/Dataset/FilesDataset.php @@ -29,7 +29,7 @@ class FilesDataset extends ArrayDataset $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 a1e38f4..afd5f33 100644 --- a/src/FeatureExtraction/TokenCountVectorizer.php +++ b/src/FeatureExtraction/TokenCountVectorizer.php @@ -157,7 +157,7 @@ class TokenCountVectorizer implements Transformer $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 ec30f91..a7ecd97 100644 --- a/tests/Dataset/FilesDatasetTest.php +++ b/tests/Dataset/FilesDatasetTest.php @@ -29,13 +29,13 @@ class FilesDatasetTest extends TestCase 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 dff9436..1347915 100644 --- a/tests/FeatureExtraction/TokenCountVectorizerTest.php +++ b/tests/FeatureExtraction/TokenCountVectorizerTest.php @@ -84,7 +84,7 @@ class TokenCountVectorizerTest extends TestCase { // 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 @@ class TokenCountVectorizerTest extends TestCase 2 => 'dolor', 3 => 'sit', 4 => 'amet', + 5 => 1550, ]; $tokensCounts = [ @@ -105,6 +106,7 @@ class TokenCountVectorizerTest extends TestCase 2 => 0, 3 => 1, 4 => 1, + 5 => 0, ], [ 0 => 1, @@ -112,6 +114,7 @@ class TokenCountVectorizerTest extends TestCase 2 => 0, 3 => 1, 4 => 1, + 5 => 0, ], [ 0 => 0, @@ -119,6 +122,7 @@ class TokenCountVectorizerTest extends TestCase 2 => 0, 3 => 1, 4 => 1, + 5 => 0, ], [ 0 => 0, @@ -126,6 +130,7 @@ class TokenCountVectorizerTest extends TestCase 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 | 1048 ++++++++++------- ecs.yml | 16 +- 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, 652 insertions(+), 495 deletions(-) diff --git a/composer.json b/composer.json index 1604bbf..8b3b158 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 fd930b9..62a34e0 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", @@ -237,28 +237,97 @@ "time": "2017-12-06T07:11:42+00:00" }, { - "name": "doctrine/instantiator", - "version": "1.1.0", + "name": "doctrine/inflector", + "version": "v1.3.0", "source": { "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" + "url": "https://github.com/doctrine/inflector.git", + "reference": "5527a48b7313d15261292c149e55e26eae771b0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/5527a48b7313d15261292c149e55e26eae771b0a", + "reference": "5527a48b7313d15261292c149e55e26eae771b0a", "shasum": "" }, "require": { "php": "^7.1" }, "require-dev": { - "athletic/athletic": "~0.1.8", + "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.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "a2c590166b2133a4633738648b6b064edae0814a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", + "reference": "a2c590166b2133a4633738648b6b064edae0814a", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "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", @@ -674,34 +845,94 @@ "time": "2018-06-11T23:09:50+00:00" }, { - "name": "nette/finder", - "version": "v2.4.2", + "name": "nesbot/carbon", + "version": "2.16.0", "source": { "type": "git", - "url": "https://github.com/nette/finder.git", - "reference": "ee951a656cb8ac622e5dd33474a01fd2470505a0" + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "dd16fedc022180ea4292a03aabe95e9895677911" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/finder/zipball/ee951a656cb8ac622e5dd33474a01fd2470505a0", - "reference": "ee951a656cb8ac622e5dd33474a01fd2470505a0", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/dd16fedc022180ea4292a03aabe95e9895677911", + "reference": "dd16fedc022180ea4292a03aabe95e9895677911", "shasum": "" }, "require": { - "nette/utils": "~2.4", - "php": ">=5.6.0" + "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.5.0", + "source": { + "type": "git", + "url": "https://github.com/nette/finder.git", + "reference": "6be1b83ea68ac558aff189d640abe242e0306fe2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/finder/zipball/6be1b83ea68ac558aff189d640abe242e0306fe2", + "reference": "6be1b83ea68ac558aff189d640abe242e0306fe2", + "shasum": "" + }, + "require": { + "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/var-exporter", - "version": "v4.2.1", + "name": "symfony/translation", + "version": "v4.2.4", "source": { "type": "git", - "url": "https://github.com/symfony/var-exporter.git", - "reference": "a39222e357362424b61dcde50e2f7b5a7d3306db" + "url": "https://github.com/symfony/translation.git", + "reference": "748464177a77011f8f4cdd076773862ce4915f8f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/a39222e357362424b61dcde50e2f7b5a7d3306db", - "reference": "a39222e357362424b61dcde50e2f7b5a7d3306db", + "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.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "d8bf4424c232b55f4c1816037d3077a89258557e" + }, + "dist": { + "type": "zip", + "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 405ef36..b19571e 100644 --- a/ecs.yml +++ b/ecs.yml @@ -30,14 +30,14 @@ services: 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: + # from strict.neon + PhpCsFixer\Fixer\PhpUnit\PhpUnitStrictFixer: ~ + PhpCsFixer\Fixer\Strict\StrictComparisonFixer: ~ + + # personal prefference + PhpCsFixer\Fixer\Operator\NotOperatorWithSuccessorSpaceFixer: ~ + 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 0ee43c4..c2abfaf 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 b75d7ae..71ea8d1 100644 --- a/src/Classification/Ensemble/RandomForest.php +++ b/src/Classification/Ensemble/RandomForest.php @@ -41,7 +41,7 @@ class RandomForest extends Bagging * 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 @@ class RandomForest extends Bagging 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 @@ class RandomForest extends Bagging } /** - * @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 8ab6939..889ea98 100644 --- a/src/Classification/Linear/LogisticRegression.php +++ b/src/Classification/Linear/LogisticRegression.php @@ -226,7 +226,7 @@ class LogisticRegression extends Adaline $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 3637ef6..abc53f1 100644 --- a/src/Clustering/FuzzyCMeans.php +++ b/src/Clustering/FuzzyCMeans.php @@ -77,9 +77,6 @@ class FuzzyCMeans implements Clusterer return $this->membership; } - /** - * @param Point[]|int[][] $samples - */ public function cluster(array $samples): array { // Initialize variables, clusters and membership matrix @@ -210,7 +207,7 @@ class FuzzyCMeans implements Clusterer $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 334ec6c..4ac951c 100644 --- a/src/Dataset/SvmDataset.php +++ b/src/Dataset/SvmDataset.php @@ -24,7 +24,7 @@ class SvmDataset extends ArrayDataset $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 18d0ba9..13bdc99 100644 --- a/src/FeatureSelection/ScoringFunction/UnivariateLinearRegression.php +++ b/src/FeatureSelection/ScoringFunction/UnivariateLinearRegression.php @@ -46,7 +46,7 @@ final class UnivariateLinearRegression implements ScoringFunction 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 4f9cfaf..bf22d6f 100644 --- a/src/Math/Kernel/RBF.php +++ b/src/Math/Kernel/RBF.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Phpml\Math\Kernel; +use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Kernel; use Phpml\Math\Product; @@ -19,12 +20,12 @@ class RBF implements Kernel $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 cc68864..94f0a9e 100644 --- a/src/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -502,7 +502,8 @@ class EigenvalueDecomposition } // 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 @@ class EigenvalueDecomposition // 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 7fe08e1..e9e6b51 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 2080019..230900f 100644 --- a/tests/Helper/Optimizer/ConjugateGradientTest.php +++ b/tests/Helper/Optimizer/ConjugateGradientTest.php @@ -23,7 +23,7 @@ class ConjugateGradientTest extends TestCase $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 @@ class ConjugateGradientTest extends TestCase $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 @@ class ConjugateGradientTest extends TestCase $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 ecbd515..548a74b 100644 --- a/tests/Helper/Optimizer/GDTest.php +++ b/tests/Helper/Optimizer/GDTest.php @@ -22,7 +22,7 @@ class GDTest extends TestCase $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 @@ class GDTest extends TestCase $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 ba3430b..075cee1 100644 --- a/tests/Helper/Optimizer/StochasticGDTest.php +++ b/tests/Helper/Optimizer/StochasticGDTest.php @@ -22,7 +22,7 @@ class StochasticGDTest extends TestCase $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 @@ class StochasticGDTest extends TestCase $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 bf24aab..15fbd2a 100644 --- a/tests/Math/Kernel/RBFTest.php +++ b/tests/Math/Kernel/RBFTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Phpml\Tests\Math\Kernel; +use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Kernel\RBF; use PHPUnit\Framework\TestCase; @@ -23,4 +24,12 @@ class RBFTest extends TestCase 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 63507f0..662a086 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 4df5730..999b48b 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 3c6ede2..25ad6b0 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 0000000..9b5df2c --- /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 0000000..71dc87e --- /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 8e2e9fd..4dc5260 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 0000000..20ee28c --- /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 0000000..f9986d5 --- /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 5fb4f2b..3adb3ef 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 68ef44b..92c643b 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 999b48b..3449ee0 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 8b3b158..52e783c 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 62a34e0..e270c97 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 25ad6b0..8ce2751 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 9573531..812870c 100644 --- a/tests/Classification/Linear/LogisticRegressionTest.php +++ b/tests/Classification/Linear/LogisticRegressionTest.php @@ -171,12 +171,12 @@ class LogisticRegressionTest extends TestCase $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 @@ class LogisticRegressionTest extends TestCase $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 cd04260..6e2ec2b 100644 --- a/tests/DimensionReduction/KernelPCATest.php +++ b/tests/DimensionReduction/KernelPCATest.php @@ -33,14 +33,14 @@ class KernelPCATest extends TestCase [-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 @@ class KernelPCATest extends TestCase $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 101d2f9..b64a7f3 100644 --- a/tests/DimensionReduction/LDATest.php +++ b/tests/DimensionReduction/LDATest.php @@ -51,7 +51,7 @@ class LDATest extends TestCase // 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 ebb5b01..5fbbc94 100644 --- a/tests/DimensionReduction/PCATest.php +++ b/tests/DimensionReduction/PCATest.php @@ -42,7 +42,7 @@ class PCATest extends TestCase // 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 @@ class PCATest extends TestCase $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 b0cf9f6..acb0103 100644 --- a/tests/FeatureExtraction/TfIdfTransformerTest.php +++ b/tests/FeatureExtraction/TfIdfTransformerTest.php @@ -54,6 +54,6 @@ class TfIdfTransformerTest extends TestCase $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 7a601db..8954e32 100644 --- a/tests/FeatureSelection/ScoringFunction/ANOVAFValueTest.php +++ b/tests/FeatureSelection/ScoringFunction/ANOVAFValueTest.php @@ -15,10 +15,9 @@ final class ANOVAFValueTest extends TestCase $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 0047e5f..48d72d3 100644 --- a/tests/FeatureSelection/ScoringFunction/UnivariateLinearRegressionTest.php +++ b/tests/FeatureSelection/ScoringFunction/UnivariateLinearRegressionTest.php @@ -15,7 +15,7 @@ final class UnivariateLinearRegressionTest extends TestCase $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 @@ final class UnivariateLinearRegressionTest extends TestCase $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 230900f..86a2991 100644 --- a/tests/Helper/Optimizer/ConjugateGradientTest.php +++ b/tests/Helper/Optimizer/ConjugateGradientTest.php @@ -33,7 +33,7 @@ class ConjugateGradientTest extends TestCase $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 @@ class ConjugateGradientTest extends TestCase $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 @@ class ConjugateGradientTest extends TestCase $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 548a74b..9640988 100644 --- a/tests/Helper/Optimizer/GDTest.php +++ b/tests/Helper/Optimizer/GDTest.php @@ -32,7 +32,7 @@ class GDTest extends TestCase $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 @@ class GDTest extends TestCase $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 075cee1..07927af 100644 --- a/tests/Helper/Optimizer/StochasticGDTest.php +++ b/tests/Helper/Optimizer/StochasticGDTest.php @@ -32,7 +32,7 @@ class StochasticGDTest extends TestCase $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 @@ class StochasticGDTest extends TestCase $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 770bf15..fbff7d9 100644 --- a/tests/Math/Distance/MinkowskiTest.php +++ b/tests/Math/Distance/MinkowskiTest.php @@ -47,7 +47,7 @@ class MinkowskiTest extends TestCase $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 @@ class MinkowskiTest extends TestCase $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 @@ class MinkowskiTest extends TestCase $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 15fbd2a..3e4ce26 100644 --- a/tests/Math/Kernel/RBFTest.php +++ b/tests/Math/Kernel/RBFTest.php @@ -15,13 +15,13 @@ class RBFTest extends TestCase $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 73018d0..884da25 100644 --- a/tests/Math/LinearAlgebra/EigenvalueDecompositionTest.php +++ b/tests/Math/LinearAlgebra/EigenvalueDecompositionTest.php @@ -21,11 +21,11 @@ class EigenvalueDecompositionTest extends TestCase $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 @@ class EigenvalueDecompositionTest extends TestCase $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 @@ class EigenvalueDecompositionTest extends TestCase $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 @@ class EigenvalueDecompositionTest extends TestCase $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 94d47e2..4c11caf 100644 --- a/tests/Math/MatrixTest.php +++ b/tests/Math/MatrixTest.php @@ -60,7 +60,7 @@ class MatrixTest extends TestCase [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 @@ class MatrixTest extends TestCase [-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 @@ class MatrixTest extends TestCase */ 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 2203bf1..acc79d9 100644 --- a/tests/Math/Statistic/ANOVATest.php +++ b/tests/Math/Statistic/ANOVATest.php @@ -19,7 +19,7 @@ final class ANOVATest extends TestCase $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 @@ final class ANOVATest extends TestCase [[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 2d0334d..98b7274 100644 --- a/tests/Math/Statistic/CorrelationTest.php +++ b/tests/Math/Statistic/CorrelationTest.php @@ -16,18 +16,18 @@ class CorrelationTest extends TestCase $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 dd98aea..fe792c4 100644 --- a/tests/Math/Statistic/CovarianceTest.php +++ b/tests/Math/Statistic/CovarianceTest.php @@ -38,18 +38,18 @@ class CovarianceTest extends TestCase // 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 @@ class CovarianceTest extends TestCase $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 b19c8db..16b1c5f 100644 --- a/tests/Math/Statistic/GaussianTest.php +++ b/tests/Math/Statistic/GaussianTest.php @@ -20,9 +20,9 @@ class GaussianTest extends TestCase $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 6e5d8d7..640225d 100644 --- a/tests/Math/Statistic/MeanTest.php +++ b/tests/Math/Statistic/MeanTest.php @@ -19,9 +19,9 @@ class MeanTest extends TestCase 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 e18c374..7f4b435 100644 --- a/tests/Math/Statistic/StandardDeviationTest.php +++ b/tests/Math/Statistic/StandardDeviationTest.php @@ -15,15 +15,15 @@ class StandardDeviationTest extends TestCase //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 @@ class StandardDeviationTest extends TestCase */ 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 310acb6..2cda011 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 792dd2f..cbb21c5 100644 --- a/tests/Metric/AccuracyTest.php +++ b/tests/Metric/AccuracyTest.php @@ -51,6 +51,6 @@ class AccuracyTest extends TestCase $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 3258bc1..3feccc8 100644 --- a/tests/Metric/ClassificationReportTest.php +++ b/tests/Metric/ClassificationReportTest.php @@ -45,11 +45,11 @@ class ClassificationReportTest extends TestCase '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 @@ class ClassificationReportTest extends TestCase '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 @@ class ClassificationReportTest extends TestCase '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 @@ class ClassificationReportTest extends TestCase '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 @@ class ClassificationReportTest extends TestCase '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 @@ class ClassificationReportTest extends TestCase $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 @@ class ClassificationReportTest extends TestCase $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 @@ class ClassificationReportTest extends TestCase $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 @@ class ClassificationReportTest extends TestCase $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 6876fd8..c44ae0f 100644 --- a/tests/NeuralNetwork/ActivationFunction/GaussianTest.php +++ b/tests/NeuralNetwork/ActivationFunction/GaussianTest.php @@ -18,7 +18,7 @@ class GaussianTest extends TestCase { $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 @@ class GaussianTest extends TestCase { $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 a6a244f..8865c59 100644 --- a/tests/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php +++ b/tests/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php @@ -18,7 +18,7 @@ class HyperboliTangentTest extends TestCase { $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 @@ class HyperboliTangentTest extends TestCase { $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 a659204..4aa069b 100644 --- a/tests/NeuralNetwork/ActivationFunction/PReLUTest.php +++ b/tests/NeuralNetwork/ActivationFunction/PReLUTest.php @@ -18,7 +18,7 @@ class PReLUTest extends TestCase { $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 d98b39c..d0cf22b 100644 --- a/tests/NeuralNetwork/ActivationFunction/SigmoidTest.php +++ b/tests/NeuralNetwork/ActivationFunction/SigmoidTest.php @@ -18,7 +18,7 @@ class SigmoidTest extends TestCase { $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 @@ class SigmoidTest extends TestCase { $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 a865831..0a48ee8 100644 --- a/tests/NeuralNetwork/Network/LayeredNetworkTest.php +++ b/tests/NeuralNetwork/Network/LayeredNetworkTest.php @@ -8,8 +8,8 @@ use Phpml\NeuralNetwork\ActivationFunction; 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 @@ class LayeredNetworkTest extends TestCase } /** - * @return LayeredNetwork|PHPUnit_Framework_MockObject_MockObject + * @return LayeredNetwork|MockObject */ private function getLayeredNetworkMock() { @@ -64,7 +64,7 @@ class LayeredNetworkTest extends TestCase } /** - * @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 d7bf7e5..1495136 100644 --- a/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php +++ b/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php @@ -9,8 +9,8 @@ use Phpml\NeuralNetwork\ActivationFunction; 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 @@ class MultilayerPerceptronTest extends TestCase } /** - * @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 1e33f34..1374ead 100644 --- a/tests/NeuralNetwork/Node/Neuron/SynapseTest.php +++ b/tests/NeuralNetwork/Node/Neuron/SynapseTest.php @@ -6,8 +6,8 @@ namespace Phpml\Tests\NeuralNetwork\Node\Neuron; 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 @@ class SynapseTest extends TestCase /** * @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 b1a77a8..448c885 100644 --- a/tests/NeuralNetwork/Node/NeuronTest.php +++ b/tests/NeuralNetwork/Node/NeuronTest.php @@ -7,8 +7,8 @@ namespace Phpml\Tests\NeuralNetwork\Node; 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 @@ class NeuronTest extends TestCase 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 @@ class NeuronTest extends TestCase $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 @@ class NeuronTest extends TestCase $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 0ba91c6..31c4f36 100644 --- a/tests/PipelineTest.php +++ b/tests/PipelineTest.php @@ -115,7 +115,7 @@ class PipelineTest extends TestCase $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 dcbb807..b410854 100644 --- a/tests/Preprocessing/ImputerTest.php +++ b/tests/Preprocessing/ImputerTest.php @@ -32,7 +32,7 @@ class ImputerTest extends TestCase $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 @@ class ImputerTest extends TestCase $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 @@ class ImputerTest extends TestCase $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 @@ class ImputerTest extends TestCase $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 @@ class ImputerTest extends TestCase $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 53b07d8..ed6b2c5 100644 --- a/tests/Preprocessing/NormalizerTest.php +++ b/tests/Preprocessing/NormalizerTest.php @@ -33,7 +33,7 @@ class NormalizerTest extends TestCase $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 @@ class NormalizerTest extends TestCase $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 @@ class NormalizerTest extends TestCase $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 @@ class NormalizerTest extends TestCase $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 1142a37..4d79c4f 100644 --- a/tests/Regression/LeastSquaresTest.php +++ b/tests/Regression/LeastSquaresTest.php @@ -21,7 +21,7 @@ class LeastSquaresTest extends TestCase $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 @@ class LeastSquaresTest extends TestCase $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 @@ class LeastSquaresTest extends TestCase $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 @@ class LeastSquaresTest extends TestCase $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 89099c0..962a713 100644 --- a/tests/Regression/SVRTest.php +++ b/tests/Regression/SVRTest.php @@ -21,7 +21,7 @@ class SVRTest extends TestCase $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 @@ class SVRTest extends TestCase $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 e3bbc85..b7b1942 100644 --- a/tests/SupportVectorMachine/SupportVectorMachineTest.php +++ b/tests/SupportVectorMachine/SupportVectorMachineTest.php @@ -59,8 +59,8 @@ SV ); $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 b19571e..21b30e9 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 99a82ab..54331e9 100644 --- a/src/Helper/Optimizer/Optimizer.php +++ b/src/Helper/Optimizer/Optimizer.php @@ -48,6 +48,11 @@ abstract class Optimizer 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 e9e6b51..beefb1e 100644 --- a/src/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/NeuralNetwork/Network/MultilayerPerceptron.php @@ -129,6 +129,16 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, 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 6c9af98..69a3e2a 100644 --- a/src/NeuralNetwork/Training/Backpropagation.php +++ b/src/NeuralNetwork/Training/Backpropagation.php @@ -34,6 +34,11 @@ class Backpropagation $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 97af2d2..184f6c7 100644 --- a/tests/Helper/Optimizer/OptimizerTest.php +++ b/tests/Helper/Optimizer/OptimizerTest.php @@ -26,9 +26,7 @@ class OptimizerTest extends TestCase $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 1495136..6123f9b 100644 --- a/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php +++ b/tests/NeuralNetwork/Network/MultilayerPerceptronTest.php @@ -55,14 +55,12 @@ class MultilayerPerceptronTest extends TestCase [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 @@ class MultilayerPerceptronTest extends TestCase [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 @@ class MultilayerPerceptronTest extends TestCase [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 0000000..9f0e024 --- /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 0000000..1f75cfd --- /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 0000000..6260a03 --- /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 0000000..5ed1504 --- /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 0000000..3176b62 --- /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 0000000..c6797b5 --- /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 0000000..f621fed --- /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 0000000..ebb848e --- /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 0000000..43db418 --- /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 0000000..2db3482 --- /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 3449ee0..83e2c31 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 6260a03..e066009 100644 --- a/src/Regression/DecisionTreeRegressor.php +++ b/src/Regression/DecisionTreeRegressor.php @@ -4,6 +4,7 @@ declare(strict_types=1); 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 5ed1504..eb8cfe7 100644 --- a/src/Tree/CART.php +++ b/src/Tree/CART.php @@ -91,7 +91,7 @@ abstract class CART $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 845d24a..046ce5d 100644 --- a/tests/Regression/DecisionTreeRegressorTest.php +++ b/tests/Regression/DecisionTreeRegressorTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Phpml\Tests\Regression; +use Phpml\Exception\InvalidArgumentException; use Phpml\Exception\InvalidOperationException; use Phpml\ModelManager; use Phpml\Regression\DecisionTreeRegressor; @@ -45,6 +46,20 @@ class DecisionTreeRegressorTest extends TestCase $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 d1ac35d..34f7533 100644 --- a/src/FeatureExtraction/TfIdfTransformer.php +++ b/src/FeatureExtraction/TfIdfTransformer.php @@ -30,7 +30,7 @@ class TfIdfTransformer implements Transformer } } - 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 afd5f33..5cc5e8d 100644 --- a/src/FeatureExtraction/TokenCountVectorizer.php +++ b/src/FeatureExtraction/TokenCountVectorizer.php @@ -46,7 +46,7 @@ class TokenCountVectorizer implements Transformer $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 36b4245..16e5278 100644 --- a/src/FeatureSelection/SelectKBest.php +++ b/src/FeatureSelection/SelectKBest.php @@ -56,7 +56,7 @@ final class SelectKBest implements Transformer $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 5ca2332..3bbc29d 100644 --- a/src/FeatureSelection/VarianceThreshold.php +++ b/src/FeatureSelection/VarianceThreshold.php @@ -48,7 +48,7 @@ final class VarianceThreshold implements Transformer } } - 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 e5b5af8..88ee2dd 100644 --- a/src/Preprocessing/Imputer.php +++ b/src/Preprocessing/Imputer.php @@ -49,7 +49,7 @@ class Imputer implements Preprocessor $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 9b5df2c..1e612a1 100644 --- a/src/Preprocessing/LabelEncoder.php +++ b/src/Preprocessing/LabelEncoder.php @@ -22,7 +22,7 @@ final class LabelEncoder implements Preprocessor } } - 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 9888e0e..5ba43e6 100644 --- a/src/Preprocessing/Normalizer.php +++ b/src/Preprocessing/Normalizer.php @@ -66,7 +66,7 @@ class Normalizer implements Preprocessor $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 0000000..68247b1 --- /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 7350e2c..3a9b91d 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 0000000..287b739 --- /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 0000000..afe2db7 --- /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 0000000..243c7eb --- /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 c90077c..bcb7895 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 0000000..f6b5a8b --- /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 0000000..6f46f3e --- /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 83e2c31..3fef8ed 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 0000000..645a421 --- /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 9f0e024..c833f6a 100644 --- a/src/Metric/Regression.php +++ b/src/Metric/Regression.php @@ -28,7 +28,7 @@ final class Regression $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 41188f3..421abb5 100644 --- a/src/Pipeline.php +++ b/src/Pipeline.php @@ -4,7 +4,9 @@ declare(strict_types=1); 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 @@ class Pipeline implements Estimator 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 @@ class Pipeline implements Estimator */ 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->transform($samples); + $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, $targets); } } } diff --git a/tests/FeatureUnionTest.php b/tests/FeatureUnionTest.php new file mode 100644 index 0000000..0a903b4 --- /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 31c4f36..f905c8b 100644 --- a/tests/PipelineTest.php +++ b/tests/PipelineTest.php @@ -11,9 +11,9 @@ use Phpml\FeatureSelection\SelectKBest; 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 @@ class PipelineTest extends TestCase 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 @@ class PipelineTest extends TestCase 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 e270c97..4eb374c 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 f9f57f5..3346315 100644 --- a/src/Clustering/KMeans/Space.php +++ b/src/Clustering/KMeans/Space.php @@ -167,6 +167,10 @@ class Space extends SplObjectStorage 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 daa7192..e24d908 100644 --- a/src/Dataset/FilesDataset.php +++ b/src/Dataset/FilesDataset.php @@ -19,7 +19,13 @@ class FilesDataset extends ArrayDataset 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 @@ class FilesDataset extends ArrayDataset { $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 662a086..b403887 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 3fef8ed..f34a49a 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 0000000..c9d4d0a --- /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 0000000..a5666b7 --- /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 d233f84..f89309e 100644 --- a/src/Math/Statistic/ANOVA.php +++ b/src/Math/Statistic/ANOVA.php @@ -45,6 +45,10 @@ final class ANOVA 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 ebf119b..5239954 100644 --- a/tests/FeatureSelection/SelectKBestTest.php +++ b/tests/FeatureSelection/SelectKBestTest.php @@ -61,6 +61,48 @@ final class SelectKBestTest extends TestCase ); } + 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 057e0ea..349f848 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 8ce2751..3457f68 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 bbf829b..779ef28 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 a4eb96c..a4ba53b 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 af3b357..57fcdcf 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 99b4da0..3d87b62 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 c82a195..ce01198 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 661f717..132c2dc 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 edfdded..a5bf402 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 d3f53be..1a6caa1 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 8bbcc37..87bae48 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 d2efaaa..557b7fc 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 f050cfd..6d55b3f 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 1ed5081..5c7a76e 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 8ac1c26..93a8cfb 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 c592b8d..4ac2e5d 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 4dc5260..7d9405e 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 2d8024c..71d1ff9 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 9c942e7..4021895 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 5045973..efdab23 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 53f125b..f5591a8 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 b07443a..4ff08c9 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 7365a71..976d475 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 219db22..302d89d 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 84a3279..5505f13 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 1678f5f..14f9e6a 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 34465eb..b89b88e 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 6970742..c7c3a98 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 626828e..a677a58 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 | 1896 +++++++++-------- 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, 971 insertions(+), 964 deletions(-) diff --git a/composer.json b/composer.json index 52e783c..19616b0 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 4eb374c..23641d3 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,6 +213,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -214,10 +225,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -234,74 +241,7 @@ "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" - }, - { - "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" + "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" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.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" - } - ], - "description": "The Illuminate Support package.", - "homepage": "https://laravel.com", - "time": "2019-05-14T13:37:34+00:00" + "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,20 +3025,78 @@ "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", + "psr/cache": "^1.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "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 caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-10-04T21:43:27+00:00" + }, + { + "name": "symfony/config", + "version": "v4.3.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "f4ee0ebb91b16ca1ac105aa39f9284f3cac19a15" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/f4ee0ebb91b16ca1ac105aa39f9284f3cac19a15", + "reference": "f4ee0ebb91b16ca1ac105aa39f9284f3cac19a15", "shasum": "" }, "require": { @@ -3272,6 +3111,7 @@ "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" }, "suggest": { @@ -3280,7 +3120,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3307,29 +3147,31 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2019-04-01T14:03:25+00:00" + "time": "2019-10-30T13:18:51+00:00" }, { "name": "symfony/console", - "version": "v4.2.8", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "e2840bb38bddad7a0feaf85931e38fdcffdb2f81" + "reference": "136c4bd62ea871d00843d1bc0316de4c4a84bb78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/e2840bb38bddad7a0feaf85931e38fdcffdb2f81", - "reference": "e2840bb38bddad7a0feaf85931e38fdcffdb2f81", + "url": "https://api.github.com/repos/symfony/console/zipball/136c4bd62ea871d00843d1bc0316de4c4a84bb78", + "reference": "136c4bd62ea871d00843d1bc0316de4c4a84bb78", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/contracts": "^1.0", - "symfony/polyfill-mbstring": "~1.0" + "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": { @@ -3339,9 +3181,10 @@ "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/event-dispatcher": "^4.3", "symfony/lock": "~3.4|~4.0", - "symfony/process": "~3.4|~4.0" + "symfony/process": "~3.4|~4.0", + "symfony/var-dumper": "^4.3" }, "suggest": { "psr/log": "For using the console logger", @@ -3352,7 +3195,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3379,91 +3222,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2019-04-08T14:23:48+00:00" - }, - { - "name": "symfony/contracts", - "version": "v1.1.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/contracts.git", - "reference": "d3636025e8253c6144358ec0a62773cae588395b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/contracts/zipball/d3636025e8253c6144358ec0a62773cae588395b", - "reference": "d3636025e8253c6144358ec0a62773cae588395b", - "shasum": "" - }, - "require": { - "php": "^7.1.3" - }, - "require-dev": { - "psr/cache": "^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.1-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": "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/filesystem", - "version": "v4.2.8", + "name": "symfony/event-dispatcher-contracts", + "version": "v1.1.7", "source": { "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "e16b9e471703b2c60b95f14d31c1239f68f11601" + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/e16b9e471703b2c60b95f14d31c1239f68f11601", - "reference": "e16b9e471703b2c60b95f14d31c1239f68f11601", + "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.3.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263" + }, + "dist": { + "type": "zip", + "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,20 +3725,79 @@ ], "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", + "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.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": { @@ -3907,7 +3806,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "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-mbstring", - "version": "v1.11.0", + "name": "symfony/polyfill-intl-idn", + "version": "v1.12.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "6af626ae6fa37d396dc90a399c0ff08e5cfc45b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", - "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", + "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.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17" + }, + "dist": { + "type": "zip", + "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,20 +4131,78 @@ "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": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "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.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/process", + "version": "v4.3.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "3b2e0cb029afbb0395034509291f21191d1a4db0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/3b2e0cb029afbb0395034509291f21191d1a4db0", + "reference": "3b2e0cb029afbb0395034509291f21191d1a4db0", "shasum": "" }, "require": { @@ -4192,7 +4211,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -4219,30 +4238,88 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2019-04-10T16:20:36+00:00" + "time": "2019-10-28T17:07:32+00:00" }, { - "name": "symfony/stopwatch", - "version": "v4.2.8", + "name": "symfony/service-contracts", + "version": "v1.1.7", "source": { "type": "git", - "url": "https://github.com/symfony/stopwatch.git", - "reference": "b1a5f646d56a3290230dbc8edf2a0d62cda23f67" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "ffcde9615dc5bb4825b9f6aed07716f1f57faae0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/b1a5f646d56a3290230dbc8edf2a0d62cda23f67", - "reference": "b1a5f646d56a3290230dbc8edf2a0d62cda23f67", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/ffcde9615dc5bb4825b9f6aed07716f1f57faae0", + "reference": "ffcde9615dc5bb4825b9f6aed07716f1f57faae0", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/contracts": "^1.0" + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "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.3-dev" } }, "autoload": { @@ -4269,95 +4346,20 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2019-01-16T20:31:39+00:00" - }, - { - "name": "symfony/translation", - "version": "v4.2.8", - "source": { - "type": "git", - "url": "https://github.com/symfony/translation.git", - "reference": "181a426dd129cb496f12d7e7555f6d0b37a7615b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/181a426dd129cb496f12d7e7555f6d0b37a7615b", - "reference": "181a426dd129cb496f12d7e7555f6d0b37a7615b", - "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/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": { - "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-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 21b30e9..a602695 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 db6be42..fb2d7ac 100644 --- a/src/Math/Matrix.php +++ b/src/Math/Matrix.php @@ -201,7 +201,7 @@ class Matrix */ public function add(self $other): self { - return $this->_add($other); + return $this->sum($other); } /** @@ -209,7 +209,7 @@ class Matrix */ public function subtract(self $other): self { - return $this->_add($other, -1); + return $this->sum($other, -1); } public function inverse(): self @@ -297,7 +297,7 @@ class Matrix /** * 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 349f848..e0f5be5 100644 --- a/src/ModelManager.php +++ b/src/ModelManager.php @@ -32,8 +32,8 @@ class ModelManager 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 9738ce7..d879fba 100644 --- a/tests/Classification/Ensemble/BaggingTest.php +++ b/tests/Classification/Ensemble/BaggingTest.php @@ -121,7 +121,9 @@ class BaggingTest extends TestCase 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 2f21c5c..abff973 100644 --- a/tests/Classification/Ensemble/RandomForestTest.php +++ b/tests/Classification/Ensemble/RandomForestTest.php @@ -61,6 +61,10 @@ class RandomForestTest extends BaggingTest 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 1374ead..f3c68cd 100644 --- a/tests/NeuralNetwork/Node/Neuron/SynapseTest.php +++ b/tests/NeuralNetwork/Node/Neuron/SynapseTest.php @@ -41,11 +41,9 @@ class SynapseTest extends TestCase } /** - * @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 448c885..376d78b 100644 --- a/tests/NeuralNetwork/Node/NeuronTest.php +++ b/tests/NeuralNetwork/Node/NeuronTest.php @@ -56,7 +56,7 @@ class NeuronTest extends TestCase /** * @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 3457f68..56d2359 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 92c643b..30ee823 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 | 951 ++++++++++++++++++++++++++++++-------------------- 2 files changed, 568 insertions(+), 389 deletions(-) diff --git a/composer.json b/composer.json index 19616b0..5d8b511 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 23641d3..fb04731 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,45 +1521,130 @@ "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": { + "php": "~7.1" + }, + "require-dev": { + "consistence/coding-standard": "^3.5", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "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": "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": "2019-06-07T19:13:52+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "0.12.4", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "100a25ba8561223f6bf5a5ff4204f951c0ec007c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/100a25ba8561223f6bf5a5ff4204f951c0ec007c", + "reference": "100a25ba8561223f6bf5a5ff4204f951c0ec007c", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "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": { - "nikic/php-parser": "^4.0", "php": "~7.1", - "phpstan/phpdoc-parser": "^0.3", - "phpstan/phpstan": "^0.11.4" + "phpstan/phpstan": "^0.12.3" }, "conflict": { "phpunit/phpunit": "<7.0" }, "require-dev": { - "consistence/coding-standard": "^3.0.1", + "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.11", + "phpstan/phpstan-strict-rules": "^0.12", "phpunit/phpunit": "^7.0", "satooshi/php-coveralls": "^1.0", - "slevomat/coding-standard": "^4.5.2" + "slevomat/coding-standard": "^4.7.2" }, "type": "phpstan-extension", "extra": { "branch-alias": { - "dev-master": "0.11-dev" + "dev-master": "0.12-dev" }, "phpstan": { "includes": [ @@ -1572,84 +1663,40 @@ "MIT" ], "description": "PHPUnit extensions and rules for PHPStan", - "time": "2019-05-17T17:50:16+00:00" - }, - { - "name": "phpstan/phpstan-shim", - "version": "0.11.19", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-shim.git", - "reference": "e3c06b1d63691dae644ae1e5b540905c8c021801" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-shim/zipball/e3c06b1d63691dae644ae1e5b540905c8c021801", - "reference": "e3c06b1d63691dae644ae1e5b540905c8c021801", - "shasum": "" - }, - "require": { - "php": "~7.1" - }, - "replace": { - "nikic/php-parser": "^4.0.2", - "phpstan/phpdoc-parser": "^0.3.3", - "phpstan/phpstan": "self.version" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.11-dev" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan Phar distribution", - "time": "2019-10-22T20:46:16+00:00" + "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/event-dispatcher", - "version": "v4.3.6", + "name": "symfony/error-handler", + "version": "v4.4.2", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "6229f58993e5a157f6096fc7145c0717d0be8807" + "url": "https://github.com/symfony/error-handler.git", + "reference": "6d7d7712a6ff5215ec26215672293b154f1db8c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/6229f58993e5a157f6096fc7145c0717d0be8807", - "reference": "6229f58993e5a157f6096fc7145c0717d0be8807", + "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.4.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "b3c3068a72623287550fe20b84a2b01dcba2686f" + }, + "dist": { + "type": "zip", + "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-exporter", - "version": "v4.3.6", + "name": "symfony/var-dumper", + "version": "v5.0.2", "source": { "type": "git", - "url": "https://github.com/symfony/var-exporter.git", - "reference": "d5b4e2d334c1d80e42876c7d489896cfd37562f2" + "url": "https://github.com/symfony/var-dumper.git", + "reference": "d7bc61d5d335fa9b1b91e14bb16861e8ca50f53a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/d5b4e2d334c1d80e42876c7d489896cfd37562f2", - "reference": "d5b4e2d334c1d80e42876c7d489896cfd37562f2", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/d7bc61d5d335fa9b1b91e14bb16861e8ca50f53a", + "reference": "d7bc61d5d335fa9b1b91e14bb16861e8ca50f53a", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.2.5", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<4.4" }, "require-dev": { - "symfony/var-dumper": "^4.1.1" + "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": "v5.0.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "1b9653e68d5b701bf6d9c91bdd3660078c9f4f28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/1b9653e68d5b701bf6d9c91bdd3660078c9f4f28", + "reference": "1b9653e68d5b701bf6d9c91bdd3660078c9f4f28", + "shasum": "" + }, + "require": { + "php": "^7.2.5" + }, + "require-dev": { + "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 fb04731..3a8a25f 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 c2abfaf..7af78fa 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 201bfbf..1f73679 100644 --- a/src/Association/Apriori.php +++ b/src/Association/Apriori.php @@ -104,11 +104,11 @@ class Apriori implements Associator */ 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 @@ class Apriori implements Associator $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 @@ class Apriori implements Associator } } - return array_map(function ($entry) { + return array_map(static function ($entry): array { return [$entry]; }, $items); } @@ -213,7 +213,7 @@ class Apriori implements Associator */ 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 @@ class Apriori implements Associator */ 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 @@ class Apriori implements Associator */ 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 797cdc9..e5bc9d9 100644 --- a/src/Classification/Linear/Adaline.php +++ b/src/Classification/Linear/Adaline.php @@ -58,7 +58,7 @@ class Adaline extends Perceptron 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 889ea98..4014fb0 100644 --- a/src/Classification/Linear/LogisticRegression.php +++ b/src/Classification/Linear/LogisticRegression.php @@ -188,7 +188,7 @@ class LogisticRegression extends Adaline * 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 @@ class LogisticRegression extends Adaline * 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 36cd4d1..44220a6 100644 --- a/src/Classification/Linear/Perceptron.php +++ b/src/Classification/Linear/Perceptron.php @@ -154,7 +154,7 @@ class Perceptron implements Classifier, IncrementalEstimator 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 abc53f1..ce86f5f 100644 --- a/src/Clustering/FuzzyCMeans.php +++ b/src/Clustering/FuzzyCMeans.php @@ -139,7 +139,7 @@ class FuzzyCMeans implements Clusterer $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 3346315..e4207cc 100644 --- a/src/Clustering/KMeans/Space.php +++ b/src/Clustering/KMeans/Space.php @@ -88,7 +88,7 @@ class Space extends SplObjectStorage $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 cdd387f..483664d 100644 --- a/src/Dataset/CsvDataset.php +++ b/src/Dataset/CsvDataset.php @@ -35,8 +35,8 @@ class CsvDataset extends ArrayDataset } $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 41c7340..beeaba4 100644 --- a/src/DimensionReduction/KernelPCA.php +++ b/src/DimensionReduction/KernelPCA.php @@ -179,13 +179,13 @@ class KernelPCA extends PCA // 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 @@ class KernelPCA extends PCA // 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 3bbc29d..0c3154e 100644 --- a/src/FeatureSelection/VarianceThreshold.php +++ b/src/FeatureSelection/VarianceThreshold.php @@ -37,7 +37,7 @@ final class VarianceThreshold implements Transformer 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 2832032..40c65c7 100644 --- a/src/Helper/Optimizer/GD.php +++ b/src/Helper/Optimizer/GD.php @@ -38,7 +38,7 @@ class GD extends StochasticGD $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 fb2d7ac..6f07d5d 100644 --- a/src/Math/Matrix.php +++ b/src/Math/Matrix.php @@ -126,7 +126,7 @@ class Matrix 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 f89309e..1629181 100644 --- a/src/Math/Statistic/ANOVA.php +++ b/src/Math/Statistic/ANOVA.php @@ -28,7 +28,7 @@ final class ANOVA 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 @@ final class ANOVA $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 @@ final class ANOVA 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 @@ final class ANOVA } } - 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 50effab..48d9302 100644 --- a/src/Math/Statistic/StandardDeviation.php +++ b/src/Math/Statistic/StandardDeviation.php @@ -50,7 +50,7 @@ class StandardDeviation $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 6263a52..0abac76 100644 --- a/src/Metric/ClassificationReport.php +++ b/src/Metric/ClassificationReport.php @@ -148,7 +148,7 @@ class ClassificationReport $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 @@ class ClassificationReport } } - /** - * @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 @@ class ClassificationReport 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 c537606..6681e66 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 0a6e0f8..d749937 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 421abb5..200173b 100644 --- a/src/Pipeline.php +++ b/src/Pipeline.php @@ -61,12 +61,12 @@ class Pipeline implements Estimator, Transformer */ 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 e066009..afc2805 100644 --- a/src/Regression/DecisionTreeRegressor.php +++ b/src/Regression/DecisionTreeRegressor.php @@ -121,7 +121,7 @@ final class DecisionTreeRegressor extends CART implements Regression 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 f621fed..311e0e7 100644 --- a/src/Tree/Node/DecisionNode.php +++ b/src/Tree/Node/DecisionNode.php @@ -50,7 +50,7 @@ class DecisionNode extends BinaryNode implements PurityNode $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 6e2ec2b..da4e51b 100644 --- a/tests/DimensionReduction/KernelPCATest.php +++ b/tests/DimensionReduction/KernelPCATest.php @@ -40,7 +40,7 @@ class KernelPCATest extends TestCase // 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 5fbbc94..3dbc5a6 100644 --- a/tests/DimensionReduction/PCATest.php +++ b/tests/DimensionReduction/PCATest.php @@ -42,7 +42,7 @@ class PCATest extends TestCase // 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 @@ class PCATest extends TestCase $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 86a2991..fc85a60 100644 --- a/tests/Helper/Optimizer/ConjugateGradientTest.php +++ b/tests/Helper/Optimizer/ConjugateGradientTest.php @@ -21,7 +21,7 @@ class ConjugateGradientTest extends TestCase $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 @@ class ConjugateGradientTest extends TestCase $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 @@ class ConjugateGradientTest extends TestCase $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 9640988..a6b4277 100644 --- a/tests/Helper/Optimizer/GDTest.php +++ b/tests/Helper/Optimizer/GDTest.php @@ -20,7 +20,7 @@ class GDTest extends TestCase $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 @@ class GDTest extends TestCase $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 07927af..4f99f78 100644 --- a/tests/Helper/Optimizer/StochasticGDTest.php +++ b/tests/Helper/Optimizer/StochasticGDTest.php @@ -20,7 +20,7 @@ class StochasticGDTest extends TestCase $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 @@ class StochasticGDTest extends TestCase $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 ed6b2c5..0a8f76c 100644 --- a/tests/Preprocessing/NormalizerTest.php +++ b/tests/Preprocessing/NormalizerTest.php @@ -126,7 +126,7 @@ class NormalizerTest extends TestCase 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 0000000..d26902d --- /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 56d2359..bff00f2 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 a677a58..0000000 --- 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 -```