← Back
Editing: KDNeighborsRegressor.php
<?php namespace Rubix\ML\Regressors; use Rubix\ML\Learner; use Rubix\ML\Estimator; use Rubix\ML\Persistable; use Rubix\ML\EstimatorType; use Rubix\ML\Helpers\Stats; use Rubix\ML\Helpers\Params; use Rubix\ML\Datasets\Dataset; use Rubix\ML\Graph\Trees\KDTree; use Rubix\ML\Graph\Trees\Spatial; use Rubix\ML\Traits\AutotrackRevisions; use Rubix\ML\Specifications\DatasetIsLabeled; use Rubix\ML\Specifications\DatasetIsNotEmpty; use Rubix\ML\Specifications\SpecificationChain; use Rubix\ML\Specifications\DatasetHasDimensionality; use Rubix\ML\Specifications\LabelsAreCompatibleWithLearner; use Rubix\ML\Specifications\SamplesAreCompatibleWithEstimator; use Rubix\ML\Exceptions\InvalidArgumentException; use Rubix\ML\Exceptions\RuntimeException; /** * K-d Neighbors Regressor * * A fast implementation of KNN Regressor using a spatially-aware binary tree for nearest * neighbors search. K-d Neighbors Regressor works by locating the neighborhood of a sample * via binary search and then does a brute force search only on the samples close to or * within the neighborhood of the unknown sample. The main advantage of K-d Neighbors over * brute force KNN is inference speed, however you no longer have the ability to partially * train. * * @category Machine Learning * @package Rubix/ML * @author Andrew DalPino */ class KDNeighborsRegressor implements Estimator, Learner, Persistable { use AutotrackRevisions; /** * The number of neighbors to consider when making a prediction. * * @var int */ protected int $k; /** * Should we consider the distances of our nearest neighbors when making predictions? * * @var bool */ protected bool $weighted; /** * The spatial tree used to run nearest neighbor searches. * * @var Spatial */ protected Spatial $tree; /** * The dimensionality of the training set. * * @var int|null */ protected ?int $featureCount = null; /** * @param int $k * @param bool $weighted * @param Spatial|null $tree * @throws InvalidArgumentException */ public function __construct(int $k = 5, bool $weighted = false, ?Spatial $tree = null) { if ($k < 1) { throw new InvalidArgumentException('At least 1 neighbor is required' . " to make a prediction, $k given."); } $this->k = $k; $this->weighted = $weighted; $this->tree = $tree ?? new KDTree(); } /** * Return the estimator type. * * @internal * * @return EstimatorType */ public function type() : EstimatorType { return EstimatorType::regressor(); } /** * Return the data types that the estimator is compatible with. * * @internal * * @return list<\Rubix\ML\DataType> */ public function compatibility() : array { return $this->tree->kernel()->compatibility(); } /** * Return the settings of the hyper-parameters in an associative array. * * @internal * * @return mixed[] */ public function params() : array { return [ 'k' => $this->k, 'weighted' => $this->weighted, 'tree' => $this->tree, ]; } /** * Has the learner been trained? * * @return bool */ public function trained() : bool { return !$this->tree->bare(); } /** * Return the base k-d tree instance. * * @return Spatial */ public function tree() : Spatial { return $this->tree; } /** * @param \Rubix\ML\Datasets\Labeled $dataset */ public function train(Dataset $dataset) : void { SpecificationChain::with([ new DatasetIsLabeled($dataset), new DatasetIsNotEmpty($dataset), new SamplesAreCompatibleWithEstimator($dataset, $this), new LabelsAreCompatibleWithLearner($dataset, $this), ])->check(); $this->featureCount = $dataset->numFeatures(); $this->tree->grow($dataset); } /** * Make a prediction based on the nearest neighbors. * * @param Dataset $dataset * @throws RuntimeException * @return list<int|float> */ public function predict(Dataset $dataset) : array { if ($this->tree->bare() or !$this->featureCount) { throw new RuntimeException('Estimator has not been trained.'); } DatasetHasDimensionality::with($dataset, $this->featureCount)->check(); return array_map([$this, 'predictSample'], $dataset->samples()); } /** * Predict a single sample and return the result. * * @internal * * @param list<string|int|float> $sample * @return int|float */ public function predictSample(array $sample) { [$samples, $labels, $distances] = $this->tree->nearest($sample, $this->k); if ($this->weighted) { $weights = []; foreach ($distances as $distance) { $weights[] = 1.0 / (1.0 + $distance); } return Stats::weightedMean($labels, $weights); } return Stats::mean($labels); } /** * Return the string representation of the object. * * @internal * * @return string */ public function __toString() : string { return 'K-d Neighbors Regressor (' . Params::stringify($this->params()) . ')'; } }
Save File
Cancel