mirror of
https://github.com/Llewellynvdm/php-ml.git
synced 2025-01-10 00:37:55 +00:00
parent
43f15d2f7e
commit
7ab80b6e56
@ -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;
|
||||
@ -126,6 +128,7 @@ class DecisionTree implements Classifier
|
||||
/**
|
||||
* @param array $records
|
||||
* @param int $depth
|
||||
*
|
||||
* @return DecisionTreeLeaf
|
||||
*/
|
||||
protected function getSplitLeaf(array $records, int $depth = 0) : DecisionTreeLeaf
|
||||
@ -166,7 +169,7 @@ class DecisionTree implements Classifier
|
||||
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
|
||||
@ -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)
|
||||
@ -460,6 +475,7 @@ class DecisionTree implements Classifier
|
||||
*
|
||||
* @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 {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -59,13 +59,13 @@ class Bagging implements Classifier
|
||||
private $samples = [];
|
||||
|
||||
/**
|
||||
* Creates an ensemble classifier with given number of base classifiers<br>
|
||||
* 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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
@ -63,7 +66,10 @@ class RandomForest extends Bagging
|
||||
*
|
||||
* @param string $classifier
|
||||
* @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);
|
||||
|
@ -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,8 +32,12 @@ 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 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)
|
||||
|
@ -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;
|
||||
@ -291,7 +293,7 @@ class DecisionStump extends WeightedClassifier
|
||||
if (!isset($prob[$predicted][$target])) {
|
||||
$prob[$predicted][$target] = 0;
|
||||
}
|
||||
$prob[$predicted][$target]++;
|
||||
++$prob[$predicted][$target];
|
||||
}
|
||||
|
||||
// Calculate probabilities: Proportion of labels in each leaf
|
||||
|
@ -4,12 +4,10 @@ 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)
|
||||
*/
|
||||
@ -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)
|
||||
{
|
||||
|
@ -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 <br>
|
||||
* number of iterations used while training the perceptron
|
||||
*
|
||||
* Learning rate should be a float value between 0.0(exclusive) and 1.0(inclusive) <br>
|
||||
* 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)
|
||||
{
|
||||
@ -187,6 +189,8 @@ class Perceptron implements Classifier, IncrementalEstimator
|
||||
*
|
||||
* @param array $samples
|
||||
* @param array $targets
|
||||
* @param \Closure $gradientFunc
|
||||
* @param bool $isBatch
|
||||
*/
|
||||
protected function runGradientDescent(array $samples, array $targets, \Closure $gradientFunc, bool $isBatch = false)
|
||||
{
|
||||
@ -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)
|
||||
|
@ -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);
|
||||
@ -117,6 +117,7 @@ class NaiveBayes implements Classifier
|
||||
* @param array $sample
|
||||
* @param int $feature
|
||||
* @param string $label
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
private function sampleProbability($sample, $feature, $label)
|
||||
@ -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);
|
||||
|
@ -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);
|
||||
@ -105,19 +112,20 @@ class FuzzyCMeans implements Clusterer
|
||||
$dim = $this->space->getDimension();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,4 +1,6 @@
|
||||
<?php declare(strict_types=1);
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Phpml\DimensionReduction;
|
||||
|
||||
@ -37,7 +39,7 @@ abstract class EigenTransformerBase
|
||||
/**
|
||||
* Top eigenValues of the matrix
|
||||
*
|
||||
* @var type
|
||||
* @var array
|
||||
*/
|
||||
protected $eigValues = [];
|
||||
|
||||
|
@ -86,7 +86,7 @@ class KernelPCA extends PCA
|
||||
$matrix = $this->calculateKernelMatrix($this->data, $numRows);
|
||||
$matrix = $this->centerMatrix($matrix, $numRows);
|
||||
|
||||
$this->eigenDecomposition($matrix, $numRows);
|
||||
$this->eigenDecomposition($matrix);
|
||||
|
||||
$this->fit = true;
|
||||
|
||||
@ -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 {
|
||||
@ -128,6 +128,8 @@ class KernelPCA extends PCA
|
||||
*
|
||||
* @param array $matrix
|
||||
* @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)
|
||||
{
|
||||
|
@ -4,7 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace Phpml\DimensionReduction;
|
||||
|
||||
use Phpml\Math\Statistic\Mean;
|
||||
use Phpml\Math\Matrix;
|
||||
|
||||
class LDA extends EigenTransformerBase
|
||||
@ -30,7 +29,7 @@ class LDA extends EigenTransformerBase
|
||||
public $counts;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
* @var float[]
|
||||
*/
|
||||
public $overallMean;
|
||||
|
||||
@ -111,12 +110,12 @@ class LDA extends EigenTransformerBase
|
||||
* Calculates mean of each column for each class and returns
|
||||
* n by m matrix where n is number of labels and m is number of columns
|
||||
*
|
||||
* @param type $data
|
||||
* @param type $classes
|
||||
* @param array $data
|
||||
* @param array $classes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function calculateMeans($data, $classes) : array
|
||||
protected function calculateMeans(array $data, array $classes) : array
|
||||
{
|
||||
$means = [];
|
||||
$counts= [];
|
||||
@ -136,7 +135,8 @@ class LDA extends EigenTransformerBase
|
||||
if (!isset($counts[$label])) {
|
||||
$counts[$label] = 0;
|
||||
}
|
||||
$counts[$label]++;
|
||||
|
||||
++$counts[$label];
|
||||
}
|
||||
|
||||
foreach ($means as $index => $row) {
|
||||
@ -231,6 +231,8 @@ class LDA extends EigenTransformerBase
|
||||
* @param array $sample
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function transform(array $sample)
|
||||
{
|
||||
|
@ -6,7 +6,6 @@ namespace Phpml\DimensionReduction;
|
||||
|
||||
use Phpml\Math\Statistic\Covariance;
|
||||
use Phpml\Math\Statistic\Mean;
|
||||
use Phpml\Math\Matrix;
|
||||
|
||||
class PCA extends EigenTransformerBase
|
||||
{
|
||||
@ -86,7 +85,7 @@ class PCA extends EigenTransformerBase
|
||||
{
|
||||
// Calculate means for each dimension
|
||||
$this->means = [];
|
||||
for ($i=0; $i < $n; $i++) {
|
||||
for ($i = 0; $i < $n; ++$i) {
|
||||
$column = array_column($data, $i);
|
||||
$this->means[] = Mean::arithmetic($column);
|
||||
}
|
||||
@ -109,7 +108,7 @@ class PCA extends EigenTransformerBase
|
||||
|
||||
// Normalize data
|
||||
foreach ($data as $i => $row) {
|
||||
for ($k=0; $k < $n; $k++) {
|
||||
for ($k = 0; $k < $n; ++$k) {
|
||||
$data[$i][$k] -= $this->means[$k];
|
||||
}
|
||||
}
|
||||
@ -124,6 +123,8 @@ class PCA extends EigenTransformerBase
|
||||
* @param array $sample
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function transform(array $sample)
|
||||
{
|
||||
|
@ -6,7 +6,6 @@ namespace Phpml\Exception;
|
||||
|
||||
class DatasetException extends \Exception
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
*
|
||||
|
@ -6,7 +6,6 @@ namespace Phpml\Exception;
|
||||
|
||||
class FileException extends \Exception
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $filepath
|
||||
*
|
||||
|
@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -6,7 +6,6 @@ namespace Phpml\Exception;
|
||||
|
||||
class SerializeException extends \Exception
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $filepath
|
||||
*
|
||||
|
@ -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
|
||||
@ -181,6 +177,7 @@ trait OneVsRest
|
||||
* return a probability for a sample to belong to the given label.
|
||||
*
|
||||
* @param array $sample
|
||||
* @param string $label
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
|
@ -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 <b>subtraction</b> of a vector with a scalar
|
||||
*
|
||||
* @param array $m1
|
||||
* @param float $m2
|
||||
* @param array $m2
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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]);
|
||||
}
|
||||
|
@ -14,13 +14,13 @@ trait Predictable
|
||||
public function predict(array $samples)
|
||||
{
|
||||
if (!is_array($samples[0])) {
|
||||
$predicted = $this->predictSample($samples);
|
||||
} else {
|
||||
return $this->predictSample($samples);
|
||||
}
|
||||
|
||||
$predicted = [];
|
||||
foreach ($samples as $index => $sample) {
|
||||
$predicted[$index] = $this->predictSample($sample);
|
||||
}
|
||||
}
|
||||
|
||||
return $predicted;
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ namespace Phpml;
|
||||
|
||||
interface IncrementalEstimator
|
||||
{
|
||||
|
||||
/**
|
||||
* @param array $samples
|
||||
* @param array $targets
|
||||
|
@ -23,8 +23,8 @@ class RBF implements Kernel
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float $a
|
||||
* @param float $b
|
||||
* @param array $a
|
||||
* @param array $b
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
|
@ -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.
|
||||
@ -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,20 +157,24 @@ 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) {
|
||||
$g += $this->V[$k][$j] * $this->d[$k];
|
||||
$this->e[$k] += $this->V[$k][$j] * $f;
|
||||
}
|
||||
$this->e[$j] = $g;
|
||||
}
|
||||
|
||||
$f = 0.0;
|
||||
if ($h === 0 || $h < 1e-32) {
|
||||
$h = 1e-32;
|
||||
}
|
||||
|
||||
for ($j = 0; $j < $i; ++$j) {
|
||||
$this->e[$j] /= $h;
|
||||
$f += $this->e[$j] * $this->d[$j];
|
||||
}
|
||||
|
||||
$hh = $f / (2 * $h);
|
||||
for ($j = 0; $j < $i; ++$j) {
|
||||
$this->e[$j] -= $hh * $this->d[$j];
|
||||
@ -378,9 +416,13 @@ class EigenvalueDecomposition
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@ -662,6 +703,7 @@ class EigenvalueDecomposition
|
||||
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;
|
||||
@ -783,44 +825,11 @@ class EigenvalueDecomposition
|
||||
}
|
||||
} // end hqr2
|
||||
|
||||
|
||||
/**
|
||||
* Constructor: Check for symmetry, then construct the eigenvalue decomposition
|
||||
*
|
||||
* @param array $Arg
|
||||
*/
|
||||
public function __construct(array $Arg)
|
||||
{
|
||||
$this->A = $Arg;
|
||||
$this->n = count($Arg[0]);
|
||||
|
||||
$issymmetric = true;
|
||||
for ($j = 0; ($j < $this->n) & $issymmetric; ++$j) {
|
||||
for ($i = 0; ($i < $this->n) & $issymmetric; ++$i) {
|
||||
$issymmetric = ($this->A[$i][$j] == $this->A[$j][$i]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($issymmetric) {
|
||||
$this->V = $this->A;
|
||||
// Tridiagonalize.
|
||||
$this->tred2();
|
||||
// Diagonalize.
|
||||
$this->tql2();
|
||||
} else {
|
||||
$this->H = $this->A;
|
||||
$this->ort = [];
|
||||
// Reduce to Hessenberg form.
|
||||
$this->orthes();
|
||||
// Reduce Hessenberg to real Schur form.
|
||||
$this->hqr2();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the eigenvector matrix
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getEigenvectors()
|
||||
@ -831,20 +840,21 @@ class EigenvalueDecomposition
|
||||
$vectors = new Matrix($vectors);
|
||||
$vectors = array_map(function ($vect) {
|
||||
$sum = 0;
|
||||
for ($i=0; $i<count($vect); $i++) {
|
||||
for ($i = 0; $i < count($vect); ++$i) {
|
||||
$sum += $vect[$i] ** 2;
|
||||
}
|
||||
|
||||
$sum = sqrt($sum);
|
||||
for ($i=0; $i<count($vect); $i++) {
|
||||
for ($i = 0; $i < count($vect); ++$i) {
|
||||
$vect[$i] /= $sum;
|
||||
}
|
||||
|
||||
return $vect;
|
||||
}, $vectors->transpose()->toArray());
|
||||
|
||||
return $vectors;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the real parts of the eigenvalues<br>
|
||||
* d = real(diag(D));
|
||||
@ -856,7 +866,6 @@ class EigenvalueDecomposition
|
||||
return $this->d;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the imaginary parts of the eigenvalues <br>
|
||||
* 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
|
||||
|
@ -1,4 +1,6 @@
|
||||
<?php declare(strict_types=1);
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @package JAMA
|
||||
*
|
||||
@ -62,10 +64,11 @@ class LUDecomposition
|
||||
|
||||
|
||||
/**
|
||||
* LU Decomposition constructor.
|
||||
* Constructs Structure to access L, U and piv.
|
||||
*
|
||||
* @param $A Rectangular matrix
|
||||
* @return Structure to access L, U and piv.
|
||||
* @param Matrix $A Rectangular matrix
|
||||
*
|
||||
* @throws MatrixException
|
||||
*/
|
||||
public function __construct(Matrix $A)
|
||||
{
|
||||
@ -81,7 +84,7 @@ class LUDecomposition
|
||||
$this->piv[$i] = $i;
|
||||
}
|
||||
$this->pivsign = 1;
|
||||
$LUrowi = $LUcolj = [];
|
||||
$LUcolj = [];
|
||||
|
||||
// Outer loop.
|
||||
for ($j = 0; $j < $this->n; ++$j) {
|
||||
@ -131,7 +134,7 @@ class LUDecomposition
|
||||
/**
|
||||
* Get lower triangular factor.
|
||||
*
|
||||
* @return array Lower triangular factor
|
||||
* @return Matrix Lower triangular factor
|
||||
*/
|
||||
public function getL()
|
||||
{
|
||||
@ -154,7 +157,7 @@ class LUDecomposition
|
||||
/**
|
||||
* Get upper triangular factor.
|
||||
*
|
||||
* @return array Upper triangular factor
|
||||
* @return Matrix Upper triangular factor
|
||||
*/
|
||||
public function getU()
|
||||
{
|
||||
@ -206,6 +209,7 @@ class LUDecomposition
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} // function isNonsingular()
|
||||
|
||||
@ -213,19 +217,22 @@ class LUDecomposition
|
||||
/**
|
||||
* Count determinants
|
||||
*
|
||||
* @return array d matrix deterninat
|
||||
* @return float|int d matrix determinant
|
||||
*
|
||||
* @throws MatrixException
|
||||
*/
|
||||
public function det()
|
||||
{
|
||||
if ($this->m == $this->n) {
|
||||
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;
|
||||
} else {
|
||||
throw MatrixException::notSquareMatrix();
|
||||
}
|
||||
} // function det()
|
||||
|
||||
|
||||
@ -274,7 +281,8 @@ class LUDecomposition
|
||||
} // function solve()
|
||||
|
||||
/**
|
||||
* @param Matrix $matrix
|
||||
* @param array $matrix
|
||||
* @param array $RL
|
||||
* @param int $j0
|
||||
* @param int $jF
|
||||
*
|
||||
|
@ -139,6 +139,7 @@ class Matrix
|
||||
}
|
||||
|
||||
$lu = new LUDecomposition($this);
|
||||
|
||||
return $this->determinant = $lu->det();
|
||||
}
|
||||
|
||||
@ -232,6 +233,8 @@ class Matrix
|
||||
* Element-wise addition of the matrix with another one
|
||||
*
|
||||
* @param Matrix $other
|
||||
*
|
||||
* @return Matrix
|
||||
*/
|
||||
public function add(Matrix $other)
|
||||
{
|
||||
@ -242,6 +245,8 @@ class Matrix
|
||||
* Element-wise subtracting of another matrix from this one
|
||||
*
|
||||
* @param Matrix $other
|
||||
*
|
||||
* @return Matrix
|
||||
*/
|
||||
public function subtract(Matrix $other)
|
||||
{
|
||||
@ -252,7 +257,9 @@ class Matrix
|
||||
* Element-wise addition or substraction depending on the given sign parameter
|
||||
*
|
||||
* @param Matrix $other
|
||||
* @param type $sign
|
||||
* @param int $sign
|
||||
*
|
||||
* @return Matrix
|
||||
*/
|
||||
protected function _add(Matrix $other, $sign = 1)
|
||||
{
|
||||
@ -260,13 +267,13 @@ class Matrix
|
||||
$a2 = $other->toArray();
|
||||
|
||||
$newMatrix = [];
|
||||
for ($i=0; $i < $this->rows; $i++) {
|
||||
for ($k=0; $k < $this->columns; $k++) {
|
||||
for ($i = 0; $i < $this->rows; ++$i) {
|
||||
for ($k = 0; $k < $this->columns; ++$k) {
|
||||
$newMatrix[$i][$k] = $a1[$i][$k] + $sign * $a2[$i][$k];
|
||||
}
|
||||
}
|
||||
|
||||
return new Matrix($newMatrix, false);
|
||||
return new self($newMatrix, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -295,7 +302,7 @@ class Matrix
|
||||
protected function getIdentity()
|
||||
{
|
||||
$array = array_fill(0, $this->rows, array_fill(0, $this->columns, 0));
|
||||
for ($i=0; $i < $this->rows; $i++) {
|
||||
for ($i = 0; $i < $this->rows; ++$i) {
|
||||
$array[$i][$i] = 1;
|
||||
}
|
||||
|
||||
@ -345,7 +352,7 @@ class Matrix
|
||||
*/
|
||||
public static function transposeArray(array $array)
|
||||
{
|
||||
return (new Matrix($array, false))->transpose()->toArray();
|
||||
return (new self($array, false))->transpose()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -359,8 +366,8 @@ class Matrix
|
||||
*/
|
||||
public static function dot(array $array1, array $array2)
|
||||
{
|
||||
$m1 = new Matrix($array1, false);
|
||||
$m2 = new Matrix($array2, false);
|
||||
$m1 = new self($array1, false);
|
||||
$m2 = new self($array2, false);
|
||||
|
||||
return $m1->multiply($m2->transpose())->toArray()[0];
|
||||
}
|
||||
|
@ -59,12 +59,16 @@ class Covariance
|
||||
* @param array $data
|
||||
* @param int $i
|
||||
* @param int $k
|
||||
* @param type $sample
|
||||
* @param int $n
|
||||
* @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();
|
||||
@ -124,6 +128,7 @@ class Covariance
|
||||
* Returns the covariance matrix of n-dimensional 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]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ class Gaussian
|
||||
*
|
||||
* @param float $value
|
||||
*
|
||||
* @return type
|
||||
* @return float|int
|
||||
*/
|
||||
public function pdf(float $value)
|
||||
{
|
||||
|
@ -68,7 +68,7 @@ class Mean
|
||||
*/
|
||||
private static function checkArrayLength(array $array)
|
||||
{
|
||||
if (0 == count($array)) {
|
||||
if (empty($array)) {
|
||||
throw InvalidArgumentException::arrayCantBeEmpty();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ class ModelManager
|
||||
/**
|
||||
* @param Estimator $estimator
|
||||
* @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
|
||||
*/
|
||||
|
@ -44,10 +44,12 @@ class Backpropagation implements Training
|
||||
*/
|
||||
public function train(array $samples, array $targets, float $desiredError = 0.001, int $maxIterations = 10000)
|
||||
{
|
||||
$samplesCount = count($samples);
|
||||
|
||||
for ($i = 0; $i < $maxIterations; ++$i) {
|
||||
$resultsWithinError = $this->trainSamples($samples, $targets, $desiredError);
|
||||
|
||||
if ($resultsWithinError == count($samples)) {
|
||||
if ($resultsWithinError === $samplesCount) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ class Normalizer implements Preprocessor
|
||||
$this->fit($samples);
|
||||
|
||||
foreach ($samples as &$sample) {
|
||||
$this->$method($sample);
|
||||
$this->{$method}($sample);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,6 +130,8 @@ class SupportVectorMachine
|
||||
|
||||
/**
|
||||
* @param string $binPath
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setBinPath(string $binPath)
|
||||
{
|
||||
@ -140,6 +142,8 @@ class SupportVectorMachine
|
||||
|
||||
/**
|
||||
* @param string $varPath
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setVarPath(string $varPath)
|
||||
{
|
||||
@ -230,8 +234,8 @@ class SupportVectorMachine
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $trainingSetFileName
|
||||
* @param $modelFileName
|
||||
* @param string $trainingSetFileName
|
||||
* @param string $modelFileName
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user