Compute the rolling counts and conditional probabilities.

@param \Rubix\ML\Datasets\Dataset $dataset
@throws \InvalidArgumentException
17145
public function partial(Dataset $dataset) : void
    {
        if (!$dataset instanceof Labeled) {
            throw new InvalidArgumentException('This Estimator requires a'
                . ' Labeled training set.');
        }

        if (!$dataset->homogeneous() or $dataset->columnType(0) !== DataType::CATEGORICAL) {
            throw new InvalidArgumentException('This estimator only works'
                . ' with categorical features.');
        }

        if (empty($this->weights) or empty($this->counts) or empty($this->probs)) {
            $this->train($dataset);
            return;
        }

        foreach ($dataset->stratify() as $class => $stratum) {
            $classCounts = $this->counts[$class];
            $classProbs = $this->probs[$class];

            foreach ($stratum->columns() as $column => $values) {
                $columnCounts = $classCounts[$column];

                $counts = array_count_values($values);

                foreach ($counts as $category => $count) {
                    if (isset($columnCounts[$category])) {
                        $columnCounts[$category] += $count;
                    } else {
                        $columnCounts[$category] = $count;
                    }
                }

                $total = (array_sum($columnCounts)
                    + (count($columnCounts) * $this->alpha))
                    ?: EPSILON;

                $probs = [];

                foreach ($columnCounts as $category => $count) {
                    $probs[$category] = log(($count + $this->alpha) / $total);
                }

                $classCounts[$column] = $columnCounts;
                $classProbs[$column] = $probs;
            }

            $this->counts[$class] = $classCounts;
            $this->probs[$class] = $classProbs;

            $this->weights[$class] += $stratum->numRows();
        }

        if ($this->fitPriors) {
            $total = (array_sum($this->weights)
                + (count($this->weights) * $this->alpha))
                ?: EPSILON;

            foreach ($this->weights as $class => $weight) {
                $this->priors[$class] = log(($weight + $this->alpha) / $total);
            }
        }
    }
380019
public function train(Dataset $dataset) : void
    {
        if (!$dataset instanceof Labeled) {
            throw new InvalidArgumentException('This estimator requires a'
                . ' labeled training set.');
        }

        DatasetIsCompatibleWithEstimator::check($dataset, $this);

        $this->classes = $dataset->possibleOutcomes();

        $mapping = array_flip($this->classes);

        $labels = $dataset->labels();

        $data = [];

        foreach ($dataset as $i => $sample) {
            $data[] = array_merge([$mapping[$labels[$i]]], $sample);
        }

        $this->model = $this->svm->train($data);
    }
898529
public function train(Dataset $dataset) : void
    {
        if (!$dataset instanceof Labeled) {
            throw new InvalidArgumentException('This estimator requires a'
                . ' labeled training set.');
        }
        
        $k = (int) round($this->ratio * $dataset->numRows());

        $this->forest = [];

        Loop::run(function () use ($dataset, $k) {
            $pool = new DefaultPool($this->workers);

            $coroutines = [];

            for ($i = 0; $i < $this->estimators; $i++) {
                $estimator = clone $this->base;

                $subset = $dataset->randomSubsetWithReplacement($k);

                $task = new CallableTask(
                    [$this, '_train'],
                    [$estimator, $subset]
                );

                $coroutines[] = call(function () use ($pool, $task) {
                    return yield $pool->enqueue($task);
                });
            }

            $this->forest = yield all($coroutines);
            
            return yield $pool->shutdown();
        });

        $this->classes = $dataset->possibleOutcomes();
        $this->featureCount = $dataset->numColumns();
    }
867679
public function proba(Dataset $dataset) : array
    {
        $base = $this->base();

        if ($base instanceof Probabilistic) {
            $this->preprocess($dataset);
            
            return $base->proba($dataset);
        }

        throw new RuntimeException('Base estimator must'
            . ' implement the probabilistic interface.');
    }
1299570
protected function validateDataSet(DataSetInterface $dataSet): void
    {
        if (!$dataSet[MerchantDataSetInterface::MERCHANT_KEY]) {
            throw new InvalidDataException('"' . MerchantDataSetInterface::MERCHANT_KEY . '" is required.');
        }

        if (!$dataSet[MerchantDataSetInterface::NAME]) {
            throw new InvalidDataException('"' . MerchantDataSetInterface::NAME . '" is required.');
        }
    }
1355878
protected function score(Dataset $dataset) : array
    {
        $scores = array_fill(
            0,
            $dataset->numRows(),
            array_fill_keys($this->classes, 0.)
        );

        foreach ($this->ensemble as $i => $estimator) {
            $influence = $this->influences[$i];

            foreach ($estimator->predict($dataset) as $j => $prediction) {
                $scores[$j][$prediction] += $influence;
            }
        }

        return $scores;
    }
313392
public function train(Dataset $dataset) : void
    {
        DatasetIsCompatibleWithEstimator::check($dataset, $this);

        if ($this->logger) {
            $this->logger->info('Learner init ' . Params::stringify([
                'radius' => $this->radius,
                'kernel' => $this->kernel,
                'max_leaf_size' => $this->maxLeafSize,
                'epochs' => $this->epochs,
                'min_change' => $this->minChange,
                'seeder' => $this->seeder,
                'ratio' => $this->ratio,
            ]));
        }

        $n = $dataset->numRows();

        $tree = new BallTree($this->maxLeafSize, $this->kernel);

        if ($this->seeder and $n > self::MIN_SEEDS) {
            $k = (int) round($this->ratio * $n);

            $centroids = $this->seeder->seed($dataset, $k);
        } else {
            $centroids = $dataset->samples();
        }

        $tree->grow($dataset);

        $previous = $centroids;

        $this->steps = [];
 
        for ($epoch = 1; $epoch <= $this->epochs; $epoch++) {
            foreach ($centroids as $i => &$centroid) {
                [$samples, $labels, $distances] = $tree->range($centroid, $this->radius);

                $step = Matrix::quick($samples)
                    ->transpose()
                    ->mean()
                    ->asArray();

                $mu2 = Stats::mean($distances) ** 2;

                $weight = exp(-$mu2 / $this->delta);

                foreach ($centroid as $column => &$mean) {
                    $mean = ($weight * $step[$column]) / $weight;
                }

                foreach ($centroids as $j => $neighbor) {
                    if ($i === $j) {
                        continue 1;
                    }

                    $distance = $this->kernel->compute($centroid, $neighbor);

                    if ($distance < $this->radius) {
                        unset($centroids[$j]);
                    }
                }
            }

            $shift = $this->shift($centroids, $previous);

            $this->steps[] = $shift;

            if ($this->logger) {
                $this->logger->info("Epoch $epoch complete, shift=$shift");
            }

            if (is_nan($shift)) {
                break 1;
            }

            if ($shift < $this->minChange) {
                break 1;
            }

            $previous = $centroids;
        }

        $this->centroids = array_values($centroids);

        if ($this->logger) {
            $this->logger->info('Training complete');
        }
    }
923334
public function fit(Dataset $dataset) : void
    {
        $columns = $dataset->columnsByType(DataType::CATEGORICAL);

        $this->vocabulary = [];

        foreach ($columns as $column => $values) {
            $tfs = $dfs = [];

            foreach ($values as $text) {
                $tokens = $this->tokenizer->tokenize($text);

                $counts = array_count_values($tokens);

                foreach ($counts as $token => $count) {
                    if (isset($tfs[$token])) {
                        $tfs[$token] += $count;
                        $dfs[$token] += 1;
                    } else {
                        $tfs[$token] = $count;
                        $dfs[$token] = 1;
                    }
                }
            }

            if ($this->minDocumentFrequency > 1) {
                foreach ($dfs as $token => $frequency) {
                    if ($frequency < $this->minDocumentFrequency) {
                        unset($tfs[$token]);
                    }
                }
            }

            if (count($tfs) > $this->maxVocabulary) {
                arsort($tfs);
    
                $tfs = array_slice($tfs, 0, $this->maxVocabulary, true);
            }
    
            $this->vocabulary[$column] = array_combine(
                array_keys($tfs),
                range(0, count($tfs) - 1)
            ) ?: [];
        }
    }
977529
public function prepend(Dataset $dataset) : self
    {
        if (!$dataset instanceof Labeled) {
            throw new InvalidArgumentException('Can only merge with a labeled'
                . 'dataset.');
        }

        $samples = array_merge($dataset->samples(), $this->samples);
        $labels = array_merge($dataset->labels(), $this->labels);

        return self::quick($samples, $labels);
    }
600242
protected function initialize(Dataset $dataset) : array
    {
        $centroids = $this->seeder->seed($dataset, $this->k);

        $kernel = new Euclidean();

        $clusters = array_fill(0, $this->k, []);

        foreach ($dataset as $sample) {
            $bestDistance = INF;
            $bestCluster = -1;
    
            foreach ($centroids as $cluster => $centroid) {
                $distance = $kernel->compute($sample, $centroid);
    
                if ($distance < $bestDistance) {
                    $bestDistance = $distance;
                    $bestCluster = $cluster;
                }
            }

            $clusters[$bestCluster][] = $sample;
        }

        $means = $variances = [];

        foreach ($clusters as $cluster => $samples) {
            $mHat = $vHat = [];

            $columns = array_map(null, ...$samples);

            foreach ($columns as $values) {
                [$mean, $variance] = Stats::meanVar($values);

                $mHat[] = $mean;
                $vHat[] = $variance;
            }

            $means[$cluster] = $mHat;
            $variances[$cluster] = $vHat;
        }

        return [$means, $variances];
    }
1162748
protected function split(Labeled $dataset) : Decision
    {
        $bestImpurity = INF;
        $bestColumn = $bestValue = null;
        $bestGroups = [];

        $max = $dataset->numRows() - 1;

        shuffle($this->columns);

        foreach (array_slice($this->columns, 0, $this->maxFeatures) as $column) {
            $sample = $dataset->row(rand(0, $max));

            $value = $sample[$column];

            $groups = $dataset->partition($column, $value);

            $impurity = $this->splitImpurity($groups);

            if ($impurity < $bestImpurity) {
                $bestColumn = $column;
                $bestValue = $value;
                $bestGroups = $groups;
                $bestImpurity = $impurity;
            }

            if ($impurity < $this->tolerance) {
                break 1;
            }
        }

        return new Decision($bestColumn, $bestValue, $bestGroups, $bestImpurity);
    }
100490
public function partial(Dataset $dataset) : void
    {
        DatasetIsCompatibleWithEstimator::check($dataset, $this);

        $this->samples = array_merge($this->samples, $dataset->samples());

        $distances = [];

        foreach ($this->samples as $i => $sample) {
            $distances[] = $d = $this->localRegion($sample);

            $this->kdistances[$i] = end($d);
        }

        foreach ($distances as $i => $row) {
            $this->lrds[$i] = $this->localReachabilityDensity($row);
        }

        $lofs = $this->rank($dataset);

        $shift = Stats::percentile($lofs, 100. * $this->contamination);
        
        $this->offset = self::THRESHOLD + $shift;
    }
1023816
public function update(Dataset $dataset) : void
    {
        if ($this->minimums === null or $this->maximums === null) {
            $this->fit($dataset);
            
            return;
        }

        $columns = $dataset->columnsByType(DataType::CONTINUOUS);

        foreach ($columns as $column => $values) {
            $min = min($values);
            $max = max($values);

            $min = min($min, $this->minimums[$column]);
            $max = max($max, $this->maximums[$column]);

            $scale = ($this->max - $this->min)
                / (($max - $min) ?: EPSILON);

            $minHat = $this->min - $min * $scale;

            $this->minimums[$column] = $min;
            $this->maximums[$column] = $max;
            $this->scales[$column] = $scale;
            $this->mins[$column] = $minHat;
        }
    }
901036
public function fit(Dataset $dataset) : void
    {
        $columns = $dataset->columnsByType(DataType::CONTINUOUS);

        $this->selected = [];

        foreach ($columns as $column => $values) {
            if (Stats::variance($values) <= $this->threshold) {
                continue 1;
            }

            $this->selected[$column] = true;
        }
    }
1119999
public function partial(Dataset $dataset) : void
    {
        if (!$this->network) {
            $this->train($dataset);

            return;
        }

        if (!$dataset instanceof Labeled) {
            throw new InvalidArgumentException('This estimator requires a'
                . ' labeled training set.');
        }

        DatasetIsCompatibleWithEstimator::check($dataset, $this);

        if ($this->logger) {
            $this->logger->info('Learner init ' . Params::stringify([
                'batch_size' => $this->batchSize,
                'optimizer' => $this->optimizer,
                'alpha' => $this->alpha,
                'epochs' => $this->epochs,
                'min_change' => $this->minChange,
                'cost_fn' => $this->costFn,
            ]));
        }
        
        $n = $dataset->numRows();

        $randomize = $n > $this->batchSize ? true : false;

        $previous = INF;

        for ($epoch = 1; $epoch <= $this->epochs; $epoch++) {
            if ($randomize) {
                $dataset->randomize();
            }

            $batches = $dataset->batch($this->batchSize);

            $loss = 0.;

            foreach ($batches as $batch) {
                $loss += $this->network->roundtrip($batch);
            }

            $loss /= $n;

            $this->steps[] = $loss;

            if ($this->logger) {
                $this->logger->info("Epoch $epoch complete, loss=$loss");
            }

            if (is_nan($loss)) {
                break 1;
            }

            if (abs($previous - $loss) < $this->minChange) {
                break 1;
            }

            $previous = $loss;
        }

        if ($this->logger) {
            $this->logger->info('Training complete');
        }
    }
785311
public function fit(Dataset $dataset) : void
    {
        $columns = $dataset->columnsByType(DataType::CONTINUOUS);

        $this->means = $this->variances = $this->stddevs = [];

        foreach ($columns as $column => $values) {
            [$mean, $variance] = Stats::meanVar($values);

            $this->means[$column] = $mean;
            $this->variances[$column] = $variance;
            $this->stddevs[$column] = sqrt($variance ?: EPSILON);
        }

        $this->n = $dataset->numRows();
    }
736016
public function predict(Dataset $dataset) : array
    {
        if (empty($this->ensemble)) {
            throw new RuntimeException('Estimator has not been trained.');
        }

        DatasetIsCompatibleWithEstimator::check($dataset, $this);

        $predictions = $this->base->predict($dataset);

        foreach ($this->ensemble as $estimator) {
            foreach ($estimator->predict($dataset) as $j => $prediction) {
                $predictions[$j] += $this->rate * $prediction;
            }
        }

        return $predictions;
    }