From 9e84757bf0e4982cb8a18a2ab58489fedea918a0 Mon Sep 17 00:00:00 2001 From: David Monllao Date: Tue, 23 May 2017 17:15:04 +0800 Subject: [PATCH] MDL-58859 mlbackend_php: Added to core Part of MDL-57791 epic. --- lib/mlbackend/php/classes/processor.php | 340 +++++++ lib/mlbackend/php/lang/en/mlbackend_php.php | 7 + lib/mlbackend/php/phpml/LICENSE | 21 + .../php/phpml/bin/libsvm/svm-predict | Bin 0 -> 82312 bytes .../php/phpml/bin/libsvm/svm-predict-osx | Bin 0 -> 75716 bytes .../php/phpml/bin/libsvm/svm-predict.exe | Bin 0 -> 107008 bytes lib/mlbackend/php/phpml/bin/libsvm/svm-scale | Bin 0 -> 18640 bytes .../php/phpml/bin/libsvm/svm-scale-osx | Bin 0 -> 14432 bytes .../php/phpml/bin/libsvm/svm-scale.exe | Bin 0 -> 80384 bytes lib/mlbackend/php/phpml/bin/libsvm/svm-train | Bin 0 -> 86712 bytes .../php/phpml/bin/libsvm/svm-train-osx | Bin 0 -> 76220 bytes .../php/phpml/bin/libsvm/svm-train.exe | Bin 0 -> 135680 bytes lib/mlbackend/php/phpml/bin/phpunit | 1 + lib/mlbackend/php/phpml/readme_moodle.txt | 6 + .../phpml/src/Phpml/Association/Apriori.php | 345 +++++++ .../src/Phpml/Association/Associator.php | 11 + .../src/Phpml/Classification/Classifier.php | 11 + .../src/Phpml/Classification/DecisionTree.php | 509 ++++++++++ .../DecisionTree/DecisionTreeLeaf.php | 171 ++++ .../Classification/Ensemble/AdaBoost.php | 265 ++++++ .../Phpml/Classification/Ensemble/Bagging.php | 194 ++++ .../Classification/Ensemble/RandomForest.php | 153 +++ .../Classification/KNearestNeighbors.php | 82 ++ .../Phpml/Classification/Linear/Adaline.php | 77 ++ .../Classification/Linear/DecisionStump.php | 362 +++++++ .../Linear/LogisticRegression.php | 281 ++++++ .../Classification/Linear/Perceptron.php | 290 ++++++ .../src/Phpml/Classification/NaiveBayes.php | 184 ++++ .../phpml/src/Phpml/Classification/SVC.php | 31 + .../Classification/WeightedClassifier.php | 23 + .../phpml/src/Phpml/Clustering/Clusterer.php | 15 + .../php/phpml/src/Phpml/Clustering/DBSCAN.php | 111 +++ .../src/Phpml/Clustering/FuzzyCMeans.php | 243 +++++ .../php/phpml/src/Phpml/Clustering/KMeans.php | 60 ++ .../src/Phpml/Clustering/KMeans/Cluster.php | 147 +++ .../src/Phpml/Clustering/KMeans/Point.php | 124 +++ .../src/Phpml/Clustering/KMeans/Space.php | 259 +++++ .../src/Phpml/CrossValidation/RandomSplit.php | 30 + .../phpml/src/Phpml/CrossValidation/Split.php | 94 ++ .../CrossValidation/StratifiedRandomSplit.php | 62 ++ .../phpml/src/Phpml/Dataset/ArrayDataset.php | 52 + .../phpml/src/Phpml/Dataset/CsvDataset.php | 54 ++ .../php/phpml/src/Phpml/Dataset/Dataset.php | 18 + .../src/Phpml/Dataset/Demo/GlassDataset.php | 28 + .../src/Phpml/Dataset/Demo/IrisDataset.php | 22 + .../src/Phpml/Dataset/Demo/WineDataset.php | 22 + .../phpml/src/Phpml/Dataset/FilesDataset.php | 47 + .../Phpml/DimensionReduction/KernelPCA.php | 246 +++++ .../src/Phpml/DimensionReduction/PCA.php | 228 +++++ .../php/phpml/src/Phpml/Estimator.php | 21 + .../src/Phpml/Exception/DatasetException.php | 19 + .../src/Phpml/Exception/FileException.php | 39 + .../Exception/InvalidArgumentException.php | 94 ++ .../src/Phpml/Exception/MatrixException.php | 32 + .../Phpml/Exception/NormalizerException.php | 16 + .../Phpml/Exception/SerializeException.php | 29 + .../src/Phpml/FeatureExtraction/StopWords.php | 51 + .../FeatureExtraction/StopWords/English.php | 33 + .../FeatureExtraction/StopWords/Polish.php | 30 + .../FeatureExtraction/TfIdfTransformer.php | 66 ++ .../TokenCountVectorizer.php | 214 +++++ .../php/phpml/src/Phpml/Helper/OneVsRest.php | 197 ++++ .../Helper/Optimizer/ConjugateGradient.php | 361 +++++++ .../phpml/src/Phpml/Helper/Optimizer/GD.php | 121 +++ .../src/Phpml/Helper/Optimizer/Optimizer.php | 61 ++ .../Phpml/Helper/Optimizer/StochasticGD.php | 285 ++++++ .../phpml/src/Phpml/Helper/Predictable.php | 34 + .../php/phpml/src/Phpml/Helper/Trainable.php | 28 + .../phpml/src/Phpml/IncrementalEstimator.php | 16 + .../php/phpml/src/Phpml/Math/Distance.php | 16 + .../src/Phpml/Math/Distance/Chebyshev.php | 35 + .../src/Phpml/Math/Distance/Euclidean.php | 47 + .../src/Phpml/Math/Distance/Manhattan.php | 35 + .../src/Phpml/Math/Distance/Minkowski.php | 48 + .../php/phpml/src/Phpml/Math/Kernel.php | 16 + .../php/phpml/src/Phpml/Math/Kernel/RBF.php | 39 + .../LinearAlgebra/EigenvalueDecomposition.php | 890 ++++++++++++++++++ .../php/phpml/src/Phpml/Math/Matrix.php | 385 ++++++++ .../php/phpml/src/Phpml/Math/Product.php | 26 + .../php/phpml/src/Phpml/Math/Set.php | 211 +++++ .../src/Phpml/Math/Statistic/Correlation.php | 45 + .../src/Phpml/Math/Statistic/Covariance.php | 155 +++ .../src/Phpml/Math/Statistic/Gaussian.php | 60 ++ .../phpml/src/Phpml/Math/Statistic/Mean.php | 75 ++ .../Math/Statistic/StandardDeviation.php | 44 + .../php/phpml/src/Phpml/Metric/Accuracy.php | 39 + .../src/Phpml/Metric/ClassificationReport.php | 183 ++++ .../src/Phpml/Metric/ConfusionMatrix.php | 71 ++ .../php/phpml/src/Phpml/ModelManager.php | 54 ++ .../NeuralNetwork/ActivationFunction.php | 15 + .../ActivationFunction/BinaryStep.php | 20 + .../ActivationFunction/Gaussian.php | 20 + .../ActivationFunction/HyperbolicTangent.php | 33 + .../ActivationFunction/Sigmoid.php | 33 + .../phpml/src/Phpml/NeuralNetwork/Layer.php | 65 ++ .../phpml/src/Phpml/NeuralNetwork/Network.php | 30 + .../NeuralNetwork/Network/LayeredNetwork.php | 81 ++ .../Network/MultilayerPerceptron.php | 95 ++ .../phpml/src/Phpml/NeuralNetwork/Node.php | 13 + .../src/Phpml/NeuralNetwork/Node/Bias.php | 18 + .../src/Phpml/NeuralNetwork/Node/Input.php | 39 + .../src/Phpml/NeuralNetwork/Node/Neuron.php | 75 ++ .../NeuralNetwork/Node/Neuron/Synapse.php | 70 ++ .../src/Phpml/NeuralNetwork/Training.php | 16 + .../Training/Backpropagation.php | 160 ++++ .../Training/Backpropagation/Sigma.php | 64 ++ .../php/phpml/src/Phpml/Pipeline.php | 106 +++ .../phpml/src/Phpml/Preprocessing/Imputer.php | 99 ++ .../Phpml/Preprocessing/Imputer/Strategy.php | 15 + .../Imputer/Strategy/MeanStrategy.php | 21 + .../Imputer/Strategy/MedianStrategy.php | 21 + .../Imputer/Strategy/MostFrequentStrategy.php | 21 + .../src/Phpml/Preprocessing/Normalizer.php | 145 +++ .../src/Phpml/Preprocessing/Preprocessor.php | 11 + .../src/Phpml/Regression/LeastSquares.php | 118 +++ .../src/Phpml/Regression/MLPRegressor.php | 80 ++ .../phpml/src/Phpml/Regression/Regression.php | 11 + .../php/phpml/src/Phpml/Regression/SVR.php | 31 + .../SupportVectorMachine/DataTransformer.php | 101 ++ .../src/Phpml/SupportVectorMachine/Kernel.php | 28 + .../SupportVectorMachine.php | 239 +++++ .../src/Phpml/SupportVectorMachine/Type.php | 33 + .../src/Phpml/Tokenization/Tokenizer.php | 15 + .../Tokenization/WhitespaceTokenizer.php | 18 + .../src/Phpml/Tokenization/WordTokenizer.php | 21 + .../php/phpml/src/Phpml/Transformer.php | 18 + lib/mlbackend/php/phpml/var/.gitkeep | 0 lib/mlbackend/php/thirdpartylibs.xml | 10 + lib/mlbackend/php/version.php | 29 + 129 files changed, 11485 insertions(+) create mode 100644 lib/mlbackend/php/classes/processor.php create mode 100644 lib/mlbackend/php/lang/en/mlbackend_php.php create mode 100644 lib/mlbackend/php/phpml/LICENSE create mode 100644 lib/mlbackend/php/phpml/bin/libsvm/svm-predict create mode 100644 lib/mlbackend/php/phpml/bin/libsvm/svm-predict-osx create mode 100644 lib/mlbackend/php/phpml/bin/libsvm/svm-predict.exe create mode 100644 lib/mlbackend/php/phpml/bin/libsvm/svm-scale create mode 100644 lib/mlbackend/php/phpml/bin/libsvm/svm-scale-osx create mode 100644 lib/mlbackend/php/phpml/bin/libsvm/svm-scale.exe create mode 100644 lib/mlbackend/php/phpml/bin/libsvm/svm-train create mode 100644 lib/mlbackend/php/phpml/bin/libsvm/svm-train-osx create mode 100644 lib/mlbackend/php/phpml/bin/libsvm/svm-train.exe create mode 120000 lib/mlbackend/php/phpml/bin/phpunit create mode 100644 lib/mlbackend/php/phpml/readme_moodle.txt create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Association/Apriori.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Association/Associator.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Classification/Classifier.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Classification/DecisionTree.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Classification/Ensemble/AdaBoost.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Classification/Ensemble/Bagging.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Classification/Ensemble/RandomForest.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Classification/KNearestNeighbors.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/Adaline.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/DecisionStump.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/LogisticRegression.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/Perceptron.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Classification/NaiveBayes.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Classification/SVC.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Classification/WeightedClassifier.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Clustering/Clusterer.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Clustering/DBSCAN.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Clustering/FuzzyCMeans.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Clustering/KMeans.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Clustering/KMeans/Cluster.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Clustering/KMeans/Point.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Clustering/KMeans/Space.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/CrossValidation/RandomSplit.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/CrossValidation/Split.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/CrossValidation/StratifiedRandomSplit.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Dataset/ArrayDataset.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Dataset/CsvDataset.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Dataset/Dataset.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Dataset/Demo/GlassDataset.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Dataset/Demo/IrisDataset.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Dataset/Demo/WineDataset.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Dataset/FilesDataset.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/DimensionReduction/KernelPCA.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/DimensionReduction/PCA.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Estimator.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Exception/DatasetException.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Exception/FileException.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Exception/InvalidArgumentException.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Exception/MatrixException.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Exception/NormalizerException.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Exception/SerializeException.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/FeatureExtraction/StopWords.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/FeatureExtraction/StopWords/English.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/FeatureExtraction/StopWords/Polish.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/FeatureExtraction/TfIdfTransformer.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/FeatureExtraction/TokenCountVectorizer.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Helper/OneVsRest.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/ConjugateGradient.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/GD.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/Optimizer.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/StochasticGD.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Helper/Predictable.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Helper/Trainable.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/IncrementalEstimator.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Math/Distance.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Math/Distance/Chebyshev.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Math/Distance/Euclidean.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Math/Distance/Manhattan.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Math/Distance/Minkowski.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Math/Kernel.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Math/Kernel/RBF.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Math/Matrix.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Math/Product.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Math/Set.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/Correlation.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/Covariance.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/Gaussian.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/Mean.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/StandardDeviation.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Metric/Accuracy.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Metric/ClassificationReport.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Metric/ConfusionMatrix.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/ModelManager.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/ActivationFunction.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/ActivationFunction/BinaryStep.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/ActivationFunction/HyperbolicTangent.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Layer.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Network.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Node.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Node/Bias.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Node/Input.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Node/Neuron.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Training.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Training/Backpropagation.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Pipeline.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Preprocessing/Imputer.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Preprocessing/Imputer/Strategy.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Preprocessing/Imputer/Strategy/MostFrequentStrategy.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Preprocessing/Normalizer.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Preprocessing/Preprocessor.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Regression/LeastSquares.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Regression/MLPRegressor.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Regression/Regression.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Regression/SVR.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/SupportVectorMachine/DataTransformer.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/SupportVectorMachine/Kernel.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/SupportVectorMachine/SupportVectorMachine.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/SupportVectorMachine/Type.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Tokenization/Tokenizer.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Tokenization/WhitespaceTokenizer.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Tokenization/WordTokenizer.php create mode 100644 lib/mlbackend/php/phpml/src/Phpml/Transformer.php create mode 100644 lib/mlbackend/php/phpml/var/.gitkeep create mode 100644 lib/mlbackend/php/thirdpartylibs.xml create mode 100644 lib/mlbackend/php/version.php diff --git a/lib/mlbackend/php/classes/processor.php b/lib/mlbackend/php/classes/processor.php new file mode 100644 index 0000000000000..065f745c70989 --- /dev/null +++ b/lib/mlbackend/php/classes/processor.php @@ -0,0 +1,340 @@ +. + +/** + * Php predictions processor + * + * @package mlbackend_php + * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mlbackend_php; + +// TODO No support for 3rd party plugins psr4?? +spl_autoload_register(function($class) { + // Autoload Phpml classes. + $path = __DIR__ . '/../phpml/src/' . str_replace('\\', '/', $class) . '.php'; + if (file_exists($path)) { + require_once($path); + } +}); + +use Phpml\NeuralNetwork\Network\MultilayerPerceptron; +use Phpml\NeuralNetwork\Training\Backpropagation; +use Phpml\CrossValidation\RandomSplit; +use Phpml\Dataset\ArrayDataset; + +defined('MOODLE_INTERNAL') || die(); + +/** + * PHP predictions processor. + * + * @package mlbackend_php + * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class processor implements \core_analytics\predictor { + + const BATCH_SIZE = 1000; + const TRAIN_ITERATIONS = 20; + const MODEL_FILENAME = 'model.ser'; + + protected $limitedsize = false; + + public function is_ready() { + return true; + } + + public function train($uniqueid, \stored_file $dataset, $outputdir) { + + // Output directory is already unique to the model. + $modelfilepath = $outputdir . DIRECTORY_SEPARATOR . self::MODEL_FILENAME; + + $modelmanager = new \Phpml\ModelManager(); + + if (file_exists($modelfilepath)) { + $classifier = $modelmanager->restoreFromFile($modelfilepath); + } else { + $classifier = new \Phpml\Classification\Linear\Perceptron(0.001, self::TRAIN_ITERATIONS, false); + } + + $fh = $dataset->get_content_file_handle(); + + // The first lines are var names and the second one values. + $metadata = $this->extract_metadata($fh); + + // Skip headers. + fgets($fh); + + $samples = array(); + $targets = array(); + while (($data = fgetcsv($fh)) !== false) { + $sampledata = array_map('floatval', $data); + $samples[] = array_slice($sampledata, 0, $metadata['nfeatures']); + $targets[] = intval($data[$metadata['nfeatures']]); + + if (count($samples) === self::BATCH_SIZE) { + // Training it batches to avoid running out of memory. + + $classifier->partialTrain($samples, $targets, array(0, 1)); + $samples = array(); + $targets = array(); + } + } + fclose($fh); + + // Train the remaining samples. + if ($samples) { + $classifier->partialTrain($samples, $targets, array(0, 1)); + } + + $resultobj = new \stdClass(); + $resultobj->status = \core_analytics\model::OK; + $resultobj->info = array(); + + // Store the trained model. + $modelmanager->saveToFile($classifier, $modelfilepath); + + return $resultobj; + } + + public function predict($uniqueid, \stored_file $dataset, $outputdir) { + + // Output directory is already unique to the model. + $modelfilepath = $outputdir . DIRECTORY_SEPARATOR . self::MODEL_FILENAME; + + if (!file_exists($modelfilepath)) { + throw new \moodle_exception('errorcantloadmodel', 'analytics', '', $modelfilepath); + } + + $modelmanager = new \Phpml\ModelManager(); + $classifier = $modelmanager->restoreFromFile($modelfilepath); + + $fh = $dataset->get_content_file_handle(); + + // The first lines are var names and the second one values. + $metadata = $this->extract_metadata($fh); + + // Skip headers. + fgets($fh); + + $sampleids = array(); + $samples = array(); + $predictions = array(); + while (($data = fgetcsv($fh)) !== false) { + $sampledata = array_map('floatval', $data); + $sampleids[] = $data[0]; + $samples[] = array_slice($sampledata, 1, $metadata['nfeatures']); + + if (count($samples) === self::BATCH_SIZE) { + // Prediction it batches to avoid running out of memory. + + // Append predictions incrementally, we want $sampleids keys in sync with $predictions keys. + $newpredictions = $classifier->predict($samples); + foreach ($newpredictions as $prediction) { + array_push($predictions, $prediction); + } + $samples = array(); + } + } + fclose($fh); + + // Finish the remaining predictions. + if ($samples) { + $predictions = $predictions + $classifier->predict($samples); + } + + $resultobj = new \stdClass(); + $resultobj->status = \core_analytics\model::OK; + $resultobj->info = array(); + + foreach ($predictions as $index => $prediction) { + $resultobj->predictions[$index] = array($sampleids[$index], $prediction); + } + + return $resultobj; + } + + /** + * Evaluates the provided dataset. + * + * During evaluation we need to shuffle the evaluation dataset samples to detect deviated results, + * if the dataset is massive we can not load everything into memory. We know that 2GB is the + * minimum memory limit we should have (\core_analytics\model::increase_memory), if we substract the memory + * that we already consumed and the memory that Phpml algorithms will need we should still have at + * least 500MB of memory, which should be enough to evaluate a model. In any case this is a robust + * solution that will work for all sites but it should minimize memory limit problems. Site admins + * can still set $CFG->mlbackend_php_no_evaluation_limits to true to skip this 500MB limit. + * + * @param string $uniqueid + * @param float $maxdeviation + * @param int $niterations + * @param \stored_file $dataset + * @param string $outputdir + * @return \stdClass + */ + public function evaluate($uniqueid, $maxdeviation, $niterations, \stored_file $dataset, $outputdir) { + $fh = $dataset->get_content_file_handle(); + + // The first lines are var names and the second one values. + $metadata = $this->extract_metadata($fh); + + // Skip headers. + fgets($fh); + + if (empty($CFG->mlbackend_php_no_evaluation_limits)) { + $samplessize = 0; + $limit = get_real_size('500MB'); + + // Just an approximation, will depend on PHP version, compile options... + // Double size + zval struct (6 bytes + 8 bytes + 16 bytes) + array bucket (96 bytes) + // https://nikic.github.io/2011/12/12/How-big-are-PHP-arrays-really-Hint-BIG.html + $floatsize = (PHP_INT_SIZE * 2) + 6 + 8 + 16 + 96; + } + + $samples = array(); + $targets = array(); + while (($data = fgetcsv($fh)) !== false) { + $sampledata = array_map('floatval', $data); + + $samples[] = array_slice($sampledata, 0, $metadata['nfeatures']); + $targets[] = array(intval($data[$metadata['nfeatures']])); + + if (empty($CFG->mlbackend_php_no_evaluation_limits)) { + // We allow admins to disable evaluation memory usage limits by modifying config.php. + + // We will have plenty of missing values in the dataset so it should be a conservative approximation: + $samplessize = $samplessize + (count($sampledata) * $floatsize); + + // Stop fetching more samples. + if ($samplessize >= $limit) { + $this->limitedsize = true; + break; + } + } + } + fclose($fh); + + $phis = array(); + + // Evaluate the model multiple times to confirm the results are not significantly random due to a short amount of data. + for ($i = 0; $i < $niterations; $i++) { + + //$classifier = new \Phpml\Classification\Linear\Perceptron(0.001, self::TRAIN_ITERATIONS, false); + $network = new MultilayerPerceptron([intval($metadata['nfeatures']), 2, 1]); + $training = new Backpropagation($network); + + // Split up the dataset in classifier and testing. + $data = new RandomSplit(new ArrayDataset($samples, $targets), 0.2); + + $training->train($data->getTrainSamples(), $data->getTrainLabels(), 0, 1); + + $predictedlabels = array(); + foreach ($data->getTestSamples() as $input) { + $output = $network->setInput($input)->getOutput(); + $predictedlabels[] = reset($output); + } + $phis[] = $this->get_phi($data->getTestLabels(), $predictedlabels); + } + + // Let's fill the results changing the returned status code depending on the phi-related calculated metrics. + return $this->get_evaluation_result_object($dataset, $phis, $maxdeviation); + } + + protected function get_evaluation_result_object(\stored_file $dataset, $phis, $maxdeviation) { + + if (count($phis) === 1) { + $avgphi = reset($phis); + } else { + $avgphi = \Phpml\Math\Statistic\Mean::arithmetic($phis); + } + + // Standard deviation should ideally be calculated against the area under the curve. + if (count($phis) === 1) { + $modeldev = 0; + } else { + $modeldev = \Phpml\Math\Statistic\StandardDeviation::population($phis); + } + + // Let's fill the results object. + $resultobj = new \stdClass(); + + // Zero is ok, now we add other bits if something is not right. + $resultobj->status = \core_analytics\model::OK; + $resultobj->info = array(); + + // Convert phi to a standard score (from -1 to 1 to a value between 0 and 1). + $resultobj->score = ($avgphi + 1) / 2; + + // If each iteration results varied too much we need more data to confirm that this is a valid model. + if ($modeldev > $maxdeviation) { + $resultobj->status = $resultobj->status + \core_analytics\model::EVALUATE_NOT_ENOUGH_DATA; + $a = new \stdClass(); + $a->deviation = $modeldev; + $a->accepteddeviation = $maxdeviation; + $resultobj->info[] = get_string('errornotenoughdata', 'mlbackend_php', $a); + } + + if ($resultobj->score < \core_analytics\model::MIN_SCORE) { + $resultobj->status = $resultobj->status + \core_analytics\model::EVALUATE_LOW_SCORE; + $a = new \stdClass(); + $a->score = $resultobj->score; + $a->minscore = \core_analytics\model::MIN_SCORE; + $resultobj->info[] = get_string('errorlowscore', 'mlbackend_php', $a); + } + + if ($this->limitedsize === true) { + $resultobj->info[] = get_string('datasetsizelimited', 'mlbackend_php', display_size($dataset->get_filesize())); + } + + return $resultobj; + } + + protected function get_phi($testlabels, $predictedlabels) { + + foreach ($testlabels as $key => $element) { + $value = reset($element); + $testlabels[$key] = $value; + } + + foreach ($predictedlabels as $key => $element) { + $predictedlabels[$key] = ($element > 0.5) ? 1 : 0; + } + + // Binary here only as well. + $matrix = \Phpml\Metric\ConfusionMatrix::compute($testlabels, $predictedlabels, array(0, 1)); + + $tptn = $matrix[0][0] * $matrix[1][1]; + $fpfn = $matrix[1][0] * $matrix[0][1]; + $tpfp = $matrix[0][0] + $matrix[1][0]; + $tpfn = $matrix[0][0] + $matrix[0][1]; + $tnfp = $matrix[1][1] + $matrix[1][0]; + $tnfn = $matrix[1][1] + $matrix[0][1]; + if ($tpfp === 0 || $tpfn === 0 || $tnfp === 0 || $tnfn === 0) { + $phi = 0; + } else { + $phi = ( $tptn - $fpfn ) / sqrt( $tpfp * $tpfn * $tnfp * $tnfn); + } + + return $phi; + } + + protected function extract_metadata($fh) { + $metadata = fgetcsv($fh); + return array_combine($metadata, fgetcsv($fh)); + } +} diff --git a/lib/mlbackend/php/lang/en/mlbackend_php.php b/lib/mlbackend/php/lang/en/mlbackend_php.php new file mode 100644 index 0000000000000..6fed20fc534eb --- /dev/null +++ b/lib/mlbackend/php/lang/en/mlbackend_php.php @@ -0,0 +1,7 @@ +deviation}, maximum recommended standard deviation = {$a->accepteddeviation}'; +$string['errorlowscore'] = 'The evaluated model prediction accuracy is not very high, some predictions may not be accurate. Model score = {$a->score}, minimum score = {$a->minscore}'; +$string['datasetsizelimited'] = 'Only a part of the evaluation dataset has been evaluated due to its size. Set $CFG->mlbackend_php_no_memory_limit if you are confident that your system can cope a {$a} dataset'; diff --git a/lib/mlbackend/php/phpml/LICENSE b/lib/mlbackend/php/phpml/LICENSE new file mode 100644 index 0000000000000..bd5cb2fd6e4de --- /dev/null +++ b/lib/mlbackend/php/phpml/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/mlbackend/php/phpml/bin/libsvm/svm-predict b/lib/mlbackend/php/phpml/bin/libsvm/svm-predict new file mode 100644 index 0000000000000000000000000000000000000000..e6df243d3c27353701adb2c07916debf59cf9896 GIT binary patch literal 82312 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
={qER^}e(J<2e}24$QYsQ^Av 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 YTtq*N7JuFh9e#yf&?;0S-NtGCAHrR=FaQ7m literal 0 HcmV?d00001 diff --git a/lib/mlbackend/php/phpml/bin/libsvm/svm-predict-osx b/lib/mlbackend/php/phpml/bin/libsvm/svm-predict-osx new file mode 100644 index 0000000000000000000000000000000000000000..480a60b7899acc51af2b93d2f840ee5b61e8c2a1 GIT binary patch literal 75716 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 z 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 G@BaW7hNhqZ literal 0 HcmV?d00001 diff --git a/lib/mlbackend/php/phpml/bin/libsvm/svm-predict.exe b/lib/mlbackend/php/phpml/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/lib/mlbackend/php/phpml/bin/libsvm/svm-scale b/lib/mlbackend/php/phpml/bin/libsvm/svm-scale new file mode 100644 index 0000000000000000000000000000000000000000..edfb98a6bd7e728118340c6a3ce0a2278c472830 GIT binary patch literal 18640 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 literal 0 HcmV?d00001 diff --git a/lib/mlbackend/php/phpml/bin/libsvm/svm-scale.exe b/lib/mlbackend/php/phpml/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/lib/mlbackend/php/phpml/bin/libsvm/svm-train b/lib/mlbackend/php/phpml/bin/libsvm/svm-train new file mode 100644 index 0000000000000000000000000000000000000000..9cb7f11166b9959f76e6326be3df9e7156b119a9 GIT binary patch literal 86712 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 A>i_@% literal 0 HcmV?d00001 diff --git a/lib/mlbackend/php/phpml/bin/libsvm/svm-train-osx b/lib/mlbackend/php/phpml/bin/libsvm/svm-train-osx new file mode 100644 index 0000000000000000000000000000000000000000..a716cdabd5f925c143f25dac1a876b8fe6da10f8 GIT binary patch literal 76220 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 sL)Ry&5t!X^Iw(y<;|b^)Td)*ANgq6E%8s4 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 literal 0 HcmV?d00001 diff --git a/lib/mlbackend/php/phpml/bin/phpunit b/lib/mlbackend/php/phpml/bin/phpunit new file mode 120000 index 0000000000000..4ba325648fba9 --- /dev/null +++ b/lib/mlbackend/php/phpml/bin/phpunit @@ -0,0 +1 @@ +../vendor/phpunit/phpunit/phpunit \ No newline at end of file diff --git a/lib/mlbackend/php/phpml/readme_moodle.txt b/lib/mlbackend/php/phpml/readme_moodle.txt new file mode 100644 index 0000000000000..4ffb5debb66ef --- /dev/null +++ b/lib/mlbackend/php/phpml/readme_moodle.txt @@ -0,0 +1,6 @@ +Current version is 12b8b11 + +# Download latest stable version from https://github.com/php-ai/php-ml +# Remove all files but: + * src/ + * LICENSE diff --git a/lib/mlbackend/php/phpml/src/Phpml/Association/Apriori.php b/lib/mlbackend/php/phpml/src/Phpml/Association/Apriori.php new file mode 100644 index 0000000000000..362f25a6ce8cd --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Association/Apriori.php @@ -0,0 +1,345 @@ +support = $support; + $this->confidence = $confidence; + } + + /** + * Get all association rules which are generated for every k-length frequent item set. + * + * @return mixed[][] + */ + public function getRules() : array + { + if (!$this->large) { + $this->large = $this->apriori(); + } + + if ($this->rules) { + return $this->rules; + } + + $this->rules = []; + + $this->generateAllRules(); + + return $this->rules; + } + + /** + * Generates frequent item sets. + * + * @return mixed[][][] + */ + 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]); + } + + return $L; + } + + /** + * @param mixed[] $sample + * + * @return mixed[][] + */ + 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); + })); + + 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, + ]; + } + } + } + + /** + * Generates the power set for given item set $sample. + * + * @param mixed[] $sample + * + * @return mixed[][] + */ + private function powerSet(array $sample) : array + { + $results = [[]]; + foreach ($sample as $item) { + foreach ($results as $combination) { + $results[] = array_merge([$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) : array + { + $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() : array + { + $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) : array + { + 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) : array + { + $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) : float + { + 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) : float + { + 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) : int + { + 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) : bool + { + 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) : bool + { + 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) : bool + { + return array_diff($set1, $set2) == array_diff($set2, $set1); + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Association/Associator.php b/lib/mlbackend/php/phpml/src/Phpml/Association/Associator.php new file mode 100644 index 0000000000000..c339b5e65f331 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Association/Associator.php @@ -0,0 +1,11 @@ +maxDepth = $maxDepth; + } + + /** + * @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($this->samples[0]); + $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)); + + // 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)); + } + } + + /** + * @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::CONTINUOUS; + } + + return $types; + } + + /** + * @param array $records + * @param int $depth + * @return DecisionTreeLeaf + */ + protected function getSplitLeaf(array $records, int $depth = 0) : DecisionTreeLeaf + { + $split = $this->getBestSplit($records); + $split->level = $depth; + 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 (! array_key_exists($target, $remainingTargets)) { + $remainingTargets[$target] = 1; + } else { + $remainingTargets[$target]++; + } + } + + if ($allSame || $depth >= $this->maxDepth || count($remainingTargets) === 1) { + $split->isTerminal = 1; + arsort($remainingTargets); + $split->classValue = key($remainingTargets); + } 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(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)); + $bestGiniVal = 1; + $bestSplit = null; + $features = $this->getSelectedFeatures(); + foreach ($features as $i) { + $colValues = []; + foreach ($samples as $index => $row) { + $colValues[$index] = $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(); + $split->value = $baseValue; + $split->giniIndex = $gini; + $split->columnIndex = $i; + $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) { + $matches = []; + preg_match("/^([<>=]{1,2})\s*(.*)/", strval($split->value), $matches); + $split->operator = $matches[1]; + $split->numericValue = floatval($matches[2]); + } + + $bestSplit = $split; + $bestGiniVal = $gini; + } + } + + return $bestSplit; + } + + /** + * 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() : array + { + $allFeatures = range(0, $this->featureCount - 1); + if ($this->numUsableFeatures === 0 && ! $this->selectedFeatures) { + return $allFeatures; + } + + if ($this->selectedFeatures) { + return $this->selectedFeatures; + } + + $numFeatures = $this->numUsableFeatures; + if ($numFeatures > $this->featureCount) { + $numFeatures = $this->featureCount; + } + shuffle($allFeatures); + $selectedFeatures = array_slice($allFeatures, 0, $numFeatures, false); + sort($selectedFeatures); + + return $selectedFeatures; + } + + /** + * @param $baseValue + * @param array $colValues + * @param array $targets + * @return float + */ + 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] / floatval($sum), 2); + } + } + $giniParts[$i] = (1 - $part) * $sum; + } + + 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 + // 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::CONTINUOUS) { + $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 static function isCategoricalColumn(array $columnValues) : bool + { + $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 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'); + $floatValues = array_filter($columnValues, 'is_float'); + if ($floatValues) { + return false; + } + if (count($numericValues) !== $count) { + return true; + } + + $distinctValues = array_count_values($columnValues); + + 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. + * + * @param int $numFeatures + * @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 + * + * @param array $selectedFeatures + */ + 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. + * + * @param array $names + * @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; + } + + /** + * @return string + */ + public function getHtml() + { + 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.
+ * + * @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 criterion + * + * @param int $column + * @param DecisionTreeLeaf $node + * @return array + */ + protected function getSplitNodesByColumn(int $column, DecisionTreeLeaf $node) : array + { + 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; + } + + /** + * @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 ? $node->classValue : $this->labels[0]; + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php b/lib/mlbackend/php/phpml/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php new file mode 100644 index 0000000000000..bbb3175112fdd --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -0,0 +1,171 @@ +columnIndex]; + + if ($this->isContinuous) { + $op = $this->operator; + $value= $this->numericValue; + $recordField = strval($recordField); + eval("\$result = $recordField $op $value;"); + return $result; + } + + return $recordField == $this->value; + } + + /** + * 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; + if ($columnNames !== null) { + $col = $columnNames[$this->columnIndex]; + } else { + $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->getHTML($columnNames) . "
 No |
" . $this->rightLeaf->getHTML($columnNames) . "
'; + return $str; + } + + /** + * HTML representation of the tree without column names + * + * @return string + */ + public function __toString() + { + return $this->getHTML(); + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Classification/Ensemble/AdaBoost.php b/lib/mlbackend/php/phpml/src/Phpml/Classification/Ensemble/AdaBoost.php new file mode 100644 index 0000000000000..3d1e41873805d --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Classification/Ensemble/AdaBoost.php @@ -0,0 +1,265 @@ +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 + */ + public function train(array $samples, array $targets) + { + // Initialize usual variables + $this->labels = array_keys(array_count_values($targets)); + if (count($this->labels) != 2) { + throw new \Exception("AdaBoost is a binary classifier and can classify between two classes only"); + } + + // 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 + $classifier = $this->getBestClassifier(); + $errorRate = $this->evaluateClassifier($classifier); + + // Update alpha & weight values at each iteration + $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() + { + $ref = new \ReflectionClass($this->baseClassifier); + if ($this->classifierOptions) { + $classifier = $ref->newInstanceArgs($this->classifierOptions); + } else { + $classifier = $ref->newInstance(); + } + + if (is_subclass_of($classifier, WeightedClassifier::class)) { + $classifier->setSampleWeights($this->weights); + $classifier->train($this->samples, $this->targets); + } else { + list($samples, $targets) = $this->resample(); + $classifier->train($samples, $targets); + } + + return $classifier; + } + + /** + * Resamples the dataset in accordance with the weights and + * returns the new dataset + * + * @return array + */ + protected function resample() + { + $weights = $this->weights; + $std = StandardDeviation::population($weights); + $mean= Mean::arithmetic($weights); + $min = min($weights); + $minZ= (int)round(($min - $mean) / $std); + + $samples = []; + $targets = []; + foreach ($weights as $index => $weight) { + $z = (int)round(($weight - $mean) / $std) - $minZ + 1; + for ($i=0; $i < $z; $i++) { + if (rand(0, 1) == 0) { + continue; + } + $samples[] = $this->samples[$index]; + $targets[] = $this->targets[$index]; + } + } + + return [$samples, $targets]; + } + + /** + * Evaluates the classifier and returns the classification error rate + * + * @param Classifier $classifier + */ + protected function evaluateClassifier(Classifier $classifier) + { + $total = (float) array_sum($this->weights); + $wrong = 0; + foreach ($this->samples as $index => $sample) { + $predicted = $classifier->predict($sample); + if ($predicted != $this->targets[$index]) { + $wrong += $this->weights[$index]; + } + } + + return $wrong / $total; + } + + /** + * 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 Classifier $classifier + * @param float $alpha + */ + protected function updateWeights(Classifier $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/lib/mlbackend/php/phpml/src/Phpml/Classification/Ensemble/Bagging.php b/lib/mlbackend/php/phpml/src/Phpml/Classification/Ensemble/Bagging.php new file mode 100644 index 0000000000000..1bb20273ec728 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Classification/Ensemble/Bagging.php @@ -0,0 +1,194 @@ + 20]; + + /** + * @var array + */ + protected $classifiers; + + /** + * @var float + */ + protected $subsetRatio = 0.7; + + /** + * @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 bootstrap 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(int $index) + { + $samples = []; + $targets = []; + 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]; + } + 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/lib/mlbackend/php/phpml/src/Phpml/Classification/Ensemble/RandomForest.php b/lib/mlbackend/php/phpml/src/Phpml/Classification/Ensemble/RandomForest.php new file mode 100644 index 0000000000000..273eb21aaa4c2 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Classification/Ensemble/RandomForest.php @@ -0,0 +1,153 @@ +setSubsetRatio(1.0); + } + + /** + * 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
+ * + * 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 + */ + 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); + } + + /** + * 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 + * @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; + } + + if ($this->columnNames === null) { + $this->columnNames = range(0, $this->featureCount - 1); + } + + return $classifier + ->setColumnNames($this->columnNames) + ->setNumFeatures($featureCount); + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Classification/KNearestNeighbors.php b/lib/mlbackend/php/phpml/src/Phpml/Classification/KNearestNeighbors.php new file mode 100644 index 0000000000000..b52c95bd5bebc --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Classification/KNearestNeighbors.php @@ -0,0 +1,82 @@ +k = $k; + $this->samples = []; + $this->targets = []; + $this->distanceMetric = $distanceMetric; + } + + /** + * @param array $sample + * + * @return mixed + */ + protected function predictSample(array $sample) + { + $distances = $this->kNeighborsDistances($sample); + + $predictions = array_combine(array_values($this->targets), array_fill(0, count($this->targets), 0)); + + foreach ($distances as $index => $distance) { + ++$predictions[$this->targets[$index]]; + } + + arsort($predictions); + reset($predictions); + + return key($predictions); + } + + /** + * @param array $sample + * + * @return array + * + * @throws \Phpml\Exception\InvalidArgumentException + */ + private function kNeighborsDistances(array $sample) + { + $distances = []; + + foreach ($this->samples as $index => $neighbor) { + $distances[$index] = $this->distanceMetric->distance($sample, $neighbor); + } + + asort($distances); + + return array_slice($distances, 0, $this->k, true); + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/Adaline.php b/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/Adaline.php new file mode 100644 index 0000000000000..f34dc5c40866c --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/Adaline.php @@ -0,0 +1,77 @@ + + * + * 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 (! 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, $normalizeInputs); + } + + /** + * 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) + { + // The cost function is the sum of squares + $callback = function ($weights, $sample, $target) { + $this->weights = $weights; + + $output = $this->output($sample); + $gradient = $output - $target; + $error = $gradient ** 2; + + return [$error, $gradient]; + }; + + $isBatch = $this->trainingType == self::BATCH_TRAINING; + + return parent::runGradientDescent($samples, $targets, $callback, $isBatch); + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/DecisionStump.php b/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/DecisionStump.php new file mode 100644 index 0000000000000..99f982ff11ca2 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/DecisionStump.php @@ -0,0 +1,362 @@ + + * + * 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) + { + $this->givenColumnIndex = $columnIndex; + } + + /** + * @param array $samples + * @param array $targets + * @throws \Exception + */ + protected function trainBinary(array $samples, array $targets, array $labels) + { + $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($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)) { + throw new \Exception("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" + $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($samples[0]) - 1); + if ($this->givenColumnIndex != self::AUTO_SELECT) { + $columns = [$this->givenColumnIndex]; + } + + $bestSplit = [ + '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); + } else { + $split = $this->getBestNominalSplit($samples, $targets, $col); + } + + if ($split['trainingErrorRate'] < $bestSplit['trainingErrorRate']) { + $bestSplit = $split; + } + } + + // Assign determined best values to the stump + foreach ($bestSplit as $name => $value) { + $this->{$name} = $value; + } + } + + /** + * 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 float $count + */ + public function setNumericalSplitCount(float $count) + { + $this->numSplitCount = $count; + } + + /** + * Determines best split point for the given column + * + * @param array $samples + * @param array $targets + * @param int $col + * + * @return array + */ + protected function getBestNumericalSplit(array $samples, array $targets, int $col) + { + $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 + // We choose the second one because it is faster in larger datasets + $minValue = min($values); + $maxValue = max($values); + $stepSize = ($maxValue - $minValue) / $this->numSplitCount; + + $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); + list($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]; + } + + // 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); + if ($errorRate < $split['trainingErrorRate']) { + $split = ['value' => $threshold, 'operator' => $operator, + 'prob' => $prob, 'column' => $col, + 'trainingErrorRate' => $errorRate]; + } + }// for + } + + 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); + $valueCounts = array_count_values($values); + $distinctVals= array_keys($valueCounts); + + $split = null; + + foreach (['=', '!='] as $operator) { + foreach ($distinctVals as $val) { + list($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]; + } + } + } + + 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; + } + + /** + * 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 + { + $wrong = 0.0; + $prob = []; + $leftLabel = $this->binaryLabels[0]; + $rightLabel= $this->binaryLabels[1]; + + foreach ($values as $index => $value) { + if ($this->evaluate($value, $operator, $threshold)) { + $predicted = $leftLabel; + } else { + $predicted = $rightLabel; + } + + $target = $targets[$index]; + if (strval($predicted) != strval($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 / (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) : float + { + $predicted = $this->predictSampleBinary($sample); + if (strval($predicted) == strval($label)) { + return $this->prob[$label]; + } + + return 0.0; + } + + /** + * @param array $sample + * + * @return mixed + */ + protected function predictSampleBinary(array $sample) + { + if ($this->evaluate($sample[$this->column], $this->operator, $this->value)) { + return $this->binaryLabels[0]; + } + + return $this->binaryLabels[1]; + } + + /** + * @return void + */ + protected function resetBinary() + { + } + + /** + * @return string + */ + public function __toString() + { + return "IF $this->column $this->operator $this->value " . + "THEN " . $this->binaryLabels[0] . " ". + "ELSE " . $this->binaryLabels[1]; + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/LogisticRegression.php b/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/LogisticRegression.php new file mode 100644 index 0000000000000..bd56d347a50a1 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/LogisticRegression.php @@ -0,0 +1,281 @@ + + * - '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 + * + * @param array $samples + * @param array $targets + */ + protected function runTraining(array $samples, array $targets) + { + $callback = $this->getCostFunction(); + + switch ($this->trainingType) { + case self::BATCH_TRAINING: + return $this->runGradientDescent($samples, $targets, $callback, true); + + case self::ONLINE_TRAINING: + return $this->runGradientDescent($samples, $targets, $callback, false); + + case self::CONJUGATE_GRAD_TRAINING: + return $this->runConjugateGradient($samples, $targets, $callback); + } + } + + /** + * Executes Conjugate Gradient method to optimize the + * weights of the LogReg model + */ + protected function runConjugateGradient(array $samples, array $targets, \Closure $gradientFunc) + { + if (empty($this->optimizer)) { + $this->optimizer = (new ConjugateGradient($this->featureCount)) + ->setMaxIterations($this->maxIterations); + } + + $this->weights = $this->optimizer->runOptimization($samples, $targets, $gradientFunc); + $this->costValues = $this->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/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/Perceptron.php b/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/Perceptron.php new file mode 100644 index 0000000000000..91ffacf91a450 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/Perceptron.php @@ -0,0 +1,290 @@ + + * + * 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, + 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"); + } + + if ($normalizeInputs) { + $this->normalizer = new Normalizer(Normalizer::NORM_STD); + } + + $this->learningRate = $learningRate; + $this->maxIterations = $maxIterations; + } + + /** + * @param array $samples + * @param array $targets + * @param array $labels + */ + public function partialTrain(array $samples, array $targets, array $labels = []) + { + return $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) { + $this->normalizer->transform($samples); + } + + // 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; + } + + // Set samples and feature count vars + $this->featureCount = count($samples[0]); + + $this->runTraining($samples, $targets); + } + + protected function resetBinary() + { + $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 + * premature convergence.
+ * + * If "false" is given, the optimization procedure will always be executed + * for $maxIterations times + * + * @param bool $enable + */ + public function setEarlyStop(bool $enable = true) + { + $this->enableEarlyStop = $enable; + + return $this; + } + + /** + * Returns the cost values obtained during the training. + * + * @return array + */ + public function getCostValues() + { + return $this->costValues; + } + + /** + * Trains the perceptron model with Stochastic Gradient Descent optimization + * to get the correct set of weights + * + * @param array $samples + * @param array $targets + */ + protected function runTraining(array $samples, array $targets) + { + // The cost function is the sum of squares + $callback = function ($weights, $sample, $target) { + $this->weights = $weights; + + $prediction = $this->outputClass($sample); + $gradient = $prediction - $target; + $error = $gradient**2; + + return [$error, $gradient]; + }; + + $this->runGradientDescent($samples, $targets, $callback); + } + + /** + * Executes a Gradient Descent algorithm for + * the given cost function + * + * @param array $samples + * @param array $targets + */ + protected function runGradientDescent(array $samples, array $targets, \Closure $gradientFunc, bool $isBatch = false) + { + $class = $isBatch ? GD::class : StochasticGD::class; + + if (empty($this->optimizer)) { + $this->optimizer = (new $class($this->featureCount)) + ->setLearningRate($this->learningRate) + ->setMaxIterations($this->maxIterations) + ->setChangeThreshold(1e-6) + ->setEarlyStop($this->enableEarlyStop); + } + + $this->weights = $this->optimizer->runOptimization($samples, $targets, $gradientFunc); + $this->costValues = $this->optimizer->getCostValues(); + } + + /** + * 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 + * + * @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; + } + + /** + * 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 predictSampleBinary(array $sample) + { + $sample = $this->checkNormalizedSample($sample); + + $predictedClass = $this->outputClass($sample); + + return $this->labels[ $predictedClass ]; + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Classification/NaiveBayes.php b/lib/mlbackend/php/phpml/src/Phpml/Classification/NaiveBayes.php new file mode 100644 index 0000000000000..af81b00a086ff --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Classification/NaiveBayes.php @@ -0,0 +1,184 @@ +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($this->targets); + $this->labels = array_keys($labelCounts); + 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 + * @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]) || + $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 = []; + 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) + { + // 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/lib/mlbackend/php/phpml/src/Phpml/Classification/SVC.php b/lib/mlbackend/php/phpml/src/Phpml/Classification/SVC.php new file mode 100644 index 0000000000000..38ae9c450157f --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Classification/SVC.php @@ -0,0 +1,31 @@ +weights = $weights; + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Clustering/Clusterer.php b/lib/mlbackend/php/phpml/src/Phpml/Clustering/Clusterer.php new file mode 100644 index 0000000000000..0c58b2e9badda --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Clustering/Clusterer.php @@ -0,0 +1,15 @@ +epsilon = $epsilon; + $this->minSamples = $minSamples; + $this->distanceMetric = $distanceMetric; + } + + /** + * @param array $samples + * + * @return array + */ + public function cluster(array $samples) + { + $clusters = []; + $visited = []; + + foreach ($samples as $index => $sample) { + if (isset($visited[$index])) { + continue; + } + $visited[$index] = true; + + $regionSamples = $this->getSamplesInRegion($sample, $samples); + if (count($regionSamples) >= $this->minSamples) { + $clusters[] = $this->expandCluster($regionSamples, $visited); + } + } + + return $clusters; + } + + /** + * @param array $localSample + * @param array $samples + * + * @return array + */ + private function getSamplesInRegion($localSample, $samples) + { + $region = []; + + foreach ($samples as $index => $sample) { + if ($this->distanceMetric->distance($localSample, $sample) < $this->epsilon) { + $region[$index] = $sample; + } + } + + return $region; + } + + /** + * @param array $samples + * @param array $visited + * + * @return array + */ + private function expandCluster($samples, &$visited) + { + $cluster = []; + + 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); + } + } + + $cluster[] = $sample; + } + + return $cluster; + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Clustering/FuzzyCMeans.php b/lib/mlbackend/php/phpml/src/Phpml/Clustering/FuzzyCMeans.php new file mode 100644 index 0000000000000..424f2f15094b8 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Clustering/FuzzyCMeans.php @@ -0,0 +1,243 @@ +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/lib/mlbackend/php/phpml/src/Phpml/Clustering/KMeans.php b/lib/mlbackend/php/phpml/src/Phpml/Clustering/KMeans.php new file mode 100644 index 0000000000000..a9e90833f7ff9 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Clustering/KMeans.php @@ -0,0 +1,60 @@ +clustersNumber = $clustersNumber; + $this->initialization = $initialization; + } + + /** + * @param array $samples + * + * @return array + */ + public function cluster(array $samples) + { + $space = new Space(count($samples[0])); + foreach ($samples as $sample) { + $space->addPoint($sample); + } + + $clusters = []; + foreach ($space->cluster($this->clustersNumber, $this->initialization) as $cluster) { + $clusters[] = $cluster->getPoints(); + } + + return $clusters; + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Clustering/KMeans/Cluster.php b/lib/mlbackend/php/phpml/src/Phpml/Clustering/KMeans/Cluster.php new file mode 100644 index 0000000000000..7cb9f126e3c52 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Clustering/KMeans/Cluster.php @@ -0,0 +1,147 @@ +space = $space; + $this->points = new SplObjectStorage(); + } + + /** + * @return array + */ + public function getPoints() + { + $points = []; + foreach ($this->points as $point) { + $points[] = $point->toArray(); + } + + return $points; + } + + /** + * @return array + */ + public function toArray() + { + return [ + 'centroid' => parent::toArray(), + 'points' => $this->getPoints(), + ]; + } + + /** + * @param Point $point + * + * @return Point + * + * @throws \LogicException + */ + public function attach(Point $point) + { + if ($point instanceof self) { + throw new LogicException('cannot attach a cluster to another'); + } + + $this->points->attach($point); + + return $point; + } + + /** + * @param Point $point + * + * @return Point + */ + public function detach(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); + } + + public function updateCentroid() + { + if (!$count = count($this->points)) { + return; + } + + $centroid = $this->space->newPoint(array_fill(0, $this->dimension, 0)); + + foreach ($this->points as $point) { + for ($n = 0; $n < $this->dimension; ++$n) { + $centroid->coordinates[$n] += $point->coordinates[$n]; + } + } + + for ($n = 0; $n < $this->dimension; ++$n) { + $this->coordinates[$n] = $centroid->coordinates[$n] / $count; + } + } + + /** + * @return Point[]|SplObjectStorage + */ + public function getIterator() + { + return $this->points; + } + + /** + * @return mixed + */ + public function count() + { + return count($this->points); + } + + /** + * @param array $newCoordinates + */ + public function setCoordinates(array $newCoordinates) + { + $this->coordinates = $newCoordinates; + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Clustering/KMeans/Point.php b/lib/mlbackend/php/phpml/src/Phpml/Clustering/KMeans/Point.php new file mode 100644 index 0000000000000..ce1c44ee5f947 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Clustering/KMeans/Point.php @@ -0,0 +1,124 @@ +dimension = count($coordinates); + $this->coordinates = $coordinates; + } + + /** + * @return array + */ + public function toArray() + { + return $this->coordinates; + } + + /** + * @param Point $point + * @param bool $precise + * + * @return int|mixed + */ + public function getDistanceWith(self $point, $precise = true) + { + $distance = 0; + for ($n = 0; $n < $this->dimension; ++$n) { + $difference = $this->coordinates[$n] - $point->coordinates[$n]; + $distance += $difference * $difference; + } + + return $precise ? sqrt((float) $distance) : $distance; + } + + /** + * @param array $points + * + * @return mixed + */ + public function getClosest(array $points) + { + foreach ($points as $point) { + $distance = $this->getDistanceWith($point, false); + + if (!isset($minDistance)) { + $minDistance = $distance; + $minPoint = $point; + continue; + } + + if ($distance < $minDistance) { + $minDistance = $distance; + $minPoint = $point; + } + } + + return $minPoint; + } + + /** + * @return array + */ + public function getCoordinates() + { + return $this->coordinates; + } + + /** + * @param mixed $offset + * + * @return bool + */ + public function offsetExists($offset) + { + return isset($this->coordinates[$offset]); + } + + /** + * @param mixed $offset + * + * @return mixed + */ + public function offsetGet($offset) + { + return $this->coordinates[$offset]; + } + + /** + * @param mixed $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) + { + $this->coordinates[$offset] = $value; + } + + /** + * @param mixed $offset + */ + public function offsetUnset($offset) + { + unset($this->coordinates[$offset]); + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Clustering/KMeans/Space.php b/lib/mlbackend/php/phpml/src/Phpml/Clustering/KMeans/Space.php new file mode 100644 index 0000000000000..5a4d5305e7360 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Clustering/KMeans/Space.php @@ -0,0 +1,259 @@ +dimension = $dimension; + } + + /** + * @return array + */ + public function toArray() + { + $points = []; + foreach ($this as $point) { + $points[] = $point->toArray(); + } + + return ['points' => $points]; + } + + /** + * @param array $coordinates + * + * @return Point + */ + public function newPoint(array $coordinates) + { + if (count($coordinates) != $this->dimension) { + throw new LogicException('('.implode(',', $coordinates).') is not a point of this space'); + } + + return new Point($coordinates); + } + + /** + * @param array $coordinates + * @param null $data + */ + public function addPoint(array $coordinates, $data = null) + { + $this->attach($this->newPoint($coordinates), $data); + } + + /** + * @param Point $point + * @param null $data + */ + public function attach($point, $data = null) + { + if (!$point instanceof Point) { + throw new InvalidArgumentException('can only attach points to spaces'); + } + + parent::attach($point, $data); + } + + /** + * @return int + */ + public function getDimension() + { + return $this->dimension; + } + + /** + * @return array|bool + */ + public function getBoundaries() + { + if (!count($this)) { + return false; + } + + $min = $this->newPoint(array_fill(0, $this->dimension, null)); + $max = $this->newPoint(array_fill(0, $this->dimension, null)); + + 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]; + } + } + + return [$min, $max]; + } + + /** + * @param Point $min + * @param Point $max + * + * @return Point + */ + public function getRandomPoint(Point $min, Point $max) + { + $point = $this->newPoint(array_fill(0, $this->dimension, null)); + + for ($n = 0; $n < $this->dimension; ++$n) { + $point[$n] = random_int($min[$n], $max[$n]); + } + + return $point; + } + + /** + * @param int $clustersNumber + * @param int $initMethod + * + * @return array|Cluster[] + */ + public function cluster(int $clustersNumber, int $initMethod = KMeans::INIT_RANDOM) + { + $clusters = $this->initializeClusters($clustersNumber, $initMethod); + + do { + } while (!$this->iterate($clusters)); + + return $clusters; + } + + /** + * @param $clustersNumber + * @param $initMethod + * + * @return array|Cluster[] + */ + protected function initializeClusters(int $clustersNumber, int $initMethod) + { + switch ($initMethod) { + case KMeans::INIT_RANDOM: + $clusters = $this->initializeRandomClusters($clustersNumber); + break; + + case KMeans::INIT_KMEANS_PLUS_PLUS: + $clusters = $this->initializeKMPPClusters($clustersNumber); + break; + } + $clusters[0]->attachAll($this); + + return $clusters; + } + + /** + * @param $clusters + * + * @return bool + */ + protected function iterate($clusters) + { + $convergence = true; + + $attach = new SplObjectStorage(); + $detach = new SplObjectStorage(); + + foreach ($clusters as $cluster) { + foreach ($cluster as $point) { + $closest = $point->getClosest($clusters); + + if ($closest !== $cluster) { + isset($attach[$closest]) || $attach[$closest] = new SplObjectStorage(); + isset($detach[$cluster]) || $detach[$cluster] = new SplObjectStorage(); + + $attach[$closest]->attach($point); + $detach[$cluster]->attach($point); + + $convergence = false; + } + } + } + + foreach ($attach as $cluster) { + $cluster->attachAll($attach[$cluster]); + } + + foreach ($detach as $cluster) { + $cluster->detachAll($detach[$cluster]); + } + + foreach ($clusters as $cluster) { + $cluster->updateCentroid(); + } + + return $convergence; + } + + /** + * @param int $clustersNumber + * + * @return array + */ + private function initializeRandomClusters(int $clustersNumber) + { + $clusters = []; + list($min, $max) = $this->getBoundaries(); + + for ($n = 0; $n < $clustersNumber; ++$n) { + $clusters[] = new Cluster($this, $this->getRandomPoint($min, $max)->getCoordinates()); + } + + return $clusters; + } + + /** + * @param int $clustersNumber + * + * @return array + */ + protected function initializeKMPPClusters(int $clustersNumber) + { + $clusters = []; + $this->rewind(); + + $clusters[] = new Cluster($this, $this->current()->getCoordinates()); + + $distances = new SplObjectStorage(); + + for ($i = 1; $i < $clustersNumber; ++$i) { + $sum = 0; + foreach ($this as $point) { + $distance = $point->getDistanceWith($point->getClosest($clusters)); + $sum += $distances[$point] = $distance; + } + + $sum = random_int(0, (int) $sum); + foreach ($this as $point) { + if (($sum -= $distances[$point]) > 0) { + continue; + } + + $clusters[] = new Cluster($this, $point->getCoordinates()); + break; + } + } + + return $clusters; + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/CrossValidation/RandomSplit.php b/lib/mlbackend/php/phpml/src/Phpml/CrossValidation/RandomSplit.php new file mode 100644 index 0000000000000..69c44c122491b --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/CrossValidation/RandomSplit.php @@ -0,0 +1,30 @@ +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) - $testCount) / $datasetSize >= $testSize ? 'train' : 'test'; + + $this->{$setName.'Samples'}[] = $samples[$key]; + $this->{$setName.'Labels'}[] = $labels[$key]; + } + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/CrossValidation/Split.php b/lib/mlbackend/php/phpml/src/Phpml/CrossValidation/Split.php new file mode 100644 index 0000000000000..add181cb0bed7 --- /dev/null +++ b/lib/mlbackend/php/phpml/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/lib/mlbackend/php/phpml/src/Phpml/CrossValidation/StratifiedRandomSplit.php b/lib/mlbackend/php/phpml/src/Phpml/CrossValidation/StratifiedRandomSplit.php new file mode 100644 index 0000000000000..e6c80a2f3d58b --- /dev/null +++ b/lib/mlbackend/php/phpml/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/lib/mlbackend/php/phpml/src/Phpml/Dataset/ArrayDataset.php b/lib/mlbackend/php/phpml/src/Phpml/Dataset/ArrayDataset.php new file mode 100644 index 0000000000000..6f765fe571532 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Dataset/ArrayDataset.php @@ -0,0 +1,52 @@ +samples = $samples; + $this->targets = $targets; + } + + /** + * @return array + */ + public function getSamples(): array + { + return $this->samples; + } + + /** + * @return array + */ + public function getTargets(): array + { + return $this->targets; + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Dataset/CsvDataset.php b/lib/mlbackend/php/phpml/src/Phpml/Dataset/CsvDataset.php new file mode 100644 index 0000000000000..8bcd3c490343a --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Dataset/CsvDataset.php @@ -0,0 +1,54 @@ +columnNames = array_slice($data, 0, $features); + } else { + $this->columnNames = range(0, $features - 1); + } + + while (($data = fgetcsv($handle, 1000, $delimiter)) !== false) { + $this->samples[] = array_slice($data, 0, $features); + $this->targets[] = $data[$features]; + } + fclose($handle); + } + + /** + * @return array + */ + public function getColumnNames() + { + return $this->columnNames; + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Dataset/Dataset.php b/lib/mlbackend/php/phpml/src/Phpml/Dataset/Dataset.php new file mode 100644 index 0000000000000..f851d8527dfdf --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Dataset/Dataset.php @@ -0,0 +1,18 @@ +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/lib/mlbackend/php/phpml/src/Phpml/DimensionReduction/KernelPCA.php b/lib/mlbackend/php/phpml/src/Phpml/DimensionReduction/KernelPCA.php new file mode 100644 index 0000000000000..86070c72bbc6c --- /dev/null +++ b/lib/mlbackend/php/phpml/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/lib/mlbackend/php/phpml/src/Phpml/DimensionReduction/PCA.php b/lib/mlbackend/php/phpml/src/Phpml/DimensionReduction/PCA.php new file mode 100644 index 0000000000000..422dae4d7874c --- /dev/null +++ b/lib/mlbackend/php/phpml/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/lib/mlbackend/php/phpml/src/Phpml/Estimator.php b/lib/mlbackend/php/phpml/src/Phpml/Estimator.php new file mode 100644 index 0000000000000..8b98bb637c7fa --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Estimator.php @@ -0,0 +1,21 @@ +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/lib/mlbackend/php/phpml/src/Phpml/FeatureExtraction/StopWords/English.php b/lib/mlbackend/php/phpml/src/Phpml/FeatureExtraction/StopWords/English.php new file mode 100644 index 0000000000000..fab079b1221f4 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/FeatureExtraction/StopWords/English.php @@ -0,0 +1,33 @@ +stopWords); + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/FeatureExtraction/StopWords/Polish.php b/lib/mlbackend/php/phpml/src/Phpml/FeatureExtraction/StopWords/Polish.php new file mode 100644 index 0000000000000..e452ebf2551eb --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/FeatureExtraction/StopWords/Polish.php @@ -0,0 +1,30 @@ +stopWords); + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/FeatureExtraction/TfIdfTransformer.php b/lib/mlbackend/php/phpml/src/Phpml/FeatureExtraction/TfIdfTransformer.php new file mode 100644 index 0000000000000..93357752444fe --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/FeatureExtraction/TfIdfTransformer.php @@ -0,0 +1,66 @@ +fit($samples); + } + } + + /** + * @param array $samples + */ + public function fit(array $samples) + { + $this->countTokensFrequency($samples); + + $count = count($samples); + foreach ($this->idf as &$value) { + $value = log((float)($count / $value), 10.0); + } + } + + /** + * @param array $samples + */ + public function transform(array &$samples) + { + foreach ($samples as &$sample) { + foreach ($sample as $index => &$feature) { + $feature *= $this->idf[$index]; + } + } + } + + /** + * @param array $samples + */ + 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/lib/mlbackend/php/phpml/src/Phpml/FeatureExtraction/TokenCountVectorizer.php b/lib/mlbackend/php/phpml/src/Phpml/FeatureExtraction/TokenCountVectorizer.php new file mode 100644 index 0000000000000..f5fab21c29f63 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/FeatureExtraction/TokenCountVectorizer.php @@ -0,0 +1,214 @@ +tokenizer = $tokenizer; + $this->stopWords = $stopWords; + $this->minDF = $minDF; + + $this->vocabulary = []; + $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) { + $this->transformSample($sample); + } + + $this->checkDocumentFrequency($samples); + } + + /** + * @return array + */ + public function getVocabulary() + { + return array_flip($this->vocabulary); + } + + /** + * @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); + } + } + } + + /** + * @param string $sample + */ + private function transformSample(string &$sample) + { + $counts = []; + $tokens = $this->tokenizer->tokenize($sample); + + foreach ($tokens as $token) { + $index = $this->getTokenIndex($token); + if (false !== $index) { + $this->updateFrequency($token); + if (!isset($counts[$index])) { + $counts[$index] = 0; + } + + ++$counts[$index]; + } + } + + foreach ($this->vocabulary as $index) { + if (!isset($counts[$index])) { + $counts[$index] = 0; + } + } + + ksort($counts); + + $sample = $counts; + } + + /** + * @param string $token + * + * @return int|bool + */ + private function getTokenIndex(string $token) + { + if ($this->isStopWord($token)) { + return false; + } + + return $this->vocabulary[$token] ?? false; + } + + /** + * @param string $token + */ + private function addTokenToVocabulary(string $token) + { + if ($this->isStopWord($token)) { + return; + } + + if (!isset($this->vocabulary[$token])) { + $this->vocabulary[$token] = count($this->vocabulary); + } + } + + /** + * @param string $token + * + * @return bool + */ + private function isStopWord(string $token): bool + { + return $this->stopWords && $this->stopWords->isStopWord($token); + } + + /** + * @param string $token + */ + private function updateFrequency(string $token) + { + if (!isset($this->frequencies[$token])) { + $this->frequencies[$token] = 0; + } + + ++$this->frequencies[$token]; + } + + /** + * @param array $samples + */ + private function checkDocumentFrequency(array &$samples) + { + if ($this->minDF > 0) { + $beyondMinimum = $this->getBeyondMinimumIndexes(count($samples)); + foreach ($samples as &$sample) { + $this->resetBeyondMinimum($sample, $beyondMinimum); + } + } + } + + /** + * @param array $sample + * @param array $beyondMinimum + */ + private function resetBeyondMinimum(array &$sample, array $beyondMinimum) + { + foreach ($beyondMinimum as $index) { + $sample[$index] = 0; + } + } + + /** + * @param int $samplesCount + * + * @return array + */ + private function getBeyondMinimumIndexes(int $samplesCount) + { + $indexes = []; + foreach ($this->frequencies as $token => $frequency) { + if (($frequency / $samplesCount) < $this->minDF) { + $indexes[] = $this->getTokenIndex($token); + } + } + + return $indexes; + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Helper/OneVsRest.php b/lib/mlbackend/php/phpml/src/Phpml/Helper/OneVsRest.php new file mode 100644 index 0000000000000..e207c46b5df2a --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Helper/OneVsRest.php @@ -0,0 +1,197 @@ +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 = []) + { + + // 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; + $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($targets, $label) + { + $notLabel = "not_$label"; + foreach ($targets as $key => $target) { + $targets[$key] = $target == $label ? $label : $notLabel; + } + + $labels = [$label, $notLabel]; + return [$targets, $labels]; + } + + + /** + * @param array $sample + * + * @return mixed + */ + protected function predictSample(array $sample) + { + if (count($this->allLabels) == 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 + * @param array $labels + */ + 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 + * 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/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/ConjugateGradient.php b/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/ConjugateGradient.php new file mode 100644 index 0000000000000..18ae89a09e8c4 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/ConjugateGradient.php @@ -0,0 +1,361 @@ +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; + } + } + + $this->clear(); + + 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/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/GD.php b/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/GD.php new file mode 100644 index 0000000000000..8974c8e769cd4 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/GD.php @@ -0,0 +1,121 @@ +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; + } + } + + $this->clear(); + + 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]); + } + } + } + + /** + * Clears the optimizer internal vars after the optimization process. + * + * @return void + */ + protected function clear() + { + $this->sampleCount = null; + parent::clear(); + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/Optimizer.php b/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/Optimizer.php new file mode 100644 index 0000000000000..9ef4c4d094969 --- /dev/null +++ b/lib/mlbackend/php/phpml/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/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/StochasticGD.php b/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/StochasticGD.php new file mode 100644 index 0000000000000..e9e318a8a5f70 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/StochasticGD.php @@ -0,0 +1,285 @@ + + * + * 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; + } + } + + $this->clear(); + + // 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; + } + + /** + * Clears the optimizer internal vars after the optimization process. + * + * @return void + */ + protected function clear() + { + $this->samples = []; + $this->targets = []; + $this->gradientCb = null; + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Helper/Predictable.php b/lib/mlbackend/php/phpml/src/Phpml/Helper/Predictable.php new file mode 100644 index 0000000000000..097edaabeaba4 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Helper/Predictable.php @@ -0,0 +1,34 @@ +predictSample($samples); + } else { + $predicted = []; + foreach ($samples as $index => $sample) { + $predicted[$index] = $this->predictSample($sample); + } + } + + return $predicted; + } + + /** + * @param array $sample + * + * @return mixed + */ + abstract protected function predictSample(array $sample); +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Helper/Trainable.php b/lib/mlbackend/php/phpml/src/Phpml/Helper/Trainable.php new file mode 100644 index 0000000000000..3d011ac47f85f --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Helper/Trainable.php @@ -0,0 +1,28 @@ +samples = array_merge($this->samples, $samples); + $this->targets = array_merge($this->targets, $targets); + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/IncrementalEstimator.php b/lib/mlbackend/php/phpml/src/Phpml/IncrementalEstimator.php new file mode 100644 index 0000000000000..fc6912d110933 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/IncrementalEstimator.php @@ -0,0 +1,16 @@ + $val) { + $distance += ($val - $b[$i]) ** 2; + } + + 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/lib/mlbackend/php/phpml/src/Phpml/Math/Distance/Manhattan.php b/lib/mlbackend/php/phpml/src/Phpml/Math/Distance/Manhattan.php new file mode 100644 index 0000000000000..b6f6eb8bdd2cf --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Math/Distance/Manhattan.php @@ -0,0 +1,35 @@ +lambda = $lambda; + } + + /** + * @param array $a + * @param array $b + * + * @return float + * + * @throws InvalidArgumentException + */ + public function distance(array $a, array $b): float + { + if (count($a) !== count($b)) { + throw InvalidArgumentException::arraySizeNotMatch(); + } + + $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); + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Math/Kernel.php b/lib/mlbackend/php/phpml/src/Phpml/Math/Kernel.php new file mode 100644 index 0000000000000..6d1461fde0c5c --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Math/Kernel.php @@ -0,0 +1,16 @@ +gamma = $gamma; + } + + /** + * @param float $a + * @param float $b + * + * @return float + */ + public function compute($a, $b) + { + $score = 2 * Product::scalar($a, $b); + $squares = Product::scalar($a, $a) + Product::scalar($b, $b); + $result = exp(-$this->gamma * ($squares - $score)); + + return $result; + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php b/lib/mlbackend/php/phpml/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php new file mode 100644 index 0000000000000..27557bbd83aa7 --- /dev/null +++ b/lib/mlbackend/php/phpml/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/lib/mlbackend/php/phpml/src/Phpml/Math/Matrix.php b/lib/mlbackend/php/phpml/src/Phpml/Math/Matrix.php new file mode 100644 index 0000000000000..25101f3f4ab1a --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Math/Matrix.php @@ -0,0 +1,385 @@ +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) { + if (count($matrix[$i]) !== $this->columns) { + throw InvalidArgumentException::matrixDimensionsDidNotMatch(); + } + } + } + + $this->matrix = $matrix; + } + + /** + * @param array $array + * + * @return Matrix + */ + public static function fromFlatArray(array $array) + { + $matrix = []; + foreach ($array as $value) { + $matrix[] = [$value]; + } + + return new self($matrix); + } + + /** + * @return array + */ + public function toArray() + { + return $this->matrix; + } + + /** + * @return float + */ + public function toScalar() + { + return $this->matrix[0][0]; + } + + /** + * @return int + */ + public function getRows() + { + return $this->rows; + } + + /** + * @return int + */ + public function getColumns() + { + return $this->columns; + } + + /** + * @param $column + * + * @return array + * + * @throws MatrixException + */ + public function getColumnValues($column) + { + if ($column >= $this->columns) { + throw MatrixException::columnOutOfRange(); + } + + return array_column($this->matrix, $column); + } + + + /** + * @return float|int + * + * @throws MatrixException + */ + public function getDeterminant() + { + if ($this->determinant) { + return $this->determinant; + } + + if (!$this->isSquare()) { + 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; + } + + /** + * @return bool + */ + public function isSquare() + { + return $this->columns === $this->rows; + } + + /** + * @return Matrix + */ + public function transpose() + { + if ($this->rows == 1) { + $matrix = array_map(function ($el) { + return [$el]; + }, $this->matrix[0]); + } else { + $matrix = array_map(null, ...$this->matrix); + } + + return new self($matrix, false); + } + + /** + * @param Matrix $matrix + * + * @return Matrix + * + * @throws InvalidArgumentException + */ + public function multiply(Matrix $matrix) + { + if ($this->columns != $matrix->getRows()) { + throw InvalidArgumentException::inconsistentMatrixSupplied(); + } + + $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]; + } + } + } + + return new self($product, false); + } + + /** + * @param $value + * + * @return Matrix + */ + public function divideByScalar($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); + } + + /** + * @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 + * + * @throws MatrixException + */ + public function inverse() + { + if (!$this->isSquare()) { + throw MatrixException::notSquareMatrix(); + } + + if ($this->isSingular()) { + throw MatrixException::singularMatrix(); + } + + $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()); + } + + /** + * @param int $row + * @param int $column + * + * @return Matrix + */ + public function crossOut(int $row, int $column) + { + $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); + } + + /** + * @return bool + */ + public function isSingular() : bool + { + return 0 == $this->getDeterminant(); + } + + /** + * Returns the transpose of given array + * + * @param array $array + * + * @return array + */ + public static function transposeArray(array $array) + { + return (new Matrix($array, false))->transpose()->toArray(); + } + + /** + * Returns the dot product of two arrays
+ * Matrix::dot(x, y) ==> x.y' + * + * @param array $array1 + * @param array $array2 + * + * @return array + */ + public static function dot(array $array1, array $array2) + { + $m1 = new Matrix($array1, false); + $m2 = new Matrix($array2, false); + + return $m1->multiply($m2->transpose())->toArray()[0]; + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Math/Product.php b/lib/mlbackend/php/phpml/src/Phpml/Math/Product.php new file mode 100644 index 0000000000000..35ef79cbfaa77 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Math/Product.php @@ -0,0 +1,26 @@ + $value) { + if (is_numeric($value) && is_numeric($b[$index])) { + $product += $value * $b[$index]; + } + } + + return $product; + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Math/Set.php b/lib/mlbackend/php/phpml/src/Phpml/Math/Set.php new file mode 100644 index 0000000000000..20fc78099e677 --- /dev/null +++ b/lib/mlbackend/php/phpml/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/lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/Correlation.php b/lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/Correlation.php new file mode 100644 index 0000000000000..0f60223fc0194 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/Correlation.php @@ -0,0 +1,45 @@ + $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/lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/Gaussian.php b/lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/Gaussian.php new file mode 100644 index 0000000000000..df27f076dc6f2 --- /dev/null +++ b/lib/mlbackend/php/phpml/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/lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/Mean.php b/lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/Mean.php new file mode 100644 index 0000000000000..581a12259033f --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/Mean.php @@ -0,0 +1,75 @@ + $label) { + if ($label == $predictedLabels[$index]) { + ++$score; + } + } + + if ($normalize) { + $score /= count($actualLabels); + } + + return $score; + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Metric/ClassificationReport.php b/lib/mlbackend/php/phpml/src/Phpml/Metric/ClassificationReport.php new file mode 100644 index 0000000000000..c7cc147898f9c --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Metric/ClassificationReport.php @@ -0,0 +1,183 @@ +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]; + } + } + + $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] = $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]); + } + } + + private function computeAverage() + { + foreach (['precision', 'recall', 'f1score'] as $metric) { + $values = array_filter($this->$metric); + if (0 == count($values)) { + $this->average[$metric] = 0.0; + continue; + } + $this->average[$metric] = array_sum($values) / count($values); + } + } + + /** + * @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 + * + * @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 $actualLabels + * @param array $predictedLabels + * + * @return array + */ + private static function getLabelIndexedArray(array $actualLabels, array $predictedLabels): array + { + $labels = array_values(array_unique(array_merge($actualLabels, $predictedLabels))); + sort($labels); + $labels = array_combine($labels, array_fill(0, count($labels), 0)); + + return $labels; + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Metric/ConfusionMatrix.php b/lib/mlbackend/php/phpml/src/Phpml/Metric/ConfusionMatrix.php new file mode 100644 index 0000000000000..664e355e945ee --- /dev/null +++ b/lib/mlbackend/php/phpml/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/lib/mlbackend/php/phpml/src/Phpml/ModelManager.php b/lib/mlbackend/php/phpml/src/Phpml/ModelManager.php new file mode 100644 index 0000000000000..c03d0ed25c72a --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/ModelManager.php @@ -0,0 +1,54 @@ += 0 ? 1.0 : 0.0; + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php new file mode 100644 index 0000000000000..0e3e848d0f1d2 --- /dev/null +++ b/lib/mlbackend/php/phpml/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/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php new file mode 100644 index 0000000000000..23ac7ce12f854 --- /dev/null +++ b/lib/mlbackend/php/phpml/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/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Layer.php b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Layer.php new file mode 100644 index 0000000000000..632bc009efd9b --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Layer.php @@ -0,0 +1,65 @@ +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 + */ + public function addNode(Node $node) + { + $this->nodes[] = $node; + } + + /** + * @return Node[] + */ + public function getNodes() + { + return $this->nodes; + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Network.php b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Network.php new file mode 100644 index 0000000000000..c6c25af2e99ee --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Network.php @@ -0,0 +1,30 @@ +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 getOutput(): array + { + $result = []; + foreach ($this->getOutputLayer()->getNodes() as $neuron) { + $result[] = $neuron->getOutput(); + } + + return $result; + } + + /** + * @param mixed $input + * + * @return $this + */ + public function setInput($input) + { + $firstLayer = $this->layers[0]; + + foreach ($firstLayer->getNodes() as $key => $neuron) { + 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/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php new file mode 100644 index 0000000000000..04664f9c7db8d --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -0,0 +1,95 @@ +addInputLayer(array_shift($layers)); + $this->addNeuronLayers($layers, $activationFunction); + $this->addBiasNodes(); + $this->generateSynapses(); + } + + /** + * @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) { + $this->addLayer(new Layer($neurons, Neuron::class, $activationFunction)); + } + } + + 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)); + } + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Node.php b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Node.php new file mode 100644 index 0000000000000..65d5cdcdcb439 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Node.php @@ -0,0 +1,13 @@ +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/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Node/Neuron.php b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Node/Neuron.php new file mode 100644 index 0000000000000..519443844a5b5 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Node/Neuron.php @@ -0,0 +1,75 @@ +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/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php new file mode 100644 index 0000000000000..b9c036fb21176 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php @@ -0,0 +1,70 @@ +node = $node; + $this->weight = $weight ?: $this->generateRandomWeight(); + } + + /** + * @return float + */ + protected function generateRandomWeight(): float + { + return 1 / random_int(5, 25) * (random_int(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/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Training.php b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Training.php new file mode 100644 index 0000000000000..d876af2e50f46 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Training.php @@ -0,0 +1,16 @@ +network = $network; + $this->theta = $theta; + } + + /** + * @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) + { + 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; + } + + /** + * @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); + + for ($i = $layersNumber; $i > 1; --$i) { + foreach ($layers[$i - 1]->getNodes() as $key => $neuron) { + if ($neuron instanceof Neuron) { + $sigma = $this->getSigma($neuron, $target, $key, $i == $layersNumber); + foreach ($neuron->getSynapses() as $synapse) { + $synapse->changeWeight($this->theta * $sigma * $synapse->getNode()->getOutput()); + } + } + } + } + } + + /** + * @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(Neuron $neuron): float + { + $sigma = 0.0; + + foreach ($this->sigmas 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/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php new file mode 100644 index 0000000000000..62e2e7a56411f --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php @@ -0,0 +1,64 @@ +neuron = $neuron; + $this->sigma = $sigma; + } + + /** + * @return Neuron + */ + public function getNeuron() + { + return $this->neuron; + } + + /** + * @return float + */ + public function getSigma() + { + return $this->sigma; + } + + /** + * @param Neuron $neuron + * + * @return float + */ + public function getSigmaForNeuron(Neuron $neuron): float + { + $sigma = 0.0; + + foreach ($this->neuron->getSynapses() as $synapse) { + if ($synapse->getNode() == $neuron) { + $sigma += $synapse->getWeight() * $this->getSigma(); + } + } + + return $sigma; + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Pipeline.php b/lib/mlbackend/php/phpml/src/Phpml/Pipeline.php new file mode 100644 index 0000000000000..a6b3d562673bb --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Pipeline.php @@ -0,0 +1,106 @@ +addTransformer($transformer); + } + + $this->estimator = $estimator; + } + + /** + * @param Transformer $transformer + */ + public function addTransformer(Transformer $transformer) + { + $this->transformers[] = $transformer; + } + + /** + * @param Estimator $estimator + */ + public function setEstimator(Estimator $estimator) + { + $this->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) + { + $this->fitTransformers($samples); + $this->transformSamples($samples); + $this->estimator->train($samples, $targets); + } + + /** + * @param array $samples + * + * @return mixed + */ + public function predict(array $samples) + { + $this->transformSamples($samples); + + return $this->estimator->predict($samples); + } + + /** + * @param array $samples + */ + private function fitTransformers(array &$samples) + { + foreach ($this->transformers as $transformer) { + $transformer->fit($samples); + } + } + + /** + * @param array $samples + */ + private function transformSamples(array &$samples) + { + foreach ($this->transformers as $transformer) { + $transformer->transform($samples); + } + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Preprocessing/Imputer.php b/lib/mlbackend/php/phpml/src/Phpml/Preprocessing/Imputer.php new file mode 100644 index 0000000000000..805d3f6209629 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Preprocessing/Imputer.php @@ -0,0 +1,99 @@ +missingValue = $missingValue; + $this->strategy = $strategy; + $this->axis = $axis; + $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) { + $this->preprocessSample($sample); + } + } + + /** + * @param array $sample + */ + private function preprocessSample(array &$sample) + { + foreach ($sample as $column => &$value) { + if ($value === $this->missingValue) { + $value = $this->strategy->replaceValue($this->getAxis($column, $sample)); + } + } + } + + /** + * @param int $column + * @param array $currentSample + * + * @return array + */ + private function getAxis(int $column, array $currentSample): array + { + if (self::AXIS_ROW === $this->axis) { + return array_diff($currentSample, [$this->missingValue]); + } + + $axis = []; + foreach ($this->samples as $sample) { + if ($sample[$column] !== $this->missingValue) { + $axis[] = $sample[$column]; + } + } + + return $axis; + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Preprocessing/Imputer/Strategy.php b/lib/mlbackend/php/phpml/src/Phpml/Preprocessing/Imputer/Strategy.php new file mode 100644 index 0000000000000..9125e06fb5cd7 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Preprocessing/Imputer/Strategy.php @@ -0,0 +1,15 @@ +norm = $norm; + } + + /** + * @param array $samples + */ + public function fit(array $samples) + { + 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; + } + + /** + * @param array $samples + */ + public function transform(array &$samples) + { + $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); + } + } + + /** + * @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 /= $norm1; + } + } + } + + /** + * @param array $sample + */ + private function normalizeL2(array &$sample) + { + $norm2 = 0; + foreach ($sample as $feature) { + $norm2 += $feature * $feature; + } + $norm2 = sqrt((float)$norm2); + + if (0 == $norm2) { + $sample = array_fill(0, count($sample), 1); + } else { + foreach ($sample as &$feature) { + $feature /= $norm2; + } + } + } + + /** + * @param array $sample + */ + private function normalizeSTD(array &$sample) + { + foreach ($sample as $i => $val) { + 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/lib/mlbackend/php/phpml/src/Phpml/Preprocessing/Preprocessor.php b/lib/mlbackend/php/phpml/src/Phpml/Preprocessing/Preprocessor.php new file mode 100644 index 0000000000000..3ec1566b88c5b --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Preprocessing/Preprocessor.php @@ -0,0 +1,11 @@ +samples = array_merge($this->samples, $samples); + $this->targets = array_merge($this->targets, $targets); + + $this->computeCoefficients(); + } + + /** + * @param array $sample + * + * @return mixed + */ + public function predictSample(array $sample) + { + $result = $this->intercept; + foreach ($this->coefficients as $index => $coefficient) { + $result += $coefficient * $sample[$index]; + } + + return $result; + } + + /** + * @return array + */ + public function getCoefficients() + { + return $this->coefficients; + } + + /** + * @return float + */ + public function getIntercept() + { + return $this->intercept; + } + + /** + * coefficient(b) = (X'X)-1X'Y. + */ + private function computeCoefficients() + { + $samplesMatrix = $this->getSamplesMatrix(); + $targetsMatrix = $this->getTargetsMatrix(); + + $ts = $samplesMatrix->transpose()->multiply($samplesMatrix)->inverse(); + $tf = $samplesMatrix->transpose()->multiply($targetsMatrix); + + $this->coefficients = $ts->multiply($tf)->getColumnValues(0); + $this->intercept = array_shift($this->coefficients); + } + + /** + * Add one dimension for intercept calculation. + * + * @return Matrix + */ + private function getSamplesMatrix() + { + $samples = []; + foreach ($this->samples as $sample) { + array_unshift($sample, 1); + $samples[] = $sample; + } + + return new Matrix($samples); + } + + /** + * @return Matrix + */ + private function getTargetsMatrix() + { + if (is_array($this->targets[0])) { + return new Matrix($this->targets); + } + + return Matrix::fromFlatArray($this->targets); + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Regression/MLPRegressor.php b/lib/mlbackend/php/phpml/src/Phpml/Regression/MLPRegressor.php new file mode 100644 index 0000000000000..72e6a81e52b49 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Regression/MLPRegressor.php @@ -0,0 +1,80 @@ +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/lib/mlbackend/php/phpml/src/Phpml/Regression/Regression.php b/lib/mlbackend/php/phpml/src/Phpml/Regression/Regression.php new file mode 100644 index 0000000000000..542685ccb28b5 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Regression/Regression.php @@ -0,0 +1,11 @@ + $label) { + $set .= sprintf('%s %s %s', ($targets ? $label : $numericLabels[$label]), self::sampleRow($samples[$index]), PHP_EOL); + } + + return $set; + } + + /** + * @param array $samples + * + * @return string + */ + public static function testSet(array $samples): string + { + if (!is_array($samples[0])) { + $samples = [$samples]; + } + + $set = ''; + foreach ($samples as $sample) { + $set .= sprintf('0 %s %s', self::sampleRow($sample), PHP_EOL); + } + + return $set; + } + + /** + * @param string $rawPredictions + * @param array $labels + * + * @return array + */ + public static function predictions(string $rawPredictions, array $labels): array + { + $numericLabels = self::numericLabels($labels); + $results = []; + foreach (explode(PHP_EOL, $rawPredictions) as $result) { + if (strlen($result) > 0) { + $results[] = array_search($result, $numericLabels); + } + } + + return $results; + } + + /** + * @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 + 1, $feature); + } + + return implode(' ', $row); + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/SupportVectorMachine/Kernel.php b/lib/mlbackend/php/phpml/src/Phpml/SupportVectorMachine/Kernel.php new file mode 100644 index 0000000000000..9918a3fc25241 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/SupportVectorMachine/Kernel.php @@ -0,0 +1,28 @@ +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, [__DIR__, '..', '..', '..'])).DIRECTORY_SEPARATOR; + + $this->binPath = $rootPath.'bin'.DIRECTORY_SEPARATOR.'libsvm'.DIRECTORY_SEPARATOR; + $this->varPath = $rootPath.'var'.DIRECTORY_SEPARATOR; + } + + /** + * @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); + + $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'; + + $command = $this->buildTrainCommand($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; + } + + /** + * @param array $samples + * + * @return array + */ + public function predict(array $samples) + { + $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); + $output = ''; + exec(escapeshellcmd($command), $output); + + $predictions = file_get_contents($outputFileName); + + unlink($testSetFileName); + unlink($modelFileName); + unlink($outputFileName); + + 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 string + */ + private function getOSExtension() + { + $os = strtoupper(substr(PHP_OS, 0, 3)); + if ($os === 'WIN') { + return '.exe'; + } elseif ($os === 'DAR') { + return '-osx'; + } + + 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, + escapeshellarg($trainingSetFileName), + escapeshellarg($modelFileName) + ); + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/SupportVectorMachine/Type.php b/lib/mlbackend/php/phpml/src/Phpml/SupportVectorMachine/Type.php new file mode 100644 index 0000000000000..1b454a5d5f5e0 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/SupportVectorMachine/Type.php @@ -0,0 +1,33 @@ + + + + phpml + PHP-ML + MIT + 0.4.1+ + + + diff --git a/lib/mlbackend/php/version.php b/lib/mlbackend/php/version.php new file mode 100644 index 0000000000000..592a6c76047fe --- /dev/null +++ b/lib/mlbackend/php/version.php @@ -0,0 +1,29 @@ +. + +/** + * Version details. + * + * @package mlbackend_php + * @copyright 2017 David Monllao {@link http://www.davidmonllao.com/} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$plugin->version = 2017051500; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2017050500; // Requires this Moodle version. +$plugin->component = 'mlbackend_php'; // Full name of the plugin (used for diagnostics).