MDL-65769 lib: update PHP-ML to 0.8.0

This commit is contained in:
Simey Lameze
2019-07-12 06:28:31 +08:00
parent f7e108438f
commit e6c25fb057
126 changed files with 3636 additions and 3750 deletions
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Phpml\Helper;
use Phpml\Classification\Classifier;
trait OneVsRest
{
/**
@@ -25,39 +27,37 @@ trait OneVsRest
/**
* Train a binary classifier in the OvR style
*
* @param array $samples
* @param array $targets
*/
public function train(array $samples, array $targets)
public function train(array $samples, array $targets): void
{
// Clears previous stuff.
$this->reset();
$this->trainBylabel($samples, $targets);
$this->trainByLabel($samples, $targets);
}
/**
* @param array $samples
* @param array $targets
* @param array $allLabels All training set labels
*
* @return void
* Resets the classifier and the vars internally used by OneVsRest to create multiple classifiers.
*/
protected function trainByLabel(array $samples, array $targets, array $allLabels = [])
public function reset(): void
{
$this->classifiers = [];
$this->allLabels = [];
$this->costValues = [];
$this->resetBinary();
}
protected function trainByLabel(array $samples, array $targets, array $allLabels = []): void
{
// Overwrites the current value if it exist. $allLabels must be provided for each partialTrain run.
if (!empty($allLabels)) {
$this->allLabels = $allLabels;
} else {
$this->allLabels = array_keys(array_count_values($targets));
}
$this->allLabels = count($allLabels) === 0 ? array_keys(array_count_values($targets)) : $allLabels;
sort($this->allLabels, SORT_STRING);
// If there are only two targets, then there is no need to perform OvR
if (count($this->allLabels) == 2) {
if (count($this->allLabels) === 2) {
// Init classifier if required.
if (empty($this->classifiers)) {
if (count($this->classifiers) === 0) {
$this->classifiers[0] = $this->getClassifierCopy();
}
@@ -67,11 +67,11 @@ trait OneVsRest
foreach ($this->allLabels as $label) {
// Init classifier if required.
if (empty($this->classifiers[$label])) {
if (!isset($this->classifiers[$label])) {
$this->classifiers[$label] = $this->getClassifierCopy();
}
list($binarizedTargets, $classifierLabels) = $this->binarizeTargets($targets, $label);
[$binarizedTargets, $classifierLabels] = $this->binarizeTargets($targets, $label);
$this->classifiers[$label]->trainBinary($samples, $binarizedTargets, $classifierLabels);
}
}
@@ -85,64 +85,26 @@ trait OneVsRest
}
}
/**
* Resets the classifier and the vars internally used by OneVsRest to create multiple classifiers.
*/
public function reset()
{
$this->classifiers = [];
$this->allLabels = [];
$this->costValues = [];
$this->resetBinary();
}
/**
* Returns an instance of the current class after cleaning up OneVsRest stuff.
*
* @return \Phpml\Estimator
*/
protected function getClassifierCopy()
protected function getClassifierCopy(): Classifier
{
// Clone the current classifier, so that
// we don't mess up its variables while training
// multiple instances of this classifier
$classifier = clone $this;
$classifier->reset();
return $classifier;
}
/**
* Groups all targets into two groups: Targets equal to
* the given label and the others
*
* $targets is not passed by reference nor contains objects so this method
* changes will not affect the caller $targets array.
*
* @param array $targets
* @param mixed $label
* @return array Binarized targets and target's labels
*/
private function binarizeTargets($targets, $label)
{
$notLabel = "not_$label";
foreach ($targets as $key => $target) {
$targets[$key] = $target == $label ? $label : $notLabel;
}
$labels = [$label, $notLabel];
return [$targets, $labels];
}
/**
* @param array $sample
*
* @return mixed
*/
protected function predictSample(array $sample)
{
if (count($this->allLabels) == 2) {
if (count($this->allLabels) === 2) {
return $this->classifiers[0]->predictSampleBinary($sample);
}
@@ -153,32 +115,24 @@ trait OneVsRest
}
arsort($probs, SORT_NUMERIC);
return key($probs);
}
/**
* Each classifier should implement this method instead of train(samples, targets)
*
* @param array $samples
* @param array $targets
* @param array $labels
*/
abstract protected function trainBinary(array $samples, array $targets, array $labels);
/**
* To be overwritten by OneVsRest classifiers.
*
* @return void
*/
abstract protected function resetBinary();
abstract protected function resetBinary(): void;
/**
* Each classifier that make use of OvR approach should be able to
* return a probability for a sample to belong to the given label.
*
* @param array $sample
* @param string $label
*
* @return mixed
*/
abstract protected function predictProbability(array $sample, string $label);
@@ -186,9 +140,30 @@ trait OneVsRest
/**
* Each classifier should implement this method instead of predictSample()
*
* @param array $sample
*
* @return mixed
*/
abstract protected function predictSampleBinary(array $sample);
/**
* Groups all targets into two groups: Targets equal to
* the given label and the others
*
* $targets is not passed by reference nor contains objects so this method
* changes will not affect the caller $targets array.
*
* @param mixed $label
*
* @return array Binarized targets and target's labels
*/
private function binarizeTargets(array $targets, $label): array
{
$notLabel = "not_${label}";
foreach ($targets as $key => $target) {
$targets[$key] = $target == $label ? $label : $notLabel;
}
$labels = [$label, $notLabel];
return [$targets, $labels];
}
}
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Phpml\Helper\Optimizer;
use Closure;
/**
* Conjugate Gradient method to solve a non-linear f(x) with respect to unknown x
* See https://en.wikipedia.org/wiki/Nonlinear_conjugate_gradient_method)
@@ -17,14 +19,7 @@ namespace Phpml\Helper\Optimizer;
*/
class ConjugateGradient extends GD
{
/**
* @param array $samples
* @param array $targets
* @param \Closure $gradientCb
*
* @return array
*/
public function runOptimization(array $samples, array $targets, \Closure $gradientCb)
public function runOptimization(array $samples, array $targets, Closure $gradientCb): array
{
$this->samples = $samples;
$this->targets = $targets;
@@ -32,11 +27,11 @@ class ConjugateGradient extends GD
$this->sampleCount = count($samples);
$this->costValues = [];
$d = mp::muls($this->gradient($this->theta), -1);
$d = MP::muls($this->gradient($this->theta), -1);
for ($i = 0; $i < $this->maxIterations; ++$i) {
// Obtain α that minimizes f(θ + α.d)
$alpha = $this->getAlpha(array_sum($d));
$alpha = $this->getAlpha($d);
// θ(k+1) = θ(k) + α.d
$thetaNew = $this->getNewTheta($alpha, $d);
@@ -65,30 +60,38 @@ class ConjugateGradient extends GD
/**
* Executes the callback function for the problem and returns
* sum of the gradient for all samples & targets.
*
* @param array $theta
*
* @return array
*/
protected function gradient(array $theta)
protected function gradient(array $theta): array
{
list(, $gradient) = parent::gradient($theta);
[, $updates, $penalty] = parent::gradient($theta);
// Calculate gradient for each dimension
$gradient = [];
for ($i = 0; $i <= $this->dimensions; ++$i) {
if ($i === 0) {
$gradient[$i] = array_sum($updates);
} else {
$col = array_column($this->samples, $i - 1);
$error = 0;
foreach ($col as $index => $val) {
$error += $val * $updates[$index];
}
$gradient[$i] = $error + $penalty * $theta[$i];
}
}
return $gradient;
}
/**
* Returns the value of f(x) for given solution
*
* @param array $theta
*
* @return float
*/
protected function cost(array $theta)
protected function cost(array $theta): float
{
list($cost) = parent::gradient($theta);
[$cost] = parent::gradient($theta);
return array_sum($cost) / $this->sampleCount;
return array_sum($cost) / (int) $this->sampleCount;
}
/**
@@ -104,19 +107,15 @@ class ConjugateGradient extends GD
* b) Probe a larger alpha (0.01) and calculate cost function
* b-1) If cost function decreases, continue enlarging alpha
* b-2) If cost function increases, take the midpoint and try again
*
* @param float $d
*
* @return float
*/
protected function getAlpha(float $d)
protected function getAlpha(array $d): float
{
$small = 0.0001 * $d;
$large = 0.01 * $d;
$small = MP::muls($d, 0.0001);
$large = MP::muls($d, 0.01);
// Obtain θ + α.d for two initial values, x0 and x1
$x0 = mp::adds($this->theta, $small);
$x1 = mp::adds($this->theta, $large);
$x0 = MP::add($this->theta, $small);
$x1 = MP::add($this->theta, $large);
$epsilon = 0.0001;
$iteration = 0;
@@ -132,20 +131,28 @@ class ConjugateGradient extends GD
if ($fx1 < $fx0) {
$x0 = $x1;
$x1 = mp::adds($x1, 0.01); // Enlarge second
$x1 = MP::adds($x1, 0.01); // Enlarge second
} else {
$x1 = mp::divs(mp::add($x1, $x0), 2.0);
$x1 = MP::divs(MP::add($x1, $x0), 2.0);
} // Get to the midpoint
$error = $fx1 / $this->dimensions;
} while ($error <= $epsilon || $iteration++ < 10);
// Return α = θ / d
if ($d == 0) {
return $x1[0] - $this->theta[0];
// Return α = θ / d
// For accuracy, choose a dimension which maximize |d[i]|
$imax = 0;
for ($i = 1; $i <= $this->dimensions; ++$i) {
if (abs($d[$i]) > abs($d[$imax])) {
$imax = $i;
}
}
return ($x1[0] - $this->theta[0]) / $d;
if ($d[$imax] == 0) {
return $x1[$imax] - $this->theta[$imax];
}
return ($x1[$imax] - $this->theta[$imax]) / $d[$imax];
}
/**
@@ -153,30 +160,10 @@ class ConjugateGradient extends GD
* gradient direction.
*
* θ(k+1) = θ(k) + α.d
*
* @param float $alpha
* @param array $d
*
* @return array
*/
protected function getNewTheta(float $alpha, array $d)
protected function getNewTheta(float $alpha, array $d): array
{
$theta = $this->theta;
for ($i = 0; $i < $this->dimensions + 1; ++$i) {
if ($i === 0) {
$theta[$i] += $alpha * array_sum($d);
} else {
$sum = 0.0;
foreach ($this->samples as $si => $sample) {
$sum += $sample[$i - 1] * $d[$si] * $alpha;
}
$theta[$i] += $sum;
}
}
return $theta;
return MP::add($this->theta, MP::muls($d, $alpha));
}
/**
@@ -187,35 +174,31 @@ class ConjugateGradient extends GD
*
* See:
* R. Fletcher and C. M. Reeves, "Function minimization by conjugate gradients", Comput. J. 7 (1964), 149154.
*
* @param array $newTheta
*
* @return float
*/
protected function getBeta(array $newTheta)
protected function getBeta(array $newTheta): float
{
$dNew = array_sum($this->gradient($newTheta));
$dOld = array_sum($this->gradient($this->theta)) + 1e-100;
$gNew = $this->gradient($newTheta);
$gOld = $this->gradient($this->theta);
$dNew = 0;
$dOld = 1e-100;
for ($i = 0; $i <= $this->dimensions; ++$i) {
$dNew += $gNew[$i] ** 2;
$dOld += $gOld[$i] ** 2;
}
return $dNew ** 2 / $dOld ** 2;
return $dNew / $dOld;
}
/**
* Calculates the new conjugate direction
*
* d(k+1) =–∇f(x(k+1)) + β(k).d(k)
*
* @param array $theta
* @param float $beta
* @param array $d
*
* @return array
*/
protected function getNewDirection(array $theta, float $beta, array $d)
protected function getNewDirection(array $theta, float $beta, array $d): array
{
$grad = $this->gradient($theta);
return mp::add(mp::muls($grad, -1), mp::muls($d, $beta));
return MP::add(MP::muls($grad, -1), MP::muls($d, $beta));
}
}
@@ -223,17 +206,12 @@ class ConjugateGradient extends GD
* Handles element-wise vector operations between vector-vector
* and vector-scalar variables
*/
class mp
class MP
{
/**
* Element-wise <b>multiplication</b> of two vectors of the same size
*
* @param array $m1
* @param array $m2
*
* @return array
*/
public static function mul(array $m1, array $m2)
public static function mul(array $m1, array $m2): array
{
$res = [];
foreach ($m1 as $i => $val) {
@@ -245,13 +223,8 @@ class mp
/**
* Element-wise <b>division</b> of two vectors of the same size
*
* @param array $m1
* @param array $m2
*
* @return array
*/
public static function div(array $m1, array $m2)
public static function div(array $m1, array $m2): array
{
$res = [];
foreach ($m1 as $i => $val) {
@@ -263,14 +236,8 @@ class mp
/**
* Element-wise <b>addition</b> of two vectors of the same size
*
* @param array $m1
* @param array $m2
* @param int $mag
*
* @return array
*/
public static function add(array $m1, array $m2, int $mag = 1)
public static function add(array $m1, array $m2, int $mag = 1): array
{
$res = [];
foreach ($m1 as $i => $val) {
@@ -282,26 +249,16 @@ class mp
/**
* Element-wise <b>subtraction</b> of two vectors of the same size
*
* @param array $m1
* @param array $m2
*
* @return array
*/
public static function sub(array $m1, array $m2)
public static function sub(array $m1, array $m2): array
{
return self::add($m1, $m2, -1);
}
/**
* Element-wise <b>multiplication</b> of a vector with a scalar
*
* @param array $m1
* @param float $m2
*
* @return array
*/
public static function muls(array $m1, float $m2)
public static function muls(array $m1, float $m2): array
{
$res = [];
foreach ($m1 as $val) {
@@ -313,13 +270,8 @@ class mp
/**
* Element-wise <b>division</b> of a vector with a scalar
*
* @param array $m1
* @param float $m2
*
* @return array
*/
public static function divs(array $m1, float $m2)
public static function divs(array $m1, float $m2): array
{
$res = [];
foreach ($m1 as $val) {
@@ -331,14 +283,8 @@ class mp
/**
* Element-wise <b>addition</b> of a vector with a scalar
*
* @param array $m1
* @param float $m2
* @param int $mag
*
* @return array
*/
public static function adds(array $m1, float $m2, int $mag = 1)
public static function adds(array $m1, float $m2, int $mag = 1): array
{
$res = [];
foreach ($m1 as $val) {
@@ -350,13 +296,8 @@ class mp
/**
* Element-wise <b>subtraction</b> of a vector with a scalar
*
* @param array $m1
* @param array $m2
*
* @return array
*/
public static function subs(array $m1, array $m2)
public static function subs(array $m1, float $m2): array
{
return self::adds($m1, $m2, -1);
}
@@ -4,6 +4,9 @@ declare(strict_types=1);
namespace Phpml\Helper\Optimizer;
use Closure;
use Phpml\Exception\InvalidOperationException;
/**
* Batch version of Gradient Descent to optimize the weights
* of a classifier given samples, targets and the objective function to minimize
@@ -13,18 +16,11 @@ class GD extends StochasticGD
/**
* Number of samples given
*
* @var int
* @var int|null
*/
protected $sampleCount = null;
protected $sampleCount;
/**
* @param array $samples
* @param array $targets
* @param \Closure $gradientCb
*
* @return array
*/
public function runOptimization(array $samples, array $targets, \Closure $gradientCb)
public function runOptimization(array $samples, array $targets, Closure $gradientCb): array
{
$this->samples = $samples;
$this->targets = $targets;
@@ -38,11 +34,11 @@ class GD extends StochasticGD
$theta = $this->theta;
// Calculate update terms for each sample
list($errors, $updates, $totalPenalty) = $this->gradient($theta);
[$errors, $updates, $totalPenalty] = $this->gradient($theta);
$this->updateWeightsWithUpdates($updates, $totalPenalty);
$this->costValues[] = array_sum($errors)/$this->sampleCount;
$this->costValues[] = array_sum($errors) / $this->sampleCount;
if ($this->earlyStop($theta)) {
break;
@@ -57,22 +53,22 @@ class GD extends StochasticGD
/**
* Calculates gradient, cost function and penalty term for each sample
* then returns them as an array of values
*
* @param array $theta
*
* @return array
*/
protected function gradient(array $theta)
protected function gradient(array $theta): array
{
$costs = [];
$gradient= [];
$gradient = [];
$totalPenalty = 0;
if ($this->gradientCb === null) {
throw new InvalidOperationException('Gradient callback is not defined');
}
foreach ($this->samples as $index => $sample) {
$target = $this->targets[$index];
$result = ($this->gradientCb)($theta, $sample, $target);
list($cost, $grad, $penalty) = array_pad($result, 3, 0);
[$cost, $grad, $penalty] = array_pad($result, 3, 0);
$costs[] = $cost;
$gradient[] = $grad;
@@ -84,11 +80,7 @@ class GD extends StochasticGD
return [$costs, $gradient, $totalPenalty];
}
/**
* @param array $updates
* @param float $penalty
*/
protected function updateWeightsWithUpdates(array $updates, float $penalty)
protected function updateWeightsWithUpdates(array $updates, float $penalty): void
{
// Updates all weights at once
for ($i = 0; $i <= $this->dimensions; ++$i) {
@@ -110,10 +102,8 @@ class GD extends StochasticGD
/**
* Clears the optimizer internal vars after the optimization process.
*
* @return void
*/
protected function clear()
protected function clear(): void
{
$this->sampleCount = null;
parent::clear();
@@ -4,6 +4,9 @@ declare(strict_types=1);
namespace Phpml\Helper\Optimizer;
use Closure;
use Phpml\Exception\InvalidArgumentException;
abstract class Optimizer
{
/**
@@ -11,7 +14,7 @@ abstract class Optimizer
*
* @var array
*/
protected $theta;
protected $theta = [];
/**
* Number of dimensions
@@ -22,8 +25,6 @@ abstract class Optimizer
/**
* Inits a new instance of Optimizer for the given number of dimensions
*
* @param int $dimensions
*/
public function __construct(int $dimensions)
{
@@ -32,23 +33,14 @@ abstract class Optimizer
// Inits the weights randomly
$this->theta = [];
for ($i = 0; $i < $this->dimensions; ++$i) {
$this->theta[] = rand() / (float) getrandmax();
$this->theta[] = (random_int(0, PHP_INT_MAX) / PHP_INT_MAX) + 0.1;
}
}
/**
* Sets the weights manually
*
* @param array $theta
*
* @return $this
*
* @throws \Exception
*/
public function setInitialTheta(array $theta)
public function setTheta(array $theta): self
{
if (count($theta) != $this->dimensions) {
throw new \Exception("Number of values in the weights array should be $this->dimensions");
if (count($theta) !== $this->dimensions) {
throw new InvalidArgumentException(sprintf('Number of values in the weights array should be %s', $this->dimensions));
}
$this->theta = $theta;
@@ -59,10 +51,6 @@ abstract class Optimizer
/**
* Executes the optimization with the given samples & targets
* and returns the weights
*
* @param array $samples
* @param array $targets
* @param \Closure $gradientCb
*/
abstract protected function runOptimization(array $samples, array $targets, \Closure $gradientCb);
abstract public function runOptimization(array $samples, array $targets, Closure $gradientCb): array;
}
@@ -4,6 +4,10 @@ declare(strict_types=1);
namespace Phpml\Helper\Optimizer;
use Closure;
use Phpml\Exception\InvalidArgumentException;
use Phpml\Exception\InvalidOperationException;
/**
* Stochastic Gradient Descent optimization method
* to find a solution for the equation A.ϴ = y where
@@ -29,9 +33,9 @@ class StochasticGD extends Optimizer
* Callback function to get the gradient and cost value
* for a specific set of theta (ϴ) and a pair of sample & target
*
* @var \Closure
* @var \Closure|null
*/
protected $gradientCb = null;
protected $gradientCb;
/**
* Maximum number of iterations used to train the model
@@ -66,18 +70,17 @@ class StochasticGD extends Optimizer
* @var bool
*/
protected $enableEarlyStop = true;
/**
* List of values obtained by evaluating the cost function at each iteration
* of the algorithm
*
* @var array
*/
protected $costValues= [];
protected $costValues = [];
/**
* Initializes the SGD optimizer for the given number of dimensions
*
* @param int $dimensions
*/
public function __construct(int $dimensions)
{
@@ -87,6 +90,17 @@ class StochasticGD extends Optimizer
$this->dimensions = $dimensions;
}
public function setTheta(array $theta): Optimizer
{
if (count($theta) !== $this->dimensions + 1) {
throw new InvalidArgumentException(sprintf('Number of values in the weights array should be %s', $this->dimensions + 1));
}
$this->theta = $theta;
return $this;
}
/**
* Sets minimum value for the change in the theta values
* between iterations to continue the iterations.<br>
@@ -94,8 +108,6 @@ class StochasticGD extends Optimizer
* If change in the theta is less than given value then the
* algorithm will stop training
*
* @param float $threshold
*
* @return $this
*/
public function setChangeThreshold(float $threshold = 1e-5)
@@ -109,8 +121,6 @@ class StochasticGD extends Optimizer
* Enable/Disable early stopping by checking at each iteration
* whether changes in theta or cost value are not large enough
*
* @param bool $enable
*
* @return $this
*/
public function setEarlyStop(bool $enable = true)
@@ -121,8 +131,6 @@ class StochasticGD extends Optimizer
}
/**
* @param float $learningRate
*
* @return $this
*/
public function setLearningRate(float $learningRate)
@@ -133,8 +141,6 @@ class StochasticGD extends Optimizer
}
/**
* @param int $maxIterations
*
* @return $this
*/
public function setMaxIterations(int $maxIterations)
@@ -150,14 +156,8 @@ class StochasticGD extends Optimizer
*
* The cost function to minimize and the gradient of the function are to be
* handled by the callback function provided as the third parameter of the method.
*
* @param array $samples
* @param array $targets
* @param \Closure $gradientCb
*
* @return array
*/
public function runOptimization(array $samples, array $targets, \Closure $gradientCb)
public function runOptimization(array $samples, array $targets, Closure $gradientCb): array
{
$this->samples = $samples;
$this->targets = $targets;
@@ -176,7 +176,7 @@ class StochasticGD extends Optimizer
// Save the best theta in the "pocket" so that
// any future set of theta worse than this will be disregarded
if ($bestTheta == null || $cost <= $bestScore) {
if ($bestTheta === null || $cost <= $bestScore) {
$bestTheta = $theta;
$bestScore = $cost;
}
@@ -194,23 +194,33 @@ class StochasticGD extends Optimizer
// Solution in the pocket is better than or equal to the last state
// so, we use this solution
return $this->theta = $bestTheta;
return $this->theta = (array) $bestTheta;
}
/**
* @return float
* Returns the list of cost values for each iteration executed in
* last run of the optimization
*/
protected function updateTheta()
public function getCostValues(): array
{
return $this->costValues;
}
protected function updateTheta(): float
{
$jValue = 0.0;
$theta = $this->theta;
if ($this->gradientCb === null) {
throw new InvalidOperationException('Gradient callback is not defined');
}
foreach ($this->samples as $index => $sample) {
$target = $this->targets[$index];
$result = ($this->gradientCb)($theta, $sample, $target);
list($error, $gradient, $penalty) = array_pad($result, 3, 0);
[$error, $gradient, $penalty] = array_pad($result, 3, 0);
// Update bias
$this->theta[0] -= $this->learningRate * $gradient;
@@ -231,19 +241,17 @@ class StochasticGD extends Optimizer
/**
* Checks if the optimization is not effective enough and can be stopped
* in case large enough changes in the solution do not happen
*
* @param array $oldTheta
*
* @return boolean
*/
protected function earlyStop($oldTheta)
protected function earlyStop(array $oldTheta): bool
{
// Check for early stop: No change larger than threshold (default 1e-5)
$diff = array_map(
function ($w1, $w2) {
return abs($w1 - $w2) > $this->threshold ? 1 : 0;
},
$oldTheta, $this->theta);
$oldTheta,
$this->theta
);
if (array_sum($diff) == 0) {
return true;
@@ -251,30 +259,17 @@ class StochasticGD extends Optimizer
// Check if the last two cost values are almost the same
$costs = array_slice($this->costValues, -2);
if (count($costs) == 2 && abs($costs[1] - $costs[0]) < $this->threshold) {
if (count($costs) === 2 && abs($costs[1] - $costs[0]) < $this->threshold) {
return true;
}
return false;
}
/**
* Returns the list of cost values for each iteration executed in
* last run of the optimization
*
* @return array
*/
public function getCostValues()
{
return $this->costValues;
}
/**
* Clears the optimizer internal vars after the optimization process.
*
* @return void
*/
protected function clear()
protected function clear(): void
{
$this->samples = [];
$this->targets = [];
@@ -7,8 +7,6 @@ namespace Phpml\Helper;
trait Predictable
{
/**
* @param array $samples
*
* @return mixed
*/
public function predict(array $samples)
@@ -26,8 +24,6 @@ trait Predictable
}
/**
* @param array $sample
*
* @return mixed
*/
abstract protected function predictSample(array $sample);
@@ -16,11 +16,7 @@ trait Trainable
*/
private $targets = [];
/**
* @param array $samples
* @param array $targets
*/
public function train(array $samples, array $targets)
public function train(array $samples, array $targets): void
{
$this->samples = array_merge($this->samples, $samples);
$this->targets = array_merge($this->targets, $targets);