maxFeatures = $maxFeatures; $this->maxBins = $maxBins; } /** * Greedy algorithm to choose the best split point for a given dataset. * * @param Labeled $dataset * @return Split */ protected function split(Labeled $dataset) : Split { [$m, $n] = $dataset->shape(); $maxFeatures = $this->maxFeatures ?? (int) round(sqrt($n)); $bins = $this->maxBins ?? 1 + (int) round(log($m, 2)); $columns = array_fill(0, $dataset->numFeatures(), null); $columns = (array) array_rand($columns, min($maxFeatures, count($columns))); $bestColumn = $bestValue = $bestSubsets = null; $bestImpurity = INF; foreach ($columns as $column) { $type = $dataset->featureType($column); $values = $dataset->feature($column); $values = array_unique($values); if ($type->isContinuous()) { if (count($values) > $bins) { if (!isset($q)) { $q = linspace(0.0, 1.0, $bins + 1); $q = array_slice($q, 1, -1); } $values = Stats::quantiles($values, $q); } } else { if (count($values) === 2) { $values = array_slice($values, 0, 1); } } foreach ($values as $value) { $subsets = $dataset->splitByFeature($column, $value); $impurity = $this->splitImpurity($subsets); if ($impurity < $bestImpurity) { $bestColumn = $column; $bestValue = $value; $bestSubsets = $subsets; $bestImpurity = $impurity; } if ($impurity <= 0.0) { break 2; } } } if ($bestColumn === null or $bestValue === null or $bestSubsets === null) { throw new RuntimeException('Could not split dataset.'); } return new Split( $bestColumn, $bestValue, $bestSubsets, $bestImpurity, $m ); } }