MDL-58859 mlbackend_php: Upgrade php-ml to latest version
Part of MDL-57791 epic.
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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 = "<b>$col $value</b><br>Gini: ". number_format($this->giniIndex, 2);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Phpml\Classification;
|
||||
|
||||
use Phpml\Exception\InvalidArgumentException;
|
||||
use Phpml\NeuralNetwork\Network\MultilayerPerceptron;
|
||||
|
||||
class MLPClassifier extends MultilayerPerceptron implements Classifier
|
||||
{
|
||||
|
||||
/**
|
||||
* @param mixed $target
|
||||
* @return int
|
||||
*/
|
||||
public function getTargetClass($target): int
|
||||
{
|
||||
if (!in_array($target, $this->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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user