reset(); $this->trainByLabel($samples, $targets); } /** * Resets the classifier and the vars internally used by OneVsRest to create multiple classifiers. */ 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. $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) { // Init classifier if required. if (count($this->classifiers) === 0) { $this->classifiers[0] = $this->getClassifierCopy(); } $this->classifiers[0]->trainBinary($samples, $targets, $this->allLabels); } else { // Train a separate classifier for each label and memorize them foreach ($this->allLabels as $label) { // Init classifier if required. if (!isset($this->classifiers[$label])) { $this->classifiers[$label] = $this->getClassifierCopy(); } [$binarizedTargets, $classifierLabels] = $this->binarizeTargets($targets, $label); $this->classifiers[$label]->trainBinary($samples, $binarizedTargets, $classifierLabels); } } // If the underlying classifier is capable of giving the cost values // during the training, then assign it to the relevant variable // Adding just the first classifier cost values to avoid complex average calculations. $classifierref = reset($this->classifiers); if (method_exists($classifierref, 'getCostValues')) { $this->costValues = $classifierref->getCostValues(); } } /** * Returns an instance of the current class after cleaning up OneVsRest stuff. */ 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; } /** * @return mixed */ protected function predictSample(array $sample) { if (count($this->allLabels) === 2) { return $this->classifiers[0]->predictSampleBinary($sample); } $probs = []; foreach ($this->classifiers as $label => $predictor) { $probs[$label] = $predictor->predictProbability($sample, $label); } arsort($probs, SORT_NUMERIC); return key($probs); } /** * Each classifier should implement this method instead of train(samples, targets) */ abstract protected function trainBinary(array $samples, array $targets, array $labels); /** * To be overwritten by OneVsRest classifiers. */ 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. * * @return mixed */ abstract protected function predictProbability(array $sample, string $label); /** * Each classifier should implement this method instead of predictSample() * * @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]; } }