diff --git a/lib/mlbackend/php/phpml/src/Phpml/Classification/DecisionTree.php b/lib/mlbackend/php/phpml/src/Phpml/Classification/DecisionTree.php index 6e890c9c296..da8b81bd386 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Classification/DecisionTree.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Classification/DecisionTree.php @@ -102,19 +102,21 @@ class DecisionTree implements Classifier $this->columnNames = array_slice($this->columnNames, 0, $this->featureCount); } elseif (count($this->columnNames) < $this->featureCount) { $this->columnNames = array_merge($this->columnNames, - range(count($this->columnNames), $this->featureCount - 1)); + range(count($this->columnNames), $this->featureCount - 1) + ); } } /** * @param array $samples + * * @return array */ public static function getColumnTypes(array $samples) : array { $types = []; $featureCount = count($samples[0]); - for ($i=0; $i < $featureCount; $i++) { + for ($i = 0; $i < $featureCount; ++$i) { $values = array_column($samples, $i); $isCategorical = self::isCategoricalColumn($values); $types[] = $isCategorical ? self::NOMINAL : self::CONTINUOUS; @@ -125,7 +127,8 @@ class DecisionTree implements Classifier /** * @param array $records - * @param int $depth + * @param int $depth + * * @return DecisionTreeLeaf */ protected function getSplitLeaf(array $records, int $depth = 0) : DecisionTreeLeaf @@ -163,10 +166,10 @@ class DecisionTree implements Classifier // Group remaining targets $target = $this->targets[$recordNo]; - if (! array_key_exists($target, $remainingTargets)) { + if (!array_key_exists($target, $remainingTargets)) { $remainingTargets[$target] = 1; } else { - $remainingTargets[$target]++; + ++$remainingTargets[$target]; } } @@ -188,6 +191,7 @@ class DecisionTree implements Classifier /** * @param array $records + * * @return DecisionTreeLeaf */ protected function getBestSplit(array $records) : DecisionTreeLeaf @@ -251,7 +255,7 @@ class DecisionTree implements Classifier protected function getSelectedFeatures() : array { $allFeatures = range(0, $this->featureCount - 1); - if ($this->numUsableFeatures === 0 && ! $this->selectedFeatures) { + if ($this->numUsableFeatures === 0 && !$this->selectedFeatures) { return $allFeatures; } @@ -271,9 +275,10 @@ class DecisionTree implements Classifier } /** - * @param $baseValue + * @param mixed $baseValue * @param array $colValues * @param array $targets + * * @return float */ public function getGiniIndex($baseValue, array $colValues, array $targets) : float @@ -282,13 +287,15 @@ class DecisionTree implements Classifier foreach ($this->labels as $label) { $countMatrix[$label] = [0, 0]; } + foreach ($colValues as $index => $value) { $label = $targets[$index]; $rowIndex = $value === $baseValue ? 0 : 1; - $countMatrix[$label][$rowIndex]++; + ++$countMatrix[$label][$rowIndex]; } + $giniParts = [0, 0]; - for ($i=0; $i<=1; $i++) { + for ($i = 0; $i <= 1; ++$i) { $part = 0; $sum = array_sum(array_column($countMatrix, $i)); if ($sum > 0) { @@ -296,6 +303,7 @@ class DecisionTree implements Classifier $part += pow($countMatrix[$label][$i] / floatval($sum), 2); } } + $giniParts[$i] = (1 - $part) * $sum; } @@ -304,6 +312,7 @@ class DecisionTree implements Classifier /** * @param array $samples + * * @return array */ protected function preprocess(array $samples) : array @@ -311,7 +320,7 @@ class DecisionTree implements Classifier // Detect and convert continuous data column values into // discrete values by using the median as a threshold value $columns = []; - for ($i=0; $i<$this->featureCount; $i++) { + for ($i = 0; $i < $this->featureCount; ++$i) { $values = array_column($samples, $i); if ($this->columnTypes[$i] == self::CONTINUOUS) { $median = Mean::median($values); @@ -332,6 +341,7 @@ class DecisionTree implements Classifier /** * @param array $columnValues + * * @return bool */ protected static function isCategoricalColumn(array $columnValues) : bool @@ -348,6 +358,7 @@ class DecisionTree implements Classifier if ($floatValues) { return false; } + if (count($numericValues) !== $count) { return true; } @@ -365,7 +376,9 @@ class DecisionTree implements Classifier * randomly selected for each split operation. * * @param int $numFeatures + * * @return $this + * * @throws InvalidArgumentException */ public function setNumFeatures(int $numFeatures) @@ -394,7 +407,9 @@ class DecisionTree implements Classifier * column importances are desired to be inspected. * * @param array $names + * * @return $this + * * @throws InvalidArgumentException */ public function setColumnNames(array $names) @@ -458,8 +473,9 @@ class DecisionTree implements Classifier * Collects and returns an array of internal nodes that use the given * column as a split criterion * - * @param int $column + * @param int $column * @param DecisionTreeLeaf $node + * * @return array */ protected function getSplitNodesByColumn(int $column, DecisionTreeLeaf $node) : array @@ -478,9 +494,11 @@ class DecisionTree implements Classifier if ($node->leftLeaf) { $lNodes = $this->getSplitNodesByColumn($column, $node->leftLeaf); } + if ($node->rightLeaf) { $rNodes = $this->getSplitNodesByColumn($column, $node->rightLeaf); } + $nodes = array_merge($nodes, $lNodes, $rNodes); return $nodes; @@ -488,6 +506,7 @@ class DecisionTree implements Classifier /** * @param array $sample + * * @return mixed */ protected function predictSample(array $sample) @@ -497,6 +516,7 @@ class DecisionTree implements Classifier if ($node->isTerminal) { break; } + if ($node->evaluate($sample)) { $node = $node->leftLeaf; } else { diff --git a/lib/mlbackend/php/phpml/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php b/lib/mlbackend/php/phpml/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php index bbb3175112f..787108f82bf 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -92,6 +92,8 @@ class DecisionTreeLeaf * Returns Mean Decrease Impurity (MDI) in the node. * For terminal nodes, this value is equal to 0 * + * @param int $parentRecordCount + * * @return float */ public function getNodeImpurityDecrease(int $parentRecordCount) @@ -133,7 +135,7 @@ class DecisionTreeLeaf } else { $col = "col_$this->columnIndex"; } - if (! preg_match("/^[<>=]{1,2}/", $value)) { + if (!preg_match("/^[<>=]{1,2}/", $value)) { $value = "=$value"; } $value = "$col $value
Gini: ". number_format($this->giniIndex, 2); diff --git a/lib/mlbackend/php/phpml/src/Phpml/Classification/Ensemble/AdaBoost.php b/lib/mlbackend/php/phpml/src/Phpml/Classification/Ensemble/AdaBoost.php index 3d1e4187380..38571da14de 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Classification/Ensemble/AdaBoost.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Classification/Ensemble/AdaBoost.php @@ -75,6 +75,7 @@ class AdaBoost implements Classifier * improve classification performance of 'weak' classifiers such as * DecisionStump (default base classifier of AdaBoost). * + * @param int $maxIterations */ public function __construct(int $maxIterations = 50) { @@ -96,6 +97,8 @@ class AdaBoost implements Classifier /** * @param array $samples * @param array $targets + * + * @throws \Exception */ public function train(array $samples, array $targets) { @@ -123,7 +126,6 @@ class AdaBoost implements Classifier // Execute the algorithm for a maximum number of iterations $currIter = 0; while ($this->maxIterations > $currIter++) { - // Determine the best 'weak' classifier based on current weights $classifier = $this->getBestClassifier(); $errorRate = $this->evaluateClassifier($classifier); @@ -181,7 +183,7 @@ class AdaBoost implements Classifier $targets = []; foreach ($weights as $index => $weight) { $z = (int)round(($weight - $mean) / $std) - $minZ + 1; - for ($i=0; $i < $z; $i++) { + for ($i = 0; $i < $z; ++$i) { if (rand(0, 1) == 0) { continue; } @@ -197,6 +199,8 @@ class AdaBoost implements Classifier * Evaluates the classifier and returns the classification error rate * * @param Classifier $classifier + * + * @return float */ protected function evaluateClassifier(Classifier $classifier) { diff --git a/lib/mlbackend/php/phpml/src/Phpml/Classification/Ensemble/Bagging.php b/lib/mlbackend/php/phpml/src/Phpml/Classification/Ensemble/Bagging.php index 1bb20273ec7..1af155d9f24 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Classification/Ensemble/Bagging.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Classification/Ensemble/Bagging.php @@ -59,13 +59,13 @@ class Bagging implements Classifier private $samples = []; /** - * Creates an ensemble classifier with given number of base classifiers
- * Default number of base classifiers is 100. + * Creates an ensemble classifier with given number of base classifiers + * Default number of base classifiers is 50. * The more number of base classifiers, the better performance but at the cost of procesing time * * @param int $numClassifier */ - public function __construct($numClassifier = 50) + public function __construct(int $numClassifier = 50) { $this->numClassifier = $numClassifier; } @@ -76,14 +76,17 @@ class Bagging implements Classifier * to train each base classifier. * * @param float $ratio + * * @return $this - * @throws Exception + * + * @throws \Exception */ public function setSubsetRatio(float $ratio) { if ($ratio < 0.1 || $ratio > 1.0) { throw new \Exception("Subset ratio should be between 0.1 and 1.0"); } + $this->subsetRatio = $ratio; return $this; } @@ -98,12 +101,14 @@ class Bagging implements Classifier * * @param string $classifier * @param array $classifierOptions + * * @return $this */ public function setClassifer(string $classifier, array $classifierOptions = []) { $this->classifier = $classifier; $this->classifierOptions = $classifierOptions; + return $this; } @@ -138,11 +143,12 @@ class Bagging implements Classifier $targets = []; srand($index); $bootstrapSize = $this->subsetRatio * $this->numSamples; - for ($i=0; $i < $bootstrapSize; $i++) { + for ($i = 0; $i < $bootstrapSize; ++$i) { $rand = rand(0, $this->numSamples - 1); $samples[] = $this->samples[$rand]; $targets[] = $this->targets[$rand]; } + return [$samples, $targets]; } @@ -152,24 +158,25 @@ class Bagging implements Classifier protected function initClassifiers() { $classifiers = []; - for ($i=0; $i<$this->numClassifier; $i++) { + for ($i = 0; $i < $this->numClassifier; ++$i) { $ref = new \ReflectionClass($this->classifier); if ($this->classifierOptions) { $obj = $ref->newInstanceArgs($this->classifierOptions); } else { $obj = $ref->newInstance(); } - $classifiers[] = $this->initSingleClassifier($obj, $i); + + $classifiers[] = $this->initSingleClassifier($obj); } return $classifiers; } /** * @param Classifier $classifier - * @param int $index + * * @return Classifier */ - protected function initSingleClassifier($classifier, $index) + protected function initSingleClassifier($classifier) { return $classifier; } diff --git a/lib/mlbackend/php/phpml/src/Phpml/Classification/Ensemble/RandomForest.php b/lib/mlbackend/php/phpml/src/Phpml/Classification/Ensemble/RandomForest.php index 273eb21aaa4..7849cd8bb4f 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Classification/Ensemble/RandomForest.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Classification/Ensemble/RandomForest.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace Phpml\Classification\Ensemble; use Phpml\Classification\DecisionTree; -use Phpml\Classification\Classifier; class RandomForest extends Bagging { @@ -24,9 +23,9 @@ class RandomForest extends Bagging * may increase the prediction performance while it will also substantially * increase the processing time and the required memory * - * @param type $numClassifier + * @param int $numClassifier */ - public function __construct($numClassifier = 50) + public function __construct(int $numClassifier = 50) { parent::__construct($numClassifier); @@ -43,17 +42,21 @@ class RandomForest extends Bagging * features to be taken into consideration while selecting subspace of features * * @param mixed $ratio string or float should be given + * * @return $this - * @throws Exception + * + * @throws \Exception */ public function setFeatureSubsetRatio($ratio) { if (is_float($ratio) && ($ratio < 0.1 || $ratio > 1.0)) { throw new \Exception("When a float given, feature subset ratio should be between 0.1 and 1.0"); } + if (is_string($ratio) && $ratio != 'sqrt' && $ratio != 'log') { throw new \Exception("When a string given, feature subset ratio can only be 'sqrt' or 'log' "); } + $this->featureSubsetRatio = $ratio; return $this; } @@ -62,8 +65,11 @@ class RandomForest extends Bagging * RandomForest algorithm is usable *only* with DecisionTree * * @param string $classifier - * @param array $classifierOptions + * @param array $classifierOptions + * * @return $this + * + * @throws \Exception */ public function setClassifer(string $classifier, array $classifierOptions = []) { @@ -125,10 +131,10 @@ class RandomForest extends Bagging /** * @param DecisionTree $classifier - * @param int $index + * * @return DecisionTree */ - protected function initSingleClassifier($classifier, $index) + protected function initSingleClassifier($classifier) { if (is_float($this->featureSubsetRatio)) { $featureCount = (int)($this->featureSubsetRatio * $this->featureCount); diff --git a/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/Adaline.php b/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/Adaline.php index f34dc5c4086..b94de28d923 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/Adaline.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/Adaline.php @@ -4,11 +4,8 @@ declare(strict_types=1); namespace Phpml\Classification\Linear; -use Phpml\Classification\Classifier; - class Adaline extends Perceptron { - /** * Batch training is the default Adaline training algorithm */ @@ -35,13 +32,17 @@ class Adaline extends Perceptron * If normalizeInputs is set to true, then every input given to the algorithm will be standardized * by use of standard deviation and mean calculation * - * @param int $learningRate - * @param int $maxIterations + * @param float $learningRate + * @param int $maxIterations + * @param bool $normalizeInputs + * @param int $trainingType + * + * @throws \Exception */ public function __construct(float $learningRate = 0.001, int $maxIterations = 1000, bool $normalizeInputs = true, int $trainingType = self::BATCH_TRAINING) { - if (! in_array($trainingType, [self::BATCH_TRAINING, self::ONLINE_TRAINING])) { + if (!in_array($trainingType, [self::BATCH_TRAINING, self::ONLINE_TRAINING])) { throw new \Exception("Adaline can only be trained with batch and online/stochastic gradient descent algorithm"); } diff --git a/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/DecisionStump.php b/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/DecisionStump.php index 99f982ff11c..5a3247fe3f5 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/DecisionStump.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/DecisionStump.php @@ -87,6 +87,8 @@ class DecisionStump extends WeightedClassifier /** * @param array $samples * @param array $targets + * @param array $labels + * * @throws \Exception */ protected function trainBinary(array $samples, array $targets, array $labels) @@ -237,13 +239,13 @@ class DecisionStump extends WeightedClassifier /** * - * @param type $leftValue - * @param type $operator - * @param type $rightValue + * @param mixed $leftValue + * @param string $operator + * @param mixed $rightValue * * @return boolean */ - protected function evaluate($leftValue, $operator, $rightValue) + protected function evaluate($leftValue, string $operator, $rightValue) { switch ($operator) { case '>': return $leftValue > $rightValue; @@ -288,10 +290,10 @@ class DecisionStump extends WeightedClassifier $wrong += $this->weights[$index]; } - if (! isset($prob[$predicted][$target])) { + if (!isset($prob[$predicted][$target])) { $prob[$predicted][$target] = 0; } - $prob[$predicted][$target]++; + ++$prob[$predicted][$target]; } // Calculate probabilities: Proportion of labels in each leaf diff --git a/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/LogisticRegression.php b/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/LogisticRegression.php index bd56d347a50..bc6a3c9ecca 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/LogisticRegression.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/LogisticRegression.php @@ -4,21 +4,19 @@ declare(strict_types=1); namespace Phpml\Classification\Linear; -use Phpml\Classification\Classifier; use Phpml\Helper\Optimizer\ConjugateGradient; class LogisticRegression extends Adaline { - /** * Batch training: Gradient descent algorithm (default) */ - const BATCH_TRAINING = 1; + const BATCH_TRAINING = 1; /** * Online training: Stochastic gradient descent learning */ - const ONLINE_TRAINING = 2; + const ONLINE_TRAINING = 2; /** * Conjugate Batch: Conjugate Gradient algorithm @@ -74,13 +72,13 @@ class LogisticRegression extends Adaline string $penalty = 'L2') { $trainingTypes = range(self::BATCH_TRAINING, self::CONJUGATE_GRAD_TRAINING); - if (! in_array($trainingType, $trainingTypes)) { + if (!in_array($trainingType, $trainingTypes)) { throw new \Exception("Logistic regression can only be trained with " . "batch (gradient descent), online (stochastic gradient descent) " . "or conjugate batch (conjugate gradients) algorithms"); } - if (! in_array($cost, ['log', 'sse'])) { + if (!in_array($cost, ['log', 'sse'])) { throw new \Exception("Logistic regression cost function can be one of the following: \n" . "'log' for log-likelihood and 'sse' for sum of squared errors"); } @@ -126,6 +124,8 @@ class LogisticRegression extends Adaline * * @param array $samples * @param array $targets + * + * @throws \Exception */ protected function runTraining(array $samples, array $targets) { @@ -140,12 +140,18 @@ class LogisticRegression extends Adaline case self::CONJUGATE_GRAD_TRAINING: return $this->runConjugateGradient($samples, $targets, $callback); + + default: + throw new \Exception('Logistic regression has invalid training type: %s.', $this->trainingType); } } /** - * Executes Conjugate Gradient method to optimize the - * weights of the LogReg model + * Executes Conjugate Gradient method to optimize the weights of the LogReg model + * + * @param array $samples + * @param array $targets + * @param \Closure $gradientFunc */ protected function runConjugateGradient(array $samples, array $targets, \Closure $gradientFunc) { @@ -162,6 +168,8 @@ class LogisticRegression extends Adaline * Returns the appropriate callback function for the selected cost function * * @return \Closure + * + * @throws \Exception */ protected function getCostFunction() { @@ -203,7 +211,7 @@ class LogisticRegression extends Adaline return $callback; case 'sse': - /** + /* * Sum of squared errors or least squared errors cost function: * J(x) = ∑ (y - h(x))^2 * @@ -224,6 +232,9 @@ class LogisticRegression extends Adaline }; return $callback; + + default: + throw new \Exception(sprintf('Logistic regression has invalid cost function: %s.', $this->costFunction)); } } @@ -245,6 +256,7 @@ class LogisticRegression extends Adaline * Returns the class value (either -1 or 1) for the given input * * @param array $sample + * * @return int */ protected function outputClass(array $sample) @@ -266,6 +278,8 @@ class LogisticRegression extends Adaline * * @param array $sample * @param mixed $label + * + * @return float */ protected function predictProbability(array $sample, $label) { diff --git a/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/Perceptron.php b/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/Perceptron.php index 91ffacf91a4..f4a8791f3f7 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/Perceptron.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Classification/Linear/Perceptron.php @@ -63,22 +63,22 @@ class Perceptron implements Classifier, IncrementalEstimator /** * Initalize a perceptron classifier with given learning rate and maximum - * number of iterations used while training the perceptron
+ * number of iterations used while training the perceptron * - * Learning rate should be a float value between 0.0(exclusive) and 1.0(inclusive)
- * Maximum number of iterations can be an integer value greater than 0 - * @param int $learningRate - * @param int $maxIterations + * @param float $learningRate Value between 0.0(exclusive) and 1.0(inclusive) + * @param int $maxIterations Must be at least 1 + * @param bool $normalizeInputs + * + * @throws \Exception */ - public function __construct(float $learningRate = 0.001, int $maxIterations = 1000, - bool $normalizeInputs = true) + public function __construct(float $learningRate = 0.001, int $maxIterations = 1000, bool $normalizeInputs = true) { if ($learningRate <= 0.0 || $learningRate > 1.0) { throw new \Exception("Learning rate should be a float value between 0.0(exclusive) and 1.0(inclusive)"); } if ($maxIterations <= 0) { - throw new \Exception("Maximum number of iterations should be an integer greater than 0"); + throw new \Exception("Maximum number of iterations must be an integer greater than 0"); } if ($normalizeInputs) { @@ -96,7 +96,7 @@ class Perceptron implements Classifier, IncrementalEstimator */ public function partialTrain(array $samples, array $targets, array $labels = []) { - return $this->trainByLabel($samples, $targets, $labels); + $this->trainByLabel($samples, $targets, $labels); } /** @@ -140,6 +140,8 @@ class Perceptron implements Classifier, IncrementalEstimator * for $maxIterations times * * @param bool $enable + * + * @return $this */ public function setEarlyStop(bool $enable = true) { @@ -185,12 +187,14 @@ class Perceptron implements Classifier, IncrementalEstimator * Executes a Gradient Descent algorithm for * the given cost function * - * @param array $samples - * @param array $targets + * @param array $samples + * @param array $targets + * @param \Closure $gradientFunc + * @param bool $isBatch */ protected function runGradientDescent(array $samples, array $targets, \Closure $gradientFunc, bool $isBatch = false) { - $class = $isBatch ? GD::class : StochasticGD::class; + $class = $isBatch ? GD::class : StochasticGD::class; if (empty($this->optimizer)) { $this->optimizer = (new $class($this->featureCount)) @@ -262,6 +266,8 @@ class Perceptron implements Classifier, IncrementalEstimator * * @param array $sample * @param mixed $label + * + * @return float */ protected function predictProbability(array $sample, $label) { @@ -277,6 +283,7 @@ class Perceptron implements Classifier, IncrementalEstimator /** * @param array $sample + * * @return mixed */ protected function predictSampleBinary(array $sample) @@ -285,6 +292,6 @@ class Perceptron implements Classifier, IncrementalEstimator $predictedClass = $this->outputClass($sample); - return $this->labels[ $predictedClass ]; + return $this->labels[$predictedClass]; } } diff --git a/lib/mlbackend/php/phpml/src/Phpml/Classification/MLPClassifier.php b/lib/mlbackend/php/phpml/src/Phpml/Classification/MLPClassifier.php new file mode 100644 index 00000000000..bde49a234e6 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Classification/MLPClassifier.php @@ -0,0 +1,58 @@ +classes)) { + throw InvalidArgumentException::invalidTarget($target); + } + return array_search($target, $this->classes); + } + + /** + * @param array $sample + * + * @return mixed + */ + protected function predictSample(array $sample) + { + $output = $this->setInput($sample)->getOutput(); + + $predictedClass = null; + $max = 0; + foreach ($output as $class => $value) { + if ($value > $max) { + $predictedClass = $class; + $max = $value; + } + } + return $this->classes[$predictedClass]; + } + + /** + * @param array $sample + * @param mixed $target + */ + protected function trainSample(array $sample, $target) + { + + // Feed-forward. + $this->setInput($sample)->getOutput(); + + // Back-propagate. + $this->backpropagation->backpropagate($this->getLayers(), $this->getTargetClass($target)); + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Classification/NaiveBayes.php b/lib/mlbackend/php/phpml/src/Phpml/Classification/NaiveBayes.php index af81b00a086..1a634da668c 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Classification/NaiveBayes.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Classification/NaiveBayes.php @@ -89,7 +89,7 @@ class NaiveBayes implements Classifier $this->mean[$label]= array_fill(0, $this->featureCount, 0); $this->dataType[$label] = array_fill(0, $this->featureCount, self::CONTINUOS); $this->discreteProb[$label] = array_fill(0, $this->featureCount, self::CONTINUOS); - for ($i=0; $i<$this->featureCount; $i++) { + for ($i = 0; $i < $this->featureCount; ++$i) { // Get the values of nth column in the samples array // Mean::arithmetic is called twice, can be optimized $values = array_column($samples, $i); @@ -114,16 +114,17 @@ class NaiveBayes implements Classifier /** * Calculates the probability P(label|sample_n) * - * @param array $sample - * @param int $feature + * @param array $sample + * @param int $feature * @param string $label + * * @return float */ private function sampleProbability($sample, $feature, $label) { $value = $sample[$feature]; if ($this->dataType[$label][$feature] == self::NOMINAL) { - if (! isset($this->discreteProb[$label][$feature][$value]) || + if (!isset($this->discreteProb[$label][$feature][$value]) || $this->discreteProb[$label][$feature][$value] == 0) { return self::EPSILON; } @@ -145,13 +146,15 @@ class NaiveBayes implements Classifier /** * Return samples belonging to specific label + * * @param string $label + * * @return array */ private function getSamplesByLabel($label) { $samples = []; - for ($i=0; $i<$this->sampleCount; $i++) { + for ($i = 0; $i < $this->sampleCount; ++$i) { if ($this->targets[$i] == $label) { $samples[] = $this->samples[$i]; } @@ -171,12 +174,13 @@ class NaiveBayes implements Classifier $predictions = []; foreach ($this->labels as $label) { $p = $this->p[$label]; - for ($i=0; $i<$this->featureCount; $i++) { + for ($i = 0; $i<$this->featureCount; ++$i) { $Plf = $this->sampleProbability($sample, $i, $label); $p += $Plf; } $predictions[$label] = $p; } + arsort($predictions, SORT_NUMERIC); reset($predictions); return key($predictions); diff --git a/lib/mlbackend/php/phpml/src/Phpml/Clustering/FuzzyCMeans.php b/lib/mlbackend/php/phpml/src/Phpml/Clustering/FuzzyCMeans.php index 424f2f15094..c6a3c46430d 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Clustering/FuzzyCMeans.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Clustering/FuzzyCMeans.php @@ -7,6 +7,7 @@ namespace Phpml\Clustering; use Phpml\Clustering\KMeans\Point; use Phpml\Clustering\KMeans\Cluster; use Phpml\Clustering\KMeans\Space; +use Phpml\Exception\InvalidArgumentException; use Phpml\Math\Distance\Euclidean; class FuzzyCMeans implements Clusterer @@ -25,10 +26,12 @@ class FuzzyCMeans implements Clusterer * @var Space */ private $space; + /** * @var array|float[][] */ private $membership; + /** * @var float */ @@ -56,6 +59,9 @@ class FuzzyCMeans implements Clusterer /** * @param int $clustersNumber + * @param float $fuzziness + * @param float $epsilon + * @param int $maxIterations * * @throws InvalidArgumentException */ @@ -86,14 +92,15 @@ class FuzzyCMeans implements Clusterer protected function generateRandomMembership(int $rows, int $cols) { $this->membership = []; - for ($i=0; $i < $rows; $i++) { + for ($i = 0; $i < $rows; ++$i) { $row = []; $total = 0.0; - for ($k=0; $k < $cols; $k++) { + for ($k = 0; $k < $cols; ++$k) { $val = rand(1, 5) / 10.0; $row[] = $val; $total += $val; } + $this->membership[] = array_map(function ($val) use ($total) { return $val / $total; }, $row); @@ -103,21 +110,22 @@ class FuzzyCMeans implements Clusterer protected function updateClusters() { $dim = $this->space->getDimension(); - if (! $this->clusters) { + if (!$this->clusters) { $this->clusters = []; - for ($i=0; $i<$this->clustersNumber; $i++) { + for ($i = 0; $i < $this->clustersNumber; ++$i) { $this->clusters[] = new Cluster($this->space, array_fill(0, $dim, 0.0)); } } - for ($i=0; $i<$this->clustersNumber; $i++) { + for ($i = 0; $i < $this->clustersNumber; ++$i) { $cluster = $this->clusters[$i]; $center = $cluster->getCoordinates(); - for ($k=0; $k<$dim; $k++) { + for ($k = 0; $k < $dim; ++$k) { $a = $this->getMembershipRowTotal($i, $k, true); $b = $this->getMembershipRowTotal($i, $k, false); $center[$k] = $a / $b; } + $cluster->setCoordinates($center); } } @@ -125,20 +133,22 @@ class FuzzyCMeans implements Clusterer protected function getMembershipRowTotal(int $row, int $col, bool $multiply) { $sum = 0.0; - for ($k = 0; $k < $this->sampleCount; $k++) { + for ($k = 0; $k < $this->sampleCount; ++$k) { $val = pow($this->membership[$row][$k], $this->fuzziness); if ($multiply) { $val *= $this->samples[$k][$col]; } + $sum += $val; } + return $sum; } protected function updateMembershipMatrix() { - for ($i = 0; $i < $this->clustersNumber; $i++) { - for ($k = 0; $k < $this->sampleCount; $k++) { + for ($i = 0; $i < $this->clustersNumber; ++$i) { + for ($k = 0; $k < $this->sampleCount; ++$k) { $distCalc = $this->getDistanceCalc($i, $k); $this->membership[$i][$k] = 1.0 / $distCalc; } @@ -157,11 +167,15 @@ class FuzzyCMeans implements Clusterer $distance = new Euclidean(); $dist1 = $distance->distance( $this->clusters[$row]->getCoordinates(), - $this->samples[$col]); - for ($j = 0; $j < $this->clustersNumber; $j++) { + $this->samples[$col] + ); + + for ($j = 0; $j < $this->clustersNumber; ++$j) { $dist2 = $distance->distance( $this->clusters[$j]->getCoordinates(), - $this->samples[$col]); + $this->samples[$col] + ); + $val = pow($dist1 / $dist2, 2.0 / ($this->fuzziness - 1)); $sum += $val; } @@ -177,13 +191,14 @@ class FuzzyCMeans implements Clusterer { $sum = 0.0; $distance = new Euclidean(); - for ($i = 0; $i < $this->clustersNumber; $i++) { + for ($i = 0; $i < $this->clustersNumber; ++$i) { $clust = $this->clusters[$i]->getCoordinates(); - for ($k = 0; $k < $this->sampleCount; $k++) { + for ($k = 0; $k < $this->sampleCount; ++$k) { $point = $this->samples[$k]; $sum += $distance->distance($clust, $point); } } + return $sum; } @@ -210,7 +225,6 @@ class FuzzyCMeans implements Clusterer // Our goal is minimizing the objective value while // executing the clustering steps at a maximum number of iterations $lastObjective = 0.0; - $difference = 0.0; $iterations = 0; do { // Update the membership matrix and cluster centers, respectively @@ -224,7 +238,7 @@ class FuzzyCMeans implements Clusterer } while ($difference > $this->epsilon && $iterations++ <= $this->maxIterations); // Attach (hard cluster) each data point to the nearest cluster - for ($k=0; $k<$this->sampleCount; $k++) { + for ($k = 0; $k < $this->sampleCount; ++$k) { $column = array_column($this->membership, $k); arsort($column); reset($column); @@ -238,6 +252,7 @@ class FuzzyCMeans implements Clusterer foreach ($this->clusters as $cluster) { $grouped[] = $cluster->getPoints(); } + return $grouped; } } diff --git a/lib/mlbackend/php/phpml/src/Phpml/Clustering/KMeans/Space.php b/lib/mlbackend/php/phpml/src/Phpml/Clustering/KMeans/Space.php index 5a4d5305e73..0276880dbaa 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Clustering/KMeans/Space.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Clustering/KMeans/Space.php @@ -156,7 +156,11 @@ class Space extends SplObjectStorage case KMeans::INIT_KMEANS_PLUS_PLUS: $clusters = $this->initializeKMPPClusters($clustersNumber); break; + + default: + return []; } + $clusters[0]->attachAll($this); return $clusters; diff --git a/lib/mlbackend/php/phpml/src/Phpml/Dataset/CsvDataset.php b/lib/mlbackend/php/phpml/src/Phpml/Dataset/CsvDataset.php index 8bcd3c49034..b2e9407795e 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Dataset/CsvDataset.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Dataset/CsvDataset.php @@ -17,6 +17,7 @@ class CsvDataset extends ArrayDataset * @param string $filepath * @param int $features * @param bool $headingRow + * @param string $delimiter * * @throws FileException */ @@ -37,11 +38,15 @@ class CsvDataset extends ArrayDataset $this->columnNames = range(0, $features - 1); } + $samples = $targets = []; while (($data = fgetcsv($handle, 1000, $delimiter)) !== false) { - $this->samples[] = array_slice($data, 0, $features); - $this->targets[] = $data[$features]; + $samples[] = array_slice($data, 0, $features); + $targets[] = $data[$features]; } + fclose($handle); + + parent::__construct($samples, $targets); } /** diff --git a/lib/mlbackend/php/phpml/src/Phpml/DimensionReduction/EigenTransformerBase.php b/lib/mlbackend/php/phpml/src/Phpml/DimensionReduction/EigenTransformerBase.php new file mode 100644 index 00000000000..6c0ef05f087 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/DimensionReduction/EigenTransformerBase.php @@ -0,0 +1,100 @@ +getRealEigenvalues(); + $eigVects= $eig->getEigenvectors(); + + $totalEigVal = array_sum($eigVals); + // Sort eigenvalues in descending order + arsort($eigVals); + + $explainedVar = 0.0; + $vectors = []; + $values = []; + foreach ($eigVals as $i => $eigVal) { + $explainedVar += $eigVal / $totalEigVal; + $vectors[] = $eigVects[$i]; + $values[] = $eigVal; + + if ($this->numFeatures !== null) { + if (count($vectors) == $this->numFeatures) { + break; + } + } else { + if ($explainedVar >= $this->totalVariance) { + break; + } + } + } + + $this->eigValues = $values; + $this->eigVectors = $vectors; + } + + /** + * Returns the reduced data + * + * @param array $data + * + * @return array + */ + protected function reduce(array $data) + { + $m1 = new Matrix($data); + $m2 = new Matrix($this->eigVectors); + + return $m1->multiply($m2->transpose())->toArray(); + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/DimensionReduction/KernelPCA.php b/lib/mlbackend/php/phpml/src/Phpml/DimensionReduction/KernelPCA.php index 86070c72bbc..94e18c92077 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/DimensionReduction/KernelPCA.php +++ b/lib/mlbackend/php/phpml/src/Phpml/DimensionReduction/KernelPCA.php @@ -54,7 +54,7 @@ class KernelPCA extends PCA 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)) { + if (!in_array($kernel, $availableKernels)) { throw new \Exception("KernelPCA can be initialized with the following kernels only: Linear, RBF, Sigmoid and Laplacian"); } @@ -86,7 +86,7 @@ class KernelPCA extends PCA $matrix = $this->calculateKernelMatrix($this->data, $numRows); $matrix = $this->centerMatrix($matrix, $numRows); - list($this->eigValues, $this->eigVectors) = $this->eigenDecomposition($matrix, $numRows); + $this->eigenDecomposition($matrix); $this->fit = true; @@ -98,7 +98,7 @@ class KernelPCA extends PCA * An n-by-m matrix is given and an n-by-n matrix is returned * * @param array $data - * @param int $numRows + * @param int $numRows * * @return array */ @@ -107,8 +107,8 @@ class KernelPCA extends PCA $kernelFunc = $this->getKernel(); $matrix = []; - for ($i=0; $i < $numRows; $i++) { - for ($k=0; $k < $numRows; $k++) { + for ($i = 0; $i < $numRows; ++$i) { + for ($k = 0; $k < $numRows; ++$k) { if ($i <= $k) { $matrix[$i][$k] = $kernelFunc($data[$i], $data[$k]); } else { @@ -127,7 +127,9 @@ class KernelPCA extends PCA * K′ = K − N.K − K.N + N.K.N where N is n-by-n matrix filled with 1/n * * @param array $matrix - * @param int $n + * @param int $n + * + * @return array */ protected function centerMatrix(array $matrix, int $n) { @@ -152,6 +154,8 @@ class KernelPCA extends PCA * Returns the callable kernel function * * @return \Closure + * + * @throws \Exception */ protected function getKernel() { @@ -181,6 +185,9 @@ class KernelPCA extends PCA return function ($x, $y) use ($dist) { return exp(-$this->gamma * $dist->distance($x, $y)); }; + + default: + throw new \Exception(sprintf('KernelPCA initialized with invalid kernel: %d', $this->kernel)); } } @@ -228,6 +235,8 @@ class KernelPCA extends PCA * @param array $sample * * @return array + * + * @throws \Exception */ public function transform(array $sample) { diff --git a/lib/mlbackend/php/phpml/src/Phpml/DimensionReduction/LDA.php b/lib/mlbackend/php/phpml/src/Phpml/DimensionReduction/LDA.php new file mode 100644 index 00000000000..e094c35732d --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/DimensionReduction/LDA.php @@ -0,0 +1,249 @@ +
+ * The algorithm can be initialized by speciyfing + * either with the totalVariance(a value between 0.1 and 0.99) + * or numFeatures (number of features in the dataset) to be preserved. + * + * @param float|null $totalVariance Total explained variance to be preserved + * @param int|null $numFeatures Number of features to be preserved + * + * @throws \Exception + */ + public function __construct($totalVariance = null, $numFeatures = null) + { + if ($totalVariance !== null && ($totalVariance < 0.1 || $totalVariance > 0.99)) { + throw new \Exception("Total variance can be a value between 0.1 and 0.99"); + } + if ($numFeatures !== null && $numFeatures <= 0) { + throw new \Exception("Number of features to be preserved should be greater than 0"); + } + if ($totalVariance !== null && $numFeatures !== null) { + throw new \Exception("Either totalVariance or numFeatures should be specified in order to run the algorithm"); + } + + if ($numFeatures !== null) { + $this->numFeatures = $numFeatures; + } + if ($totalVariance !== null) { + $this->totalVariance = $totalVariance; + } + } + + /** + * Trains the algorithm to transform the given data to a lower dimensional space. + * + * @param array $data + * @param array $classes + * + * @return array + */ + public function fit(array $data, array $classes) : array + { + $this->labels = $this->getLabels($classes); + $this->means = $this->calculateMeans($data, $classes); + + $sW = $this->calculateClassVar($data, $classes); + $sB = $this->calculateClassCov(); + + $S = $sW->inverse()->multiply($sB); + $this->eigenDecomposition($S->toArray()); + + $this->fit = true; + + return $this->reduce($data); + } + + /** + * Returns unique labels in the dataset + * + * @param array $classes + * + * @return array + */ + protected function getLabels(array $classes): array + { + $counts = array_count_values($classes); + + return array_keys($counts); + } + + + /** + * Calculates mean of each column for each class and returns + * n by m matrix where n is number of labels and m is number of columns + * + * @param array $data + * @param array $classes + * + * @return array + */ + protected function calculateMeans(array $data, array $classes) : array + { + $means = []; + $counts= []; + $overallMean = array_fill(0, count($data[0]), 0.0); + + foreach ($data as $index => $row) { + $label = array_search($classes[$index], $this->labels); + + foreach ($row as $col => $val) { + if (!isset($means[$label][$col])) { + $means[$label][$col] = 0.0; + } + $means[$label][$col] += $val; + $overallMean[$col] += $val; + } + + if (!isset($counts[$label])) { + $counts[$label] = 0; + } + + ++$counts[$label]; + } + + foreach ($means as $index => $row) { + foreach ($row as $col => $sum) { + $means[$index][$col] = $sum / $counts[$index]; + } + } + + // Calculate overall mean of the dataset for each column + $numElements = array_sum($counts); + $map = function ($el) use ($numElements) { + return $el / $numElements; + }; + $this->overallMean = array_map($map, $overallMean); + $this->counts = $counts; + + return $means; + } + + + /** + * Returns in-class scatter matrix for each class, which + * is a n by m matrix where n is number of classes and + * m is number of columns + * + * @param array $data + * @param array $classes + * + * @return Matrix + */ + protected function calculateClassVar($data, $classes) + { + // s is an n (number of classes) by m (number of column) matrix + $s = array_fill(0, count($data[0]), array_fill(0, count($data[0]), 0)); + $sW = new Matrix($s, false); + + foreach ($data as $index => $row) { + $label = array_search($classes[$index], $this->labels); + $means = $this->means[$label]; + + $row = $this->calculateVar($row, $means); + + $sW = $sW->add($row); + } + + return $sW; + } + + /** + * Returns between-class scatter matrix for each class, which + * is an n by m matrix where n is number of classes and + * m is number of columns + * + * @return Matrix + */ + protected function calculateClassCov() + { + // s is an n (number of classes) by m (number of column) matrix + $s = array_fill(0, count($this->overallMean), array_fill(0, count($this->overallMean), 0)); + $sB = new Matrix($s, false); + + foreach ($this->means as $index => $classMeans) { + $row = $this->calculateVar($classMeans, $this->overallMean); + $N = $this->counts[$index]; + $sB = $sB->add($row->multiplyByScalar($N)); + } + + return $sB; + } + + /** + * Returns the result of the calculation (x - m)T.(x - m) + * + * @param array $row + * @param array $means + * + * @return Matrix + */ + protected function calculateVar(array $row, array $means) + { + $x = new Matrix($row, false); + $m = new Matrix($means, false); + $diff = $x->subtract($m); + + return $diff->transpose()->multiply($diff); + } + + /** + * Transforms the given sample to a lower dimensional vector by using + * the eigenVectors obtained in the last run of fit. + * + * @param array $sample + * + * @return array + * + * @throws \Exception + */ + public function transform(array $sample) + { + if (!$this->fit) { + throw new \Exception("LDA has not been fitted with respect to original dataset, please run LDA::fit() first"); + } + + if (!is_array($sample[0])) { + $sample = [$sample]; + } + + return $this->reduce($sample); + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/DimensionReduction/PCA.php b/lib/mlbackend/php/phpml/src/Phpml/DimensionReduction/PCA.php index 422dae4d787..acaa8e01135 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/DimensionReduction/PCA.php +++ b/lib/mlbackend/php/phpml/src/Phpml/DimensionReduction/PCA.php @@ -4,27 +4,11 @@ declare(strict_types=1); namespace Phpml\DimensionReduction; -use Phpml\Math\LinearAlgebra\EigenvalueDecomposition; use Phpml\Math\Statistic\Covariance; use Phpml\Math\Statistic\Mean; -use Phpml\Math\Matrix; -class PCA +class PCA extends EigenTransformerBase { - /** - * Total variance to be conserved after the reduction - * - * @var float - */ - public $totalVariance = 0.9; - - /** - * Number of features to be preserved after the reduction - * - * @var int - */ - public $numFeatures = null; - /** * Temporary storage for mean values for each dimension in given data * @@ -32,20 +16,6 @@ class PCA */ protected $means = []; - /** - * Eigenvectors of the covariance matrix - * - * @var array - */ - protected $eigVectors = []; - - /** - * Top eigenValues of the covariance matrix - * - * @var type - */ - protected $eigValues = []; - /** * @var bool */ @@ -100,7 +70,7 @@ class PCA $covMatrix = Covariance::covarianceMatrix($data, array_fill(0, $n, 0)); - list($this->eigValues, $this->eigVectors) = $this->eigenDecomposition($covMatrix, $n); + $this->eigenDecomposition($covMatrix); $this->fit = true; @@ -115,7 +85,7 @@ class PCA { // Calculate means for each dimension $this->means = []; - for ($i=0; $i < $n; $i++) { + for ($i = 0; $i < $n; ++$i) { $column = array_column($data, $i); $this->means[] = Mean::arithmetic($column); } @@ -126,7 +96,7 @@ class PCA * each dimension therefore dimensions will be centered to zero * * @param array $data - * @param int $n + * @param int $n * * @return array */ @@ -138,7 +108,7 @@ class PCA // Normalize data foreach ($data as $i => $row) { - for ($k=0; $k < $n; $k++) { + for ($k = 0; $k < $n; ++$k) { $data[$i][$k] -= $this->means[$k]; } } @@ -146,63 +116,6 @@ class PCA return $data; } - /** - * Calculates eigenValues and eigenVectors of the given matrix. Returns - * top eigenVectors along with the largest eigenValues. The total explained variance - * of these eigenVectors will be no less than desired $totalVariance value - * - * @param array $matrix - * @param int $n - * - * @return array - */ - protected function eigenDecomposition(array $matrix, int $n) - { - $eig = new EigenvalueDecomposition($matrix); - $eigVals = $eig->getRealEigenvalues(); - $eigVects= $eig->getEigenvectors(); - - $totalEigVal = array_sum($eigVals); - // Sort eigenvalues in descending order - arsort($eigVals); - - $explainedVar = 0.0; - $vectors = []; - $values = []; - foreach ($eigVals as $i => $eigVal) { - $explainedVar += $eigVal / $totalEigVal; - $vectors[] = $eigVects[$i]; - $values[] = $eigVal; - - if ($this->numFeatures !== null) { - if (count($vectors) == $this->numFeatures) { - break; - } - } else { - if ($explainedVar >= $this->totalVariance) { - break; - } - } - } - - return [$values, $vectors]; - } - - /** - * Returns the reduced data - * - * @param array $data - * - * @return array - */ - protected function reduce(array $data) - { - $m1 = new Matrix($data); - $m2 = new Matrix($this->eigVectors); - - return $m1->multiply($m2->transpose())->toArray(); - } - /** * Transforms the given sample to a lower dimensional vector by using * the eigenVectors obtained in the last run of fit. @@ -210,6 +123,8 @@ class PCA * @param array $sample * * @return array + * + * @throws \Exception */ public function transform(array $sample) { @@ -217,7 +132,7 @@ class PCA throw new \Exception("PCA has not been fitted with respect to original dataset, please run PCA::fit() first"); } - if (! is_array($sample[0])) { + if (!is_array($sample[0])) { $sample = [$sample]; } diff --git a/lib/mlbackend/php/phpml/src/Phpml/Exception/DatasetException.php b/lib/mlbackend/php/phpml/src/Phpml/Exception/DatasetException.php index 60920536a50..ca7b0656b5e 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Exception/DatasetException.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Exception/DatasetException.php @@ -6,7 +6,6 @@ namespace Phpml\Exception; class DatasetException extends \Exception { - /** * @param string $path * diff --git a/lib/mlbackend/php/phpml/src/Phpml/Exception/FileException.php b/lib/mlbackend/php/phpml/src/Phpml/Exception/FileException.php index 558ae48a89f..20b29360451 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Exception/FileException.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Exception/FileException.php @@ -6,7 +6,6 @@ namespace Phpml\Exception; class FileException extends \Exception { - /** * @param string $filepath * diff --git a/lib/mlbackend/php/phpml/src/Phpml/Exception/InvalidArgumentException.php b/lib/mlbackend/php/phpml/src/Phpml/Exception/InvalidArgumentException.php index f6b0031a8fc..277aecdd855 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Exception/InvalidArgumentException.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Exception/InvalidArgumentException.php @@ -11,7 +11,7 @@ class InvalidArgumentException extends \Exception */ public static function arraySizeNotMatch() { - return new self('Size of given arrays not match'); + return new self('Size of given arrays does not match'); } /** @@ -55,7 +55,7 @@ class InvalidArgumentException extends \Exception */ public static function inconsistentMatrixSupplied() { - return new self('Inconsistent matrix applied'); + return new self('Inconsistent matrix supplied'); } /** @@ -66,6 +66,14 @@ class InvalidArgumentException extends \Exception return new self('Invalid clusters number'); } + /** + * @return InvalidArgumentException + */ + public static function invalidTarget($target) + { + return new self('Target with value ' . $target . ' is not part of the accepted classes'); + } + /** * @param string $language * @@ -89,6 +97,19 @@ class InvalidArgumentException extends \Exception */ public static function invalidLayersNumber() { - return new self('Provide at least 2 layers: 1 input and 1 output'); + return new self('Provide at least 1 hidden layer'); + } + + /** + * @return InvalidArgumentException + */ + public static function invalidClassesNumber() + { + return new self('Provide at least 2 different classes'); + } + + public static function inconsistentClasses() + { + return new self('The provided classes don\'t match the classes provided in the constructor'); } } diff --git a/lib/mlbackend/php/phpml/src/Phpml/Exception/SerializeException.php b/lib/mlbackend/php/phpml/src/Phpml/Exception/SerializeException.php index 70e6892544c..5753eb7ee52 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Exception/SerializeException.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Exception/SerializeException.php @@ -6,7 +6,6 @@ namespace Phpml\Exception; class SerializeException extends \Exception { - /** * @param string $filepath * diff --git a/lib/mlbackend/php/phpml/src/Phpml/FeatureExtraction/StopWords/French.php b/lib/mlbackend/php/phpml/src/Phpml/FeatureExtraction/StopWords/French.php new file mode 100644 index 00000000000..96cc11096a4 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/FeatureExtraction/StopWords/French.php @@ -0,0 +1,29 @@ +stopWords); + } +} diff --git a/lib/mlbackend/php/phpml/src/Phpml/Helper/OneVsRest.php b/lib/mlbackend/php/phpml/src/Phpml/Helper/OneVsRest.php index e207c46b5df..8d71fbcbf16 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Helper/OneVsRest.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Helper/OneVsRest.php @@ -6,7 +6,6 @@ namespace Phpml\Helper; trait OneVsRest { - /** * @var array */ @@ -35,18 +34,18 @@ trait OneVsRest // Clears previous stuff. $this->reset(); - return $this->trainBylabel($samples, $targets); + $this->trainBylabel($samples, $targets); } /** * @param array $samples * @param array $targets * @param array $allLabels All training set labels + * * @return void */ protected function trainByLabel(array $samples, array $targets, array $allLabels = []) { - // Overwrites the current value if it exist. $allLabels must be provided for each partialTrain run. if (!empty($allLabels)) { $this->allLabels = $allLabels; @@ -57,7 +56,6 @@ trait OneVsRest // If there are only two targets, then there is no need to perform OvR if (count($this->allLabels) == 2) { - // Init classifier if required. if (empty($this->classifiers)) { $this->classifiers[0] = $this->getClassifierCopy(); @@ -68,7 +66,6 @@ trait OneVsRest // Train a separate classifier for each label and memorize them foreach ($this->allLabels as $label) { - // Init classifier if required. if (empty($this->classifiers[$label])) { $this->classifiers[$label] = $this->getClassifierCopy(); @@ -107,7 +104,6 @@ trait OneVsRest */ protected function getClassifierCopy() { - // Clone the current classifier, so that // we don't mess up its variables while training // multiple instances of this classifier @@ -180,7 +176,8 @@ trait OneVsRest * Each classifier that make use of OvR approach should be able to * return a probability for a sample to belong to the given label. * - * @param array $sample + * @param array $sample + * @param string $label * * @return mixed */ diff --git a/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/ConjugateGradient.php b/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/ConjugateGradient.php index 18ae89a09e8..44bcd14cf68 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/ConjugateGradient.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/ConjugateGradient.php @@ -18,8 +18,8 @@ namespace Phpml\Helper\Optimizer; class ConjugateGradient extends GD { /** - * @param array $samples - * @param array $targets + * @param array $samples + * @param array $targets * @param \Closure $gradientCb * * @return array @@ -34,7 +34,7 @@ class ConjugateGradient extends GD $d = mp::muls($this->gradient($this->theta), -1); - for ($i=0; $i < $this->maxIterations; $i++) { + for ($i = 0; $i < $this->maxIterations; ++$i) { // Obtain α that minimizes f(θ + α.d) $alpha = $this->getAlpha(array_sum($d)); @@ -68,11 +68,11 @@ class ConjugateGradient extends GD * * @param array $theta * - * @return float + * @return array */ protected function gradient(array $theta) { - list($_, $gradient, $_) = parent::gradient($theta); + list(, $gradient) = parent::gradient($theta); return $gradient; } @@ -86,7 +86,7 @@ class ConjugateGradient extends GD */ protected function cost(array $theta) { - list($cost, $_, $_) = parent::gradient($theta); + list($cost) = parent::gradient($theta); return array_sum($cost) / $this->sampleCount; } @@ -107,7 +107,7 @@ class ConjugateGradient extends GD * * @param float $d * - * @return array + * @return float */ protected function getAlpha(float $d) { @@ -157,14 +157,14 @@ class ConjugateGradient extends GD * @param float $alpha * @param array $d * - * return array + * @return array */ protected function getNewTheta(float $alpha, array $d) { $theta = $this->theta; - for ($i=0; $i < $this->dimensions + 1; $i++) { - if ($i == 0) { + for ($i = 0; $i < $this->dimensions + 1; ++$i) { + if ($i === 0) { $theta[$i] += $alpha * array_sum($d); } else { $sum = 0.0; @@ -266,10 +266,11 @@ class mp * * @param array $m1 * @param array $m2 + * @param int $mag * * @return array */ - public static function add(array $m1, array $m2, $mag = 1) + public static function add(array $m1, array $m2, int $mag = 1) { $res = []; foreach ($m1 as $i => $val) { @@ -333,10 +334,11 @@ class mp * * @param array $m1 * @param float $m2 + * @param int $mag * * @return array */ - public static function adds(array $m1, float $m2, $mag = 1) + public static function adds(array $m1, float $m2, int $mag = 1) { $res = []; foreach ($m1 as $val) { @@ -350,7 +352,7 @@ class mp * Element-wise subtraction of a vector with a scalar * * @param array $m1 - * @param float $m2 + * @param array $m2 * * @return array */ diff --git a/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/GD.php b/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/GD.php index 8974c8e769c..b88b0c7c920 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/GD.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/GD.php @@ -18,8 +18,8 @@ class GD extends StochasticGD protected $sampleCount = null; /** - * @param array $samples - * @param array $targets + * @param array $samples + * @param array $targets * @param \Closure $gradientCb * * @return array @@ -75,7 +75,7 @@ class GD extends StochasticGD list($cost, $grad, $penalty) = array_pad($result, 3, 0); $costs[] = $cost; - $gradient[]= $grad; + $gradient[] = $grad; $totalPenalty += $penalty; } @@ -91,8 +91,8 @@ class GD extends StochasticGD protected function updateWeightsWithUpdates(array $updates, float $penalty) { // Updates all weights at once - for ($i=0; $i <= $this->dimensions; $i++) { - if ($i == 0) { + for ($i = 0; $i <= $this->dimensions; ++$i) { + if ($i === 0) { $this->theta[0] -= $this->learningRate * array_sum($updates); } else { $col = array_column($this->samples, $i - 1); diff --git a/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/Optimizer.php b/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/Optimizer.php index 9ef4c4d0949..09668a95b0c 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/Optimizer.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/Optimizer.php @@ -31,7 +31,7 @@ abstract class Optimizer // Inits the weights randomly $this->theta = []; - for ($i=0; $i < $this->dimensions; $i++) { + for ($i = 0; $i < $this->dimensions; ++$i) { $this->theta[] = rand() / (float) getrandmax(); } } @@ -40,6 +40,10 @@ abstract class Optimizer * Sets the weights manually * * @param array $theta + * + * @return $this + * + * @throws \Exception */ public function setInitialTheta(array $theta) { @@ -56,6 +60,9 @@ abstract class Optimizer * Executes the optimization with the given samples & targets * and returns the weights * + * @param array $samples + * @param array $targets + * @param \Closure $gradientCb */ abstract protected function runOptimization(array $samples, array $targets, \Closure $gradientCb); } diff --git a/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/StochasticGD.php b/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/StochasticGD.php index e9e318a8a5f..fa2401a4a45 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/StochasticGD.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Helper/Optimizer/StochasticGD.php @@ -166,7 +166,6 @@ class StochasticGD extends Optimizer $currIter = 0; $bestTheta = null; $bestScore = 0.0; - $bestWeightIter = 0; $this->costValues = []; while ($this->maxIterations > $currIter++) { @@ -180,7 +179,6 @@ class StochasticGD extends Optimizer if ($bestTheta == null || $cost <= $bestScore) { $bestTheta = $theta; $bestScore = $cost; - $bestWeightIter = $currIter; } // Add the cost value for this iteration to the list @@ -218,7 +216,7 @@ class StochasticGD extends Optimizer $this->theta[0] -= $this->learningRate * $gradient; // Update other values - for ($i=1; $i <= $this->dimensions; $i++) { + for ($i = 1; $i <= $this->dimensions; ++$i) { $this->theta[$i] -= $this->learningRate * ($gradient * $sample[$i - 1] + $penalty * $this->theta[$i]); } diff --git a/lib/mlbackend/php/phpml/src/Phpml/Helper/Predictable.php b/lib/mlbackend/php/phpml/src/Phpml/Helper/Predictable.php index 097edaabeab..2ef90177200 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Helper/Predictable.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Helper/Predictable.php @@ -14,12 +14,12 @@ trait Predictable public function predict(array $samples) { if (!is_array($samples[0])) { - $predicted = $this->predictSample($samples); - } else { - $predicted = []; - foreach ($samples as $index => $sample) { - $predicted[$index] = $this->predictSample($sample); - } + return $this->predictSample($samples); + } + + $predicted = []; + foreach ($samples as $index => $sample) { + $predicted[$index] = $this->predictSample($sample); } return $predicted; diff --git a/lib/mlbackend/php/phpml/src/Phpml/IncrementalEstimator.php b/lib/mlbackend/php/phpml/src/Phpml/IncrementalEstimator.php index fc6912d1109..4a0d1ccbdc8 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/IncrementalEstimator.php +++ b/lib/mlbackend/php/phpml/src/Phpml/IncrementalEstimator.php @@ -6,7 +6,6 @@ namespace Phpml; interface IncrementalEstimator { - /** * @param array $samples * @param array $targets diff --git a/lib/mlbackend/php/phpml/src/Phpml/Math/Kernel/RBF.php b/lib/mlbackend/php/phpml/src/Phpml/Math/Kernel/RBF.php index 8ca7d84bf14..2cd92db2aee 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Math/Kernel/RBF.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Math/Kernel/RBF.php @@ -23,8 +23,8 @@ class RBF implements Kernel } /** - * @param float $a - * @param float $b + * @param array $a + * @param array $b * * @return float */ diff --git a/lib/mlbackend/php/phpml/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php b/lib/mlbackend/php/phpml/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php index 27557bbd83a..7f0ec4ba0c9 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -33,7 +33,6 @@ use Phpml\Math\Matrix; class EigenvalueDecomposition { - /** * Row and column dimension (square matrix). * @var int @@ -42,9 +41,9 @@ class EigenvalueDecomposition /** * Internal symmetry flag. - * @var int + * @var bool */ - private $issymmetric; + private $symmetric; /** * Arrays for internal storage of eigenvalues. @@ -78,6 +77,38 @@ class EigenvalueDecomposition private $cdivr; private $cdivi; + /** + * Constructor: Check for symmetry, then construct the eigenvalue decomposition + * + * @param array $Arg + */ + public function __construct(array $Arg) + { + $this->A = $Arg; + $this->n = count($Arg[0]); + $this->symmetric = true; + + for ($j = 0; ($j < $this->n) && $this->symmetric; ++$j) { + for ($i = 0; ($i < $this->n) & $this->symmetric; ++$i) { + $this->symmetric = ($this->A[$i][$j] == $this->A[$j][$i]); + } + } + + if ($this->symmetric) { + $this->V = $this->A; + // Tridiagonalize. + $this->tred2(); + // Diagonalize. + $this->tql2(); + } else { + $this->H = $this->A; + $this->ort = []; + // Reduce to Hessenberg form. + $this->orthes(); + // Reduce Hessenberg to real Schur form. + $this->hqr2(); + } + } /** * Symmetric Householder reduction to tridiagonal form. @@ -88,10 +119,10 @@ class EigenvalueDecomposition // Bowdler, Martin, Reinsch, and Wilkinson, Handbook for // Auto. Comp., Vol.ii-Linear Algebra, and the corresponding // Fortran subroutine in EISPACK. - $this->d = $this->V[$this->n-1]; + $this->d = $this->V[$this->n - 1]; // Householder reduction to tridiagonal form. - for ($i = $this->n-1; $i > 0; --$i) { - $i_ = $i -1; + for ($i = $this->n - 1; $i > 0; --$i) { + $i_ = $i - 1; // Scale to avoid under/overflow. $h = $scale = 0.0; $scale += array_sum(array_map('abs', $this->d)); @@ -107,14 +138,17 @@ class EigenvalueDecomposition $this->d[$k] /= $scale; $h += pow($this->d[$k], 2); } + $f = $this->d[$i_]; $g = sqrt($h); if ($f > 0) { $g = -$g; } + $this->e[$i] = $scale * $g; $h = $h - $f * $g; $this->d[$i_] = $f - $g; + for ($j = 0; $j < $i; ++$j) { $this->e[$j] = 0.0; } @@ -123,22 +157,26 @@ class EigenvalueDecomposition $f = $this->d[$j]; $this->V[$j][$i] = $f; $g = $this->e[$j] + $this->V[$j][$j] * $f; - for ($k = $j+1; $k <= $i_; ++$k) { + + for ($k = $j + 1; $k <= $i_; ++$k) { $g += $this->V[$k][$j] * $this->d[$k]; $this->e[$k] += $this->V[$k][$j] * $f; } $this->e[$j] = $g; } + $f = 0.0; + if ($h === 0 || $h < 1e-32) { + $h = 1e-32; + } + for ($j = 0; $j < $i; ++$j) { - 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) { + for ($j = 0; $j < $i; ++$j) { $this->e[$j] -= $hh * $this->d[$j]; } for ($j = 0; $j < $i; ++$j) { @@ -147,7 +185,7 @@ class EigenvalueDecomposition for ($k = $j; $k <= $i_; ++$k) { $this->V[$k][$j] -= ($f * $this->e[$k] + $g * $this->d[$k]); } - $this->d[$j] = $this->V[$i-1][$j]; + $this->d[$j] = $this->V[$i - 1][$j]; $this->V[$i][$j] = 0.0; } } @@ -155,18 +193,18 @@ class EigenvalueDecomposition } // Accumulate transformations. - for ($i = 0; $i < $this->n-1; ++$i) { - $this->V[$this->n-1][$i] = $this->V[$i][$i]; + for ($i = 0; $i < $this->n - 1; ++$i) { + $this->V[$this->n - 1][$i] = $this->V[$i][$i]; $this->V[$i][$i] = 1.0; - $h = $this->d[$i+1]; + $h = $this->d[$i + 1]; if ($h != 0.0) { for ($k = 0; $k <= $i; ++$k) { - $this->d[$k] = $this->V[$k][$i+1] / $h; + $this->d[$k] = $this->V[$k][$i + 1] / $h; } for ($j = 0; $j <= $i; ++$j) { $g = 0.0; for ($k = 0; $k <= $i; ++$k) { - $g += $this->V[$k][$i+1] * $this->V[$k][$j]; + $g += $this->V[$k][$i + 1] * $this->V[$k][$j]; } for ($k = 0; $k <= $i; ++$k) { $this->V[$k][$j] -= $g * $this->d[$k]; @@ -174,13 +212,13 @@ class EigenvalueDecomposition } } for ($k = 0; $k <= $i; ++$k) { - $this->V[$k][$i+1] = 0.0; + $this->V[$k][$i + 1] = 0.0; } } - $this->d = $this->V[$this->n-1]; - $this->V[$this->n-1] = array_fill(0, $j, 0.0); - $this->V[$this->n-1][$this->n-1] = 1.0; + $this->d = $this->V[$this->n - 1]; + $this->V[$this->n - 1] = array_fill(0, $j, 0.0); + $this->V[$this->n - 1][$this->n - 1] = 1.0; $this->e[0] = 0.0; } @@ -196,9 +234,9 @@ class EigenvalueDecomposition private function tql2() { for ($i = 1; $i < $this->n; ++$i) { - $this->e[$i-1] = $this->e[$i]; + $this->e[$i - 1] = $this->e[$i]; } - $this->e[$this->n-1] = 0.0; + $this->e[$this->n - 1] = 0.0; $f = 0.0; $tst1 = 0.0; $eps = pow(2.0, -52.0); @@ -222,14 +260,14 @@ class EigenvalueDecomposition $iter += 1; // Compute implicit shift $g = $this->d[$l]; - $p = ($this->d[$l+1] - $g) / (2.0 * $this->e[$l]); + $p = ($this->d[$l + 1] - $g) / (2.0 * $this->e[$l]); $r = hypot($p, 1.0); if ($p < 0) { $r *= -1; } $this->d[$l] = $this->e[$l] / ($p + $r); - $this->d[$l+1] = $this->e[$l] * ($p + $r); - $dl1 = $this->d[$l+1]; + $this->d[$l + 1] = $this->e[$l] * ($p + $r); + $dl1 = $this->d[$l + 1]; $h = $g - $this->d[$l]; for ($i = $l + 2; $i < $this->n; ++$i) { $this->d[$i] -= $h; @@ -241,23 +279,23 @@ class EigenvalueDecomposition $c2 = $c3 = $c; $el1 = $this->e[$l + 1]; $s = $s2 = 0.0; - for ($i = $m-1; $i >= $l; --$i) { + for ($i = $m - 1; $i >= $l; --$i) { $c3 = $c2; $c2 = $c; $s2 = $s; $g = $c * $this->e[$i]; $h = $c * $p; $r = hypot($p, $this->e[$i]); - $this->e[$i+1] = $s * $r; + $this->e[$i + 1] = $s * $r; $s = $this->e[$i] / $r; $c = $p / $r; $p = $c * $this->d[$i] - $s * $g; - $this->d[$i+1] = $h + $s * ($c * $g + $s * $this->d[$i]); + $this->d[$i + 1] = $h + $s * ($c * $g + $s * $this->d[$i]); // Accumulate transformation. for ($k = 0; $k < $this->n; ++$k) { - $h = $this->V[$k][$i+1]; - $this->V[$k][$i+1] = $s * $this->V[$k][$i] + $c * $h; - $this->V[$k][$i] = $c * $this->V[$k][$i] - $s * $h; + $h = $this->V[$k][$i + 1]; + $this->V[$k][$i + 1] = $s * $this->V[$k][$i] + $c * $h; + $this->V[$k][$i] = $c * $this->V[$k][$i] - $s * $h; } } $p = -$s * $s2 * $c3 * $el1 * $this->e[$l] / $dl1; @@ -274,7 +312,7 @@ class EigenvalueDecomposition for ($i = 0; $i < $this->n - 1; ++$i) { $k = $i; $p = $this->d[$i]; - for ($j = $i+1; $j < $this->n; ++$j) { + for ($j = $i + 1; $j < $this->n; ++$j) { if ($this->d[$j] < $p) { $k = $j; $p = $this->d[$j]; @@ -304,19 +342,19 @@ class EigenvalueDecomposition private function orthes() { $low = 0; - $high = $this->n-1; + $high = $this->n - 1; - for ($m = $low+1; $m <= $high-1; ++$m) { + for ($m = $low + 1; $m <= $high - 1; ++$m) { // Scale column. $scale = 0.0; for ($i = $m; $i <= $high; ++$i) { - $scale = $scale + abs($this->H[$i][$m-1]); + $scale = $scale + abs($this->H[$i][$m - 1]); } if ($scale != 0.0) { // Compute Householder transformation. $h = 0.0; for ($i = $high; $i >= $m; --$i) { - $this->ort[$i] = $this->H[$i][$m-1] / $scale; + $this->ort[$i] = $this->H[$i][$m - 1] / $scale; $h += $this->ort[$i] * $this->ort[$i]; } $g = sqrt($h); @@ -348,7 +386,7 @@ class EigenvalueDecomposition } } $this->ort[$m] = $scale * $this->ort[$m]; - $this->H[$m][$m-1] = $scale * $g; + $this->H[$m][$m - 1] = $scale * $g; } } @@ -358,10 +396,10 @@ class EigenvalueDecomposition $this->V[$i][$j] = ($i == $j ? 1.0 : 0.0); } } - for ($m = $high-1; $m >= $low+1; --$m) { - if ($this->H[$m][$m-1] != 0.0) { - for ($i = $m+1; $i <= $high; ++$i) { - $this->ort[$i] = $this->H[$i][$m-1]; + for ($m = $high - 1; $m >= $low + 1; --$m) { + if ($this->H[$m][$m - 1] != 0.0) { + for ($i = $m + 1; $i <= $high; ++$i) { + $this->ort[$i] = $this->H[$i][$m - 1]; } for ($j = $m; $j <= $high; ++$j) { $g = 0.0; @@ -369,7 +407,7 @@ class EigenvalueDecomposition $g += $this->ort[$i] * $this->V[$i][$j]; } // Double division avoids possible underflow - $g = ($g / $this->ort[$m]) / $this->H[$m][$m-1]; + $g = ($g / $this->ort[$m]) / $this->H[$m][$m - 1]; for ($i = $m; $i <= $high; ++$i) { $this->V[$i][$j] += $g * $this->ort[$i]; } @@ -378,9 +416,13 @@ class EigenvalueDecomposition } } - /** - * Performs complex division. + * Performs complex division. + * + * @param int|float $xr + * @param int|float $xi + * @param int|float $yr + * @param int|float $yi */ private function cdiv($xr, $xi, $yr, $yi) { @@ -397,7 +439,6 @@ class EigenvalueDecomposition } } - /** * Nonsymmetric reduction from Hessenberg to real Schur form. * @@ -424,7 +465,7 @@ class EigenvalueDecomposition $this->d[$i] = $this->H[$i][$i]; $this->e[$i] = 0.0; } - for ($j = max($i-1, 0); $j < $nn; ++$j) { + for ($j = max($i - 1, 0); $j < $nn; ++$j) { $norm = $norm + abs($this->H[$i][$j]); } } @@ -435,11 +476,11 @@ class EigenvalueDecomposition // Look for single small sub-diagonal element $l = $n; while ($l > $low) { - $s = abs($this->H[$l-1][$l-1]) + abs($this->H[$l][$l]); + $s = abs($this->H[$l - 1][$l - 1]) + abs($this->H[$l][$l]); if ($s == 0.0) { $s = $norm; } - if (abs($this->H[$l][$l-1]) < $eps * $s) { + if (abs($this->H[$l][$l - 1]) < $eps * $s) { break; } --$l; @@ -453,13 +494,13 @@ class EigenvalueDecomposition --$n; $iter = 0; // Two roots found - } elseif ($l == $n-1) { - $w = $this->H[$n][$n-1] * $this->H[$n-1][$n]; - $p = ($this->H[$n-1][$n-1] - $this->H[$n][$n]) / 2.0; + } elseif ($l == $n - 1) { + $w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n]; + $p = ($this->H[$n - 1][$n - 1] - $this->H[$n][$n]) / 2.0; $q = $p * $p + $w; $z = sqrt(abs($q)); $this->H[$n][$n] = $this->H[$n][$n] + $exshift; - $this->H[$n-1][$n-1] = $this->H[$n-1][$n-1] + $exshift; + $this->H[$n - 1][$n - 1] = $this->H[$n - 1][$n - 1] + $exshift; $x = $this->H[$n][$n]; // Real pair if ($q >= 0) { @@ -468,14 +509,14 @@ class EigenvalueDecomposition } else { $z = $p - $z; } - $this->d[$n-1] = $x + $z; - $this->d[$n] = $this->d[$n-1]; + $this->d[$n - 1] = $x + $z; + $this->d[$n] = $this->d[$n - 1]; if ($z != 0.0) { $this->d[$n] = $x - $w / $z; } - $this->e[$n-1] = 0.0; + $this->e[$n - 1] = 0.0; $this->e[$n] = 0.0; - $x = $this->H[$n][$n-1]; + $x = $this->H[$n][$n - 1]; $s = abs($x) + abs($z); $p = $x / $s; $q = $z / $s; @@ -483,29 +524,29 @@ class EigenvalueDecomposition $p = $p / $r; $q = $q / $r; // Row modification - for ($j = $n-1; $j < $nn; ++$j) { - $z = $this->H[$n-1][$j]; - $this->H[$n-1][$j] = $q * $z + $p * $this->H[$n][$j]; + for ($j = $n - 1; $j < $nn; ++$j) { + $z = $this->H[$n - 1][$j]; + $this->H[$n - 1][$j] = $q * $z + $p * $this->H[$n][$j]; $this->H[$n][$j] = $q * $this->H[$n][$j] - $p * $z; } // Column modification for ($i = 0; $i <= $n; ++$i) { - $z = $this->H[$i][$n-1]; - $this->H[$i][$n-1] = $q * $z + $p * $this->H[$i][$n]; + $z = $this->H[$i][$n - 1]; + $this->H[$i][$n - 1] = $q * $z + $p * $this->H[$i][$n]; $this->H[$i][$n] = $q * $this->H[$i][$n] - $p * $z; } // Accumulate transformations for ($i = $low; $i <= $high; ++$i) { - $z = $this->V[$i][$n-1]; - $this->V[$i][$n-1] = $q * $z + $p * $this->V[$i][$n]; + $z = $this->V[$i][$n - 1]; + $this->V[$i][$n - 1] = $q * $z + $p * $this->V[$i][$n]; $this->V[$i][$n] = $q * $this->V[$i][$n] - $p * $z; } // Complex pair } else { - $this->d[$n-1] = $x + $p; - $this->d[$n] = $x + $p; - $this->e[$n-1] = $z; - $this->e[$n] = -$z; + $this->d[$n - 1] = $x + $p; + $this->d[$n] = $x + $p; + $this->e[$n - 1] = $z; + $this->e[$n] = -$z; } $n = $n - 2; $iter = 0; @@ -516,8 +557,8 @@ class EigenvalueDecomposition $y = 0.0; $w = 0.0; if ($l < $n) { - $y = $this->H[$n-1][$n-1]; - $w = $this->H[$n][$n-1] * $this->H[$n-1][$n]; + $y = $this->H[$n - 1][$n - 1]; + $w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n]; } // Wilkinson's original ad hoc shift if ($iter == 10) { @@ -525,7 +566,7 @@ class EigenvalueDecomposition for ($i = $low; $i <= $n; ++$i) { $this->H[$i][$i] -= $x; } - $s = abs($this->H[$n][$n-1]) + abs($this->H[$n-1][$n-2]); + $s = abs($this->H[$n][$n - 1]) + abs($this->H[$n - 1][$n - 2]); $x = $y = 0.75 * $s; $w = -0.4375 * $s * $s; } @@ -554,9 +595,9 @@ class EigenvalueDecomposition $z = $this->H[$m][$m]; $r = $x - $z; $s = $y - $z; - $p = ($r * $s - $w) / $this->H[$m+1][$m] + $this->H[$m][$m+1]; - $q = $this->H[$m+1][$m+1] - $z - $r - $s; - $r = $this->H[$m+2][$m+1]; + $p = ($r * $s - $w) / $this->H[$m + 1][$m] + $this->H[$m][$m + 1]; + $q = $this->H[$m + 1][$m + 1] - $z - $r - $s; + $r = $this->H[$m + 2][$m + 1]; $s = abs($p) + abs($q) + abs($r); $p = $p / $s; $q = $q / $s; @@ -564,25 +605,25 @@ class EigenvalueDecomposition if ($m == $l) { break; } - if (abs($this->H[$m][$m-1]) * (abs($q) + abs($r)) < - $eps * (abs($p) * (abs($this->H[$m-1][$m-1]) + abs($z) + abs($this->H[$m+1][$m+1])))) { + if (abs($this->H[$m][$m - 1]) * (abs($q) + abs($r)) < + $eps * (abs($p) * (abs($this->H[$m - 1][$m - 1]) + abs($z) + abs($this->H[$m + 1][$m + 1])))) { break; } --$m; } for ($i = $m + 2; $i <= $n; ++$i) { - $this->H[$i][$i-2] = 0.0; - if ($i > $m+2) { - $this->H[$i][$i-3] = 0.0; + $this->H[$i][$i - 2] = 0.0; + if ($i > $m + 2) { + $this->H[$i][$i - 3] = 0.0; } } // Double QR step involving rows l:n and columns m:n - for ($k = $m; $k <= $n-1; ++$k) { - $notlast = ($k != $n-1); + for ($k = $m; $k <= $n - 1; ++$k) { + $notlast = ($k != $n - 1); if ($k != $m) { - $p = $this->H[$k][$k-1]; - $q = $this->H[$k+1][$k-1]; - $r = ($notlast ? $this->H[$k+2][$k-1] : 0.0); + $p = $this->H[$k][$k - 1]; + $q = $this->H[$k + 1][$k - 1]; + $r = ($notlast ? $this->H[$k + 2][$k - 1] : 0.0); $x = abs($p) + abs($q) + abs($r); if ($x != 0.0) { $p = $p / $x; @@ -599,9 +640,9 @@ class EigenvalueDecomposition } if ($s != 0) { if ($k != $m) { - $this->H[$k][$k-1] = -$s * $x; + $this->H[$k][$k - 1] = -$s * $x; } elseif ($l != $m) { - $this->H[$k][$k-1] = -$this->H[$k][$k-1]; + $this->H[$k][$k - 1] = -$this->H[$k][$k - 1]; } $p = $p + $s; $x = $p / $s; @@ -611,33 +652,33 @@ class EigenvalueDecomposition $r = $r / $p; // Row modification for ($j = $k; $j < $nn; ++$j) { - $p = $this->H[$k][$j] + $q * $this->H[$k+1][$j]; + $p = $this->H[$k][$j] + $q * $this->H[$k + 1][$j]; if ($notlast) { - $p = $p + $r * $this->H[$k+2][$j]; - $this->H[$k+2][$j] = $this->H[$k+2][$j] - $p * $z; + $p = $p + $r * $this->H[$k + 2][$j]; + $this->H[$k + 2][$j] = $this->H[$k + 2][$j] - $p * $z; } $this->H[$k][$j] = $this->H[$k][$j] - $p * $x; - $this->H[$k+1][$j] = $this->H[$k+1][$j] - $p * $y; + $this->H[$k + 1][$j] = $this->H[$k + 1][$j] - $p * $y; } // Column modification - for ($i = 0; $i <= min($n, $k+3); ++$i) { - $p = $x * $this->H[$i][$k] + $y * $this->H[$i][$k+1]; + for ($i = 0; $i <= min($n, $k + 3); ++$i) { + $p = $x * $this->H[$i][$k] + $y * $this->H[$i][$k + 1]; if ($notlast) { - $p = $p + $z * $this->H[$i][$k+2]; - $this->H[$i][$k+2] = $this->H[$i][$k+2] - $p * $r; + $p = $p + $z * $this->H[$i][$k + 2]; + $this->H[$i][$k + 2] = $this->H[$i][$k + 2] - $p * $r; } $this->H[$i][$k] = $this->H[$i][$k] - $p; - $this->H[$i][$k+1] = $this->H[$i][$k+1] - $p * $q; + $this->H[$i][$k + 1] = $this->H[$i][$k + 1] - $p * $q; } // Accumulate transformations for ($i = $low; $i <= $high; ++$i) { - $p = $x * $this->V[$i][$k] + $y * $this->V[$i][$k+1]; + $p = $x * $this->V[$i][$k] + $y * $this->V[$i][$k + 1]; if ($notlast) { - $p = $p + $z * $this->V[$i][$k+2]; - $this->V[$i][$k+2] = $this->V[$i][$k+2] - $p * $r; + $p = $p + $z * $this->V[$i][$k + 2]; + $this->V[$i][$k + 2] = $this->V[$i][$k + 2] - $p * $r; } $this->V[$i][$k] = $this->V[$i][$k] - $p; - $this->V[$i][$k+1] = $this->V[$i][$k+1] - $p * $q; + $this->V[$i][$k + 1] = $this->V[$i][$k + 1] - $p * $q; } } // ($s != 0) } // k loop @@ -649,19 +690,20 @@ class EigenvalueDecomposition return; } - for ($n = $nn-1; $n >= 0; --$n) { + for ($n = $nn - 1; $n >= 0; --$n) { $p = $this->d[$n]; $q = $this->e[$n]; // Real vector if ($q == 0) { $l = $n; $this->H[$n][$n] = 1.0; - for ($i = $n-1; $i >= 0; --$i) { + for ($i = $n - 1; $i >= 0; --$i) { $w = $this->H[$i][$i] - $p; $r = 0.0; for ($j = $l; $j <= $n; ++$j) { $r = $r + $this->H[$i][$j] * $this->H[$j][$n]; } + if ($this->e[$i] < 0.0) { $z = $w; $s = $r; @@ -675,15 +717,15 @@ class EigenvalueDecomposition } // Solve real equations } else { - $x = $this->H[$i][$i+1]; - $y = $this->H[$i+1][$i]; + $x = $this->H[$i][$i + 1]; + $y = $this->H[$i + 1][$i]; $q = ($this->d[$i] - $p) * ($this->d[$i] - $p) + $this->e[$i] * $this->e[$i]; $t = ($x * $s - $z * $r) / $q; $this->H[$i][$n] = $t; if (abs($x) > abs($z)) { - $this->H[$i+1][$n] = (-$r - $w * $t) / $x; + $this->H[$i + 1][$n] = (-$r - $w * $t) / $x; } else { - $this->H[$i+1][$n] = (-$s - $y * $t) / $z; + $this->H[$i + 1][$n] = (-$s - $y * $t) / $z; } } // Overflow control @@ -697,24 +739,24 @@ class EigenvalueDecomposition } // Complex vector } elseif ($q < 0) { - $l = $n-1; + $l = $n - 1; // Last vector component imaginary so matrix is triangular - if (abs($this->H[$n][$n-1]) > abs($this->H[$n-1][$n])) { - $this->H[$n-1][$n-1] = $q / $this->H[$n][$n-1]; - $this->H[$n-1][$n] = -($this->H[$n][$n] - $p) / $this->H[$n][$n-1]; + if (abs($this->H[$n][$n - 1]) > abs($this->H[$n - 1][$n])) { + $this->H[$n - 1][$n - 1] = $q / $this->H[$n][$n - 1]; + $this->H[$n - 1][$n] = -($this->H[$n][$n] - $p) / $this->H[$n][$n - 1]; } else { - $this->cdiv(0.0, -$this->H[$n-1][$n], $this->H[$n-1][$n-1] - $p, $q); - $this->H[$n-1][$n-1] = $this->cdivr; - $this->H[$n-1][$n] = $this->cdivi; + $this->cdiv(0.0, -$this->H[$n - 1][$n], $this->H[$n - 1][$n - 1] - $p, $q); + $this->H[$n - 1][$n - 1] = $this->cdivr; + $this->H[$n - 1][$n] = $this->cdivi; } - $this->H[$n][$n-1] = 0.0; - $this->H[$n][$n] = 1.0; - for ($i = $n-2; $i >= 0; --$i) { + $this->H[$n][$n - 1] = 0.0; + $this->H[$n][$n] = 1.0; + for ($i = $n - 2; $i >= 0; --$i) { // double ra,sa,vr,vi; $ra = 0.0; $sa = 0.0; for ($j = $l; $j <= $n; ++$j) { - $ra = $ra + $this->H[$i][$j] * $this->H[$j][$n-1]; + $ra = $ra + $this->H[$i][$j] * $this->H[$j][$n - 1]; $sa = $sa + $this->H[$i][$j] * $this->H[$j][$n]; } $w = $this->H[$i][$i] - $p; @@ -726,35 +768,35 @@ class EigenvalueDecomposition $l = $i; if ($this->e[$i] == 0) { $this->cdiv(-$ra, -$sa, $w, $q); - $this->H[$i][$n-1] = $this->cdivr; - $this->H[$i][$n] = $this->cdivi; + $this->H[$i][$n - 1] = $this->cdivr; + $this->H[$i][$n] = $this->cdivi; } else { // Solve complex equations - $x = $this->H[$i][$i+1]; - $y = $this->H[$i+1][$i]; + $x = $this->H[$i][$i + 1]; + $y = $this->H[$i + 1][$i]; $vr = ($this->d[$i] - $p) * ($this->d[$i] - $p) + $this->e[$i] * $this->e[$i] - $q * $q; $vi = ($this->d[$i] - $p) * 2.0 * $q; if ($vr == 0.0 & $vi == 0.0) { $vr = $eps * $norm * (abs($w) + abs($q) + abs($x) + abs($y) + abs($z)); } $this->cdiv($x * $r - $z * $ra + $q * $sa, $x * $s - $z * $sa - $q * $ra, $vr, $vi); - $this->H[$i][$n-1] = $this->cdivr; - $this->H[$i][$n] = $this->cdivi; + $this->H[$i][$n - 1] = $this->cdivr; + $this->H[$i][$n] = $this->cdivi; if (abs($x) > (abs($z) + abs($q))) { - $this->H[$i+1][$n-1] = (-$ra - $w * $this->H[$i][$n-1] + $q * $this->H[$i][$n]) / $x; - $this->H[$i+1][$n] = (-$sa - $w * $this->H[$i][$n] - $q * $this->H[$i][$n-1]) / $x; + $this->H[$i + 1][$n - 1] = (-$ra - $w * $this->H[$i][$n - 1] + $q * $this->H[$i][$n]) / $x; + $this->H[$i + 1][$n] = (-$sa - $w * $this->H[$i][$n] - $q * $this->H[$i][$n - 1]) / $x; } else { - $this->cdiv(-$r - $y * $this->H[$i][$n-1], -$s - $y * $this->H[$i][$n], $z, $q); - $this->H[$i+1][$n-1] = $this->cdivr; - $this->H[$i+1][$n] = $this->cdivi; + $this->cdiv(-$r - $y * $this->H[$i][$n - 1], -$s - $y * $this->H[$i][$n], $z, $q); + $this->H[$i + 1][$n - 1] = $this->cdivr; + $this->H[$i + 1][$n] = $this->cdivi; } } // Overflow control - $t = max(abs($this->H[$i][$n-1]), abs($this->H[$i][$n])); + $t = max(abs($this->H[$i][$n - 1]), abs($this->H[$i][$n])); if (($eps * $t) * $t > 1) { for ($j = $i; $j <= $n; ++$j) { - $this->H[$j][$n-1] = $this->H[$j][$n-1] / $t; - $this->H[$j][$n] = $this->H[$j][$n] / $t; + $this->H[$j][$n - 1] = $this->H[$j][$n - 1] / $t; + $this->H[$j][$n] = $this->H[$j][$n] / $t; } } } // end else @@ -772,7 +814,7 @@ class EigenvalueDecomposition } // Back transformation to get eigenvectors of original matrix - for ($j = $nn-1; $j >= $low; --$j) { + for ($j = $nn - 1; $j >= $low; --$j) { for ($i = $low; $i <= $high; ++$i) { $z = 0.0; for ($k = $low; $k <= min($j, $high); ++$k) { @@ -783,45 +825,12 @@ class EigenvalueDecomposition } } // end hqr2 - /** - * Constructor: Check for symmetry, then construct the eigenvalue decomposition + * Return the eigenvector matrix * - * @param array $Arg - */ - public function __construct(array $Arg) - { - $this->A = $Arg; - $this->n = count($Arg[0]); - - $issymmetric = true; - for ($j = 0; ($j < $this->n) & $issymmetric; ++$j) { - for ($i = 0; ($i < $this->n) & $issymmetric; ++$i) { - $issymmetric = ($this->A[$i][$j] == $this->A[$j][$i]); - } - } - - if ($issymmetric) { - $this->V = $this->A; - // Tridiagonalize. - $this->tred2(); - // Diagonalize. - $this->tql2(); - } else { - $this->H = $this->A; - $this->ort = []; - // Reduce to Hessenberg form. - $this->orthes(); - // Reduce Hessenberg to real Schur form. - $this->hqr2(); - } - } - - /** - * Return the eigenvector matrix + * @access public * - * @access public - * @return array + * @return array */ public function getEigenvectors() { @@ -831,20 +840,21 @@ class EigenvalueDecomposition $vectors = new Matrix($vectors); $vectors = array_map(function ($vect) { $sum = 0; - for ($i=0; $itranspose()->toArray()); return $vectors; } - /** * Return the real parts of the eigenvalues
* d = real(diag(D)); @@ -856,7 +866,6 @@ class EigenvalueDecomposition return $this->d; } - /** * Return the imaginary parts of the eigenvalues
* d = imag(diag(D)) @@ -868,7 +877,6 @@ class EigenvalueDecomposition return $this->e; } - /** * Return the block diagonal eigenvalue matrix * @@ -876,15 +884,19 @@ class EigenvalueDecomposition */ public function getDiagonalEigenvalues() { + $D = []; + for ($i = 0; $i < $this->n; ++$i) { $D[$i] = array_fill(0, $this->n, 0.0); $D[$i][$i] = $this->d[$i]; if ($this->e[$i] == 0) { continue; } + $o = ($this->e[$i] > 0) ? $i + 1 : $i - 1; $D[$i][$o] = $this->e[$i]; } + return $D; } } // class EigenvalueDecomposition diff --git a/lib/mlbackend/php/phpml/src/Phpml/Math/LinearAlgebra/LUDecomposition.php b/lib/mlbackend/php/phpml/src/Phpml/Math/LinearAlgebra/LUDecomposition.php new file mode 100644 index 00000000000..de6a15da3f4 --- /dev/null +++ b/lib/mlbackend/php/phpml/src/Phpml/Math/LinearAlgebra/LUDecomposition.php @@ -0,0 +1,305 @@ += n, the LU decomposition is an m-by-n + * unit lower triangular matrix L, an n-by-n upper triangular matrix U, + * and a permutation vector piv of length m so that A(piv,:) = L*U. + * If m < n, then L is m-by-m and U is m-by-n. + * + * The LU decompostion with pivoting always exists, even if the matrix is + * singular, so the constructor will never fail. The primary use of the + * LU decomposition is in the solution of square systems of simultaneous + * linear equations. This will fail if isNonsingular() returns false. + * + * @author Paul Meagher + * @author Bartosz Matosiuk + * @author Michael Bommarito + * @version 1.1 + * @license PHP v3.0 + * + * Slightly changed to adapt the original code to PHP-ML library + * @date 2017/04/24 + * @author Mustafa Karabulut + */ + +namespace Phpml\Math\LinearAlgebra; + +use Phpml\Math\Matrix; +use Phpml\Exception\MatrixException; + +class LUDecomposition +{ + /** + * Decomposition storage + * @var array + */ + private $LU = []; + + /** + * Row dimension. + * @var int + */ + private $m; + + /** + * Column dimension. + * @var int + */ + private $n; + + /** + * Pivot sign. + * @var int + */ + private $pivsign; + + /** + * Internal storage of pivot vector. + * @var array + */ + private $piv = []; + + + /** + * Constructs Structure to access L, U and piv. + * + * @param Matrix $A Rectangular matrix + * + * @throws MatrixException + */ + public function __construct(Matrix $A) + { + if ($A->getRows() != $A->getColumns()) { + throw MatrixException::notSquareMatrix(); + } + + // Use a "left-looking", dot-product, Crout/Doolittle algorithm. + $this->LU = $A->toArray(); + $this->m = $A->getRows(); + $this->n = $A->getColumns(); + for ($i = 0; $i < $this->m; ++$i) { + $this->piv[$i] = $i; + } + $this->pivsign = 1; + $LUcolj = []; + + // Outer loop. + for ($j = 0; $j < $this->n; ++$j) { + // Make a copy of the j-th column to localize references. + for ($i = 0; $i < $this->m; ++$i) { + $LUcolj[$i] = &$this->LU[$i][$j]; + } + // Apply previous transformations. + for ($i = 0; $i < $this->m; ++$i) { + $LUrowi = $this->LU[$i]; + // Most of the time is spent in the following dot product. + $kmax = min($i, $j); + $s = 0.0; + for ($k = 0; $k < $kmax; ++$k) { + $s += $LUrowi[$k] * $LUcolj[$k]; + } + $LUrowi[$j] = $LUcolj[$i] -= $s; + } + // Find pivot and exchange if necessary. + $p = $j; + for ($i = $j + 1; $i < $this->m; ++$i) { + if (abs($LUcolj[$i]) > abs($LUcolj[$p])) { + $p = $i; + } + } + if ($p != $j) { + for ($k = 0; $k < $this->n; ++$k) { + $t = $this->LU[$p][$k]; + $this->LU[$p][$k] = $this->LU[$j][$k]; + $this->LU[$j][$k] = $t; + } + $k = $this->piv[$p]; + $this->piv[$p] = $this->piv[$j]; + $this->piv[$j] = $k; + $this->pivsign = $this->pivsign * -1; + } + // Compute multipliers. + if (($j < $this->m) && ($this->LU[$j][$j] != 0.0)) { + for ($i = $j + 1; $i < $this->m; ++$i) { + $this->LU[$i][$j] /= $this->LU[$j][$j]; + } + } + } + } // function __construct() + + + /** + * Get lower triangular factor. + * + * @return Matrix Lower triangular factor + */ + public function getL() + { + $L = []; + for ($i = 0; $i < $this->m; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + if ($i > $j) { + $L[$i][$j] = $this->LU[$i][$j]; + } elseif ($i == $j) { + $L[$i][$j] = 1.0; + } else { + $L[$i][$j] = 0.0; + } + } + } + return new Matrix($L); + } // function getL() + + + /** + * Get upper triangular factor. + * + * @return Matrix Upper triangular factor + */ + public function getU() + { + $U = []; + for ($i = 0; $i < $this->n; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + if ($i <= $j) { + $U[$i][$j] = $this->LU[$i][$j]; + } else { + $U[$i][$j] = 0.0; + } + } + } + return new Matrix($U); + } // function getU() + + + /** + * Return pivot permutation vector. + * + * @return array Pivot vector + */ + public function getPivot() + { + return $this->piv; + } // function getPivot() + + + /** + * Alias for getPivot + * + * @see getPivot + */ + public function getDoublePivot() + { + return $this->getPivot(); + } // function getDoublePivot() + + + /** + * Is the matrix nonsingular? + * + * @return true if U, and hence A, is nonsingular. + */ + public function isNonsingular() + { + for ($j = 0; $j < $this->n; ++$j) { + if ($this->LU[$j][$j] == 0) { + return false; + } + } + + return true; + } // function isNonsingular() + + + /** + * Count determinants + * + * @return float|int d matrix determinant + * + * @throws MatrixException + */ + public function det() + { + if ($this->m !== $this->n) { + throw MatrixException::notSquareMatrix(); + } + + $d = $this->pivsign; + for ($j = 0; $j < $this->n; ++$j) { + $d *= $this->LU[$j][$j]; + } + + return $d; + } // function det() + + + /** + * Solve A*X = B + * + * @param Matrix $B A Matrix with as many rows as A and any number of columns. + * + * @return array X so that L*U*X = B(piv,:) + * + * @throws MatrixException + */ + public function solve(Matrix $B) + { + if ($B->getRows() != $this->m) { + throw MatrixException::notSquareMatrix(); + } + + if (!$this->isNonsingular()) { + throw MatrixException::singularMatrix(); + } + + // Copy right hand side with pivoting + $nx = $B->getColumns(); + $X = $this->getSubMatrix($B->toArray(), $this->piv, 0, $nx - 1); + // Solve L*Y = B(piv,:) + for ($k = 0; $k < $this->n; ++$k) { + for ($i = $k + 1; $i < $this->n; ++$i) { + for ($j = 0; $j < $nx; ++$j) { + $X[$i][$j] -= $X[$k][$j] * $this->LU[$i][$k]; + } + } + } + // Solve U*X = Y; + for ($k = $this->n - 1; $k >= 0; --$k) { + for ($j = 0; $j < $nx; ++$j) { + $X[$k][$j] /= $this->LU[$k][$k]; + } + for ($i = 0; $i < $k; ++$i) { + for ($j = 0; $j < $nx; ++$j) { + $X[$i][$j] -= $X[$k][$j] * $this->LU[$i][$k]; + } + } + } + return $X; + } // function solve() + + /** + * @param array $matrix + * @param array $RL + * @param int $j0 + * @param int $jF + * + * @return array + */ + protected function getSubMatrix(array $matrix, array $RL, int $j0, int $jF) + { + $m = count($RL); + $n = $jF - $j0; + $R = array_fill(0, $m, array_fill(0, $n + 1, 0.0)); + + for ($i = 0; $i < $m; ++$i) { + for ($j = $j0; $j <= $jF; ++$j) { + $R[$i][$j - $j0] = $matrix[$RL[$i]][$j]; + } + } + + return $R; + } +} // class LUDecomposition diff --git a/lib/mlbackend/php/phpml/src/Phpml/Math/Matrix.php b/lib/mlbackend/php/phpml/src/Phpml/Math/Matrix.php index 25101f3f4ab..3c310528dc5 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Math/Matrix.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Math/Matrix.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Phpml\Math; +use Phpml\Math\LinearAlgebra\LUDecomposition; use Phpml\Exception\InvalidArgumentException; use Phpml\Exception\MatrixException; @@ -137,32 +138,9 @@ class Matrix throw MatrixException::notSquareMatrix(); } - return $this->determinant = $this->calculateDeterminant(); - } + $lu = new LUDecomposition($this); - /** - * @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 $this->determinant = $lu->det(); } /** @@ -255,6 +233,8 @@ class Matrix * Element-wise addition of the matrix with another one * * @param Matrix $other + * + * @return Matrix */ public function add(Matrix $other) { @@ -265,6 +245,8 @@ class Matrix * Element-wise subtracting of another matrix from this one * * @param Matrix $other + * + * @return Matrix */ public function subtract(Matrix $other) { @@ -275,7 +257,9 @@ class Matrix * Element-wise addition or substraction depending on the given sign parameter * * @param Matrix $other - * @param type $sign + * @param int $sign + * + * @return Matrix */ protected function _add(Matrix $other, $sign = 1) { @@ -283,13 +267,13 @@ class Matrix $a2 = $other->toArray(); $newMatrix = []; - for ($i=0; $i < $this->rows; $i++) { - for ($k=0; $k < $this->columns; $k++) { + for ($i = 0; $i < $this->rows; ++$i) { + for ($k = 0; $k < $this->columns; ++$k) { $newMatrix[$i][$k] = $a1[$i][$k] + $sign * $a2[$i][$k]; } } - return new Matrix($newMatrix, false); + return new self($newMatrix, false); } /** @@ -303,21 +287,26 @@ class Matrix throw MatrixException::notSquareMatrix(); } - if ($this->isSingular()) { - throw MatrixException::singularMatrix(); - } + $LU = new LUDecomposition($this); + $identity = $this->getIdentity(); + $inverse = $LU->solve($identity); - $newMatrix = []; + return new self($inverse, false); + } + + /** + * Returns diagonal identity matrix of the same size of this matrix + * + * @return Matrix + */ + protected function getIdentity() + { + $array = array_fill(0, $this->rows, array_fill(0, $this->columns, 0)); for ($i = 0; $i < $this->rows; ++$i) { - 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; - } + $array[$i][$i] = 1; } - $cofactorMatrix = new self($newMatrix, false); - - return $cofactorMatrix->transpose()->divideByScalar($this->getDeterminant()); + return new self($array, false); } /** @@ -363,7 +352,7 @@ class Matrix */ public static function transposeArray(array $array) { - return (new Matrix($array, false))->transpose()->toArray(); + return (new self($array, false))->transpose()->toArray(); } /** @@ -377,8 +366,8 @@ class Matrix */ public static function dot(array $array1, array $array2) { - $m1 = new Matrix($array1, false); - $m2 = new Matrix($array2, false); + $m1 = new self($array1, false); + $m2 = new self($array2, false); return $m1->multiply($m2->transpose())->toArray()[0]; } diff --git a/lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/Covariance.php b/lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/Covariance.php index 4a9b613d89f..8c8781d7159 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/Covariance.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/Covariance.php @@ -13,7 +13,7 @@ class Covariance * * @param array $x * @param array $y - * @param bool $sample + * @param bool $sample * @param float $meanX * @param float $meanY * @@ -57,14 +57,18 @@ class Covariance * Calculates covariance of two dimensions, i and k in the given data. * * @param array $data - * @param int $i - * @param int $k - * @param type $sample - * @param int $n + * @param int $i + * @param int $k + * @param bool $sample * @param float $meanX * @param float $meanY + * + * @return float + * + * @throws InvalidArgumentException + * @throws \Exception */ - public static function fromDataset(array $data, int $i, int $k, $sample = true, float $meanX = null, float $meanY = null) + public static function fromDataset(array $data, int $i, int $k, bool $sample = true, float $meanX = null, float $meanY = null) { if (empty($data)) { throw InvalidArgumentException::arrayCantBeEmpty(); @@ -123,7 +127,8 @@ class Covariance /** * Returns the covariance matrix of n-dimensional data * - * @param array $data + * @param array $data + * @param array|null $means * * @return array */ @@ -133,19 +138,20 @@ class Covariance if ($means === null) { $means = []; - for ($i=0; $i < $n; $i++) { + for ($i = 0; $i < $n; ++$i) { $means[] = Mean::arithmetic(array_column($data, $i)); } } $cov = []; - for ($i=0; $i < $n; $i++) { - for ($k=0; $k < $n; $k++) { + for ($i = 0; $i < $n; ++$i) { + for ($k = 0; $k < $n; ++$k) { if ($i > $k) { $cov[$i][$k] = $cov[$k][$i]; } else { - $cov[$i][$k] = Covariance::fromDataset( - $data, $i, $k, true, $means[$i], $means[$k]); + $cov[$i][$k] = self::fromDataset( + $data, $i, $k, true, $means[$i], $means[$k] + ); } } } diff --git a/lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/Gaussian.php b/lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/Gaussian.php index df27f076dc6..d09edba3b26 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/Gaussian.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/Gaussian.php @@ -31,7 +31,7 @@ class Gaussian * * @param float $value * - * @return type + * @return float|int */ public function pdf(float $value) { diff --git a/lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/Mean.php b/lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/Mean.php index 581a1225903..bd9657ed4d7 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/Mean.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Math/Statistic/Mean.php @@ -68,7 +68,7 @@ class Mean */ private static function checkArrayLength(array $array) { - if (0 == count($array)) { + if (empty($array)) { throw InvalidArgumentException::arrayCantBeEmpty(); } } diff --git a/lib/mlbackend/php/phpml/src/Phpml/Metric/ClassificationReport.php b/lib/mlbackend/php/phpml/src/Phpml/Metric/ClassificationReport.php index c7cc147898f..6fc026c0ab6 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Metric/ClassificationReport.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Metric/ClassificationReport.php @@ -112,8 +112,8 @@ class ClassificationReport private function computeAverage() { foreach (['precision', 'recall', 'f1score'] as $metric) { - $values = array_filter($this->$metric); - if (0 == count($values)) { + $values = array_filter($this->{$metric}); + if (empty($values)) { $this->average[$metric] = 0.0; continue; } diff --git a/lib/mlbackend/php/phpml/src/Phpml/ModelManager.php b/lib/mlbackend/php/phpml/src/Phpml/ModelManager.php index c03d0ed25c7..08ab3e63907 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/ModelManager.php +++ b/lib/mlbackend/php/phpml/src/Phpml/ModelManager.php @@ -11,7 +11,8 @@ class ModelManager { /** * @param Estimator $estimator - * @param string $filepath + * @param string $filepath + * * @throws FileException * @throws SerializeException */ @@ -23,7 +24,7 @@ class ModelManager $serialized = serialize($estimator); if (empty($serialized)) { - throw SerializeException::cantSerialize(get_type($estimator)); + throw SerializeException::cantSerialize(gettype($estimator)); } $result = file_put_contents($filepath, $serialized, LOCK_EX); @@ -34,7 +35,9 @@ class ModelManager /** * @param string $filepath + * * @return Estimator + * * @throws FileException * @throws SerializeException */ diff --git a/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php index af2d72397d9..b20f6bbc25c 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php +++ b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php @@ -32,6 +32,14 @@ abstract class LayeredNetwork implements Network return $this->layers; } + /** + * @return void + */ + public function removeLayers() + { + unset($this->layers); + } + /** * @return Layer */ @@ -71,7 +79,7 @@ abstract class LayeredNetwork implements Network foreach ($this->getLayers() as $layer) { foreach ($layer->getNodes() as $node) { if ($node instanceof Neuron) { - $node->refresh(); + $node->reset(); } } } diff --git a/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php index 04664f9c7db..25037743093 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -4,32 +4,147 @@ declare(strict_types=1); namespace Phpml\NeuralNetwork\Network; +use Phpml\Estimator; +use Phpml\IncrementalEstimator; use Phpml\Exception\InvalidArgumentException; +use Phpml\NeuralNetwork\Training\Backpropagation; use Phpml\NeuralNetwork\ActivationFunction; use Phpml\NeuralNetwork\Layer; use Phpml\NeuralNetwork\Node\Bias; use Phpml\NeuralNetwork\Node\Input; use Phpml\NeuralNetwork\Node\Neuron; use Phpml\NeuralNetwork\Node\Neuron\Synapse; +use Phpml\Helper\Predictable; -class MultilayerPerceptron extends LayeredNetwork +abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, IncrementalEstimator { + use Predictable; + /** - * @param array $layers + * @var int + */ + private $inputLayerFeatures; + + /** + * @var array + */ + private $hiddenLayers; + + /** + * @var array + */ + protected $classes = []; + + /** + * @var int + */ + private $iterations; + + /** + * @var ActivationFunction + */ + protected $activationFunction; + + /** + * @var int + */ + private $theta; + + /** + * @var Backpropagation + */ + protected $backpropagation = null; + + /** + * @param int $inputLayerFeatures + * @param array $hiddenLayers + * @param array $classes + * @param int $iterations * @param ActivationFunction|null $activationFunction + * @param int $theta * * @throws InvalidArgumentException */ - public function __construct(array $layers, ActivationFunction $activationFunction = null) + public function __construct(int $inputLayerFeatures, array $hiddenLayers, array $classes, int $iterations = 10000, ActivationFunction $activationFunction = null, int $theta = 1) { - if (count($layers) < 2) { + if (empty($hiddenLayers)) { throw InvalidArgumentException::invalidLayersNumber(); } - $this->addInputLayer(array_shift($layers)); - $this->addNeuronLayers($layers, $activationFunction); + if (count($classes) < 2) { + throw InvalidArgumentException::invalidClassesNumber(); + } + + $this->classes = array_values($classes); + $this->iterations = $iterations; + $this->inputLayerFeatures = $inputLayerFeatures; + $this->hiddenLayers = $hiddenLayers; + $this->activationFunction = $activationFunction; + $this->theta = $theta; + + $this->initNetwork(); + } + + /** + * @return void + */ + private function initNetwork() + { + $this->addInputLayer($this->inputLayerFeatures); + $this->addNeuronLayers($this->hiddenLayers, $this->activationFunction); + $this->addNeuronLayers([count($this->classes)], $this->activationFunction); + $this->addBiasNodes(); $this->generateSynapses(); + + $this->backpropagation = new Backpropagation($this->theta); + } + + /** + * @param array $samples + * @param array $targets + */ + public function train(array $samples, array $targets) + { + $this->reset(); + $this->initNetwork(); + $this->partialTrain($samples, $targets, $this->classes); + } + + /** + * @param array $samples + * @param array $targets + */ + public function partialTrain(array $samples, array $targets, array $classes = []) + { + if (!empty($classes) && array_values($classes) !== $this->classes) { + // We require the list of classes in the constructor. + throw InvalidArgumentException::inconsistentClasses(); + } + + for ($i = 0; $i < $this->iterations; ++$i) { + $this->trainSamples($samples, $targets); + } + } + + /** + * @param array $sample + * @param mixed $target + */ + abstract protected function trainSample(array $sample, $target); + + /** + * @param array $sample + * @return mixed + */ + abstract protected function predictSample(array $sample); + + /** + * @return void + */ + protected function reset() + { + $this->removeLayers(); } /** @@ -92,4 +207,15 @@ class MultilayerPerceptron extends LayeredNetwork $nextNeuron->addSynapse(new Synapse($currentNeuron)); } } + + /** + * @param array $samples + * @param array $targets + */ + private function trainSamples(array $samples, array $targets) + { + foreach ($targets as $key => $target) { + $this->trainSample($samples[$key], $target); + } + } } diff --git a/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Node/Neuron.php b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Node/Neuron.php index 519443844a5..7c246bed2d2 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Node/Neuron.php +++ b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Node/Neuron.php @@ -68,7 +68,7 @@ class Neuron implements Node return $this->output; } - public function refresh() + public function reset() { $this->output = 0; } diff --git a/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Training.php b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Training.php index d876af2e50f..fcb6d73cb66 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Training.php +++ b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Training.php @@ -9,8 +9,6 @@ interface Training /** * @param array $samples * @param array $targets - * @param float $desiredError - * @param int $maxIterations */ - public function train(array $samples, array $targets, float $desiredError = 0.001, int $maxIterations = 10000); + public function train(array $samples, array $targets); } diff --git a/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Training/Backpropagation.php b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Training/Backpropagation.php index 136e8bf5f28..ba90b45e9ff 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/lib/mlbackend/php/phpml/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -4,18 +4,11 @@ declare(strict_types=1); namespace Phpml\NeuralNetwork\Training; -use Phpml\NeuralNetwork\Network; use Phpml\NeuralNetwork\Node\Neuron; -use Phpml\NeuralNetwork\Training; use Phpml\NeuralNetwork\Training\Backpropagation\Sigma; -class Backpropagation implements Training +class Backpropagation { - /** - * @var Network - */ - private $network; - /** * @var int */ @@ -24,97 +17,67 @@ class Backpropagation implements Training /** * @var array */ - private $sigmas; + private $sigmas = null; /** - * @param Network $network - * @param int $theta + * @var array */ - public function __construct(Network $network, int $theta = 1) + private $prevSigmas = null; + + /** + * @param int $theta + */ + public function __construct(int $theta) { - $this->network = $network; $this->theta = $theta; } /** - * @param array $samples - * @param array $targets - * @param float $desiredError - * @param int $maxIterations + * @param array $layers + * @param mixed $targetClass */ - public function train(array $samples, array $targets, float $desiredError = 0.001, int $maxIterations = 10000) + public function backpropagate(array $layers, $targetClass) { - 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); + // Backpropagation. for ($i = $layersNumber; $i > 1; --$i) { + $this->sigmas = []; foreach ($layers[$i - 1]->getNodes() as $key => $neuron) { if ($neuron instanceof Neuron) { - $sigma = $this->getSigma($neuron, $target, $key, $i == $layersNumber); + $sigma = $this->getSigma($neuron, $targetClass, $key, $i == $layersNumber); foreach ($neuron->getSynapses() as $synapse) { $synapse->changeWeight($this->theta * $sigma * $synapse->getNode()->getOutput()); } } } + $this->prevSigmas = $this->sigmas; } + + // Clean some memory (also it helps make MLP persistency & children more maintainable). + $this->sigmas = null; + $this->prevSigmas = null; } /** * @param Neuron $neuron - * @param array $target + * @param int $targetClass * @param int $key * @param bool $lastLayer * * @return float */ - private function getSigma(Neuron $neuron, array $target, int $key, bool $lastLayer): float + private function getSigma(Neuron $neuron, int $targetClass, int $key, bool $lastLayer): float { $neuronOutput = $neuron->getOutput(); $sigma = $neuronOutput * (1 - $neuronOutput); if ($lastLayer) { - $sigma *= ($target[$key] - $neuronOutput); + $value = 0; + if ($targetClass === $key) { + $value = 1; + } + $sigma *= ($value - $neuronOutput); } else { $sigma *= $this->getPrevSigma($neuron); } @@ -133,28 +96,10 @@ class Backpropagation implements Training { $sigma = 0.0; - foreach ($this->sigmas as $neuronSigma) { + foreach ($this->prevSigmas as $neuronSigma) { $sigma += $neuronSigma->getSigmaForNeuron($neuron); } return $sigma; } - - /** - * @param array $result - * @param array $target - * @param float $desiredError - * - * @return bool - */ - private function isResultWithinError(array $result, array $target, float $desiredError) - { - foreach ($target as $key => $value) { - if ($result[$key] > $value + $desiredError || $result[$key] < $value - $desiredError) { - return false; - } - } - - return true; - } } diff --git a/lib/mlbackend/php/phpml/src/Phpml/Preprocessing/Normalizer.php b/lib/mlbackend/php/phpml/src/Phpml/Preprocessing/Normalizer.php index 8392db7bd1d..c61b4478402 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/Preprocessing/Normalizer.php +++ b/lib/mlbackend/php/phpml/src/Phpml/Preprocessing/Normalizer.php @@ -84,7 +84,7 @@ class Normalizer implements Preprocessor $this->fit($samples); foreach ($samples as &$sample) { - $this->$method($sample); + $this->{$method}($sample); } } diff --git a/lib/mlbackend/php/phpml/src/Phpml/Regression/MLPRegressor.php b/lib/mlbackend/php/phpml/src/Phpml/Regression/MLPRegressor.php deleted file mode 100644 index 72e6a81e52b..00000000000 --- a/lib/mlbackend/php/phpml/src/Phpml/Regression/MLPRegressor.php +++ /dev/null @@ -1,80 +0,0 @@ -hiddenLayers = $hiddenLayers; - $this->desiredError = $desiredError; - $this->maxIterations = $maxIterations; - $this->activationFunction = $activationFunction; - } - - /** - * @param array $samples - * @param array $targets - */ - public function train(array $samples, array $targets) - { - $layers = $this->hiddenLayers; - array_unshift($layers, count($samples[0])); - $layers[] = count($targets[0]); - - $this->perceptron = new MultilayerPerceptron($layers, $this->activationFunction); - - $trainer = new Backpropagation($this->perceptron); - $trainer->train($samples, $targets, $this->desiredError, $this->maxIterations); - } - - /** - * @param array $sample - * - * @return array - */ - protected function predictSample(array $sample) - { - return $this->perceptron->setInput($sample)->getOutput(); - } -} diff --git a/lib/mlbackend/php/phpml/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/lib/mlbackend/php/phpml/src/Phpml/SupportVectorMachine/SupportVectorMachine.php index f3828b4d024..c6ec0178b82 100644 --- a/lib/mlbackend/php/phpml/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/lib/mlbackend/php/phpml/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -9,8 +9,8 @@ use Phpml\Helper\Trainable; class SupportVectorMachine { use Trainable; - - /** + + /** * @var int */ private $type; @@ -128,6 +128,30 @@ class SupportVectorMachine $this->varPath = $rootPath.'var'.DIRECTORY_SEPARATOR; } + /** + * @param string $binPath + * + * @return $this + */ + public function setBinPath(string $binPath) + { + $this->binPath = $binPath; + + return $this; + } + + /** + * @param string $varPath + * + * @return $this + */ + public function setVarPath(string $varPath) + { + $this->varPath = $varPath; + + return $this; + } + /** * @param array $samples * @param array $targets @@ -210,8 +234,8 @@ class SupportVectorMachine } /** - * @param $trainingSetFileName - * @param $modelFileName + * @param string $trainingSetFileName + * @param string $modelFileName * * @return string */ diff --git a/lib/mlbackend/php/readme_moodle.txt b/lib/mlbackend/php/readme_moodle.txt new file mode 100644 index 00000000000..c0d1435dc38 --- /dev/null +++ b/lib/mlbackend/php/readme_moodle.txt @@ -0,0 +1,7 @@ +Description of php-ml import into mlbackend_php. + +The current version is de50490. + +Prodedure: +* Get rid of everything else than src/ directory and LICENSE +* Copy src/ and LICENSE into lib/mlbackend/php/phpml/