aggregateClassificationResults($actualLabels, $predictedLabels); $this->computeMetrics(); $this->computeAverage($average); } public function getPrecision(): array { return $this->precision; } public function getRecall(): array { return $this->recall; } public function getF1score(): array { return $this->f1score; } public function getSupport(): array { return $this->support; } public function getAverage(): array { return $this->average; } private function aggregateClassificationResults(array $actualLabels, array $predictedLabels): void { $truePositive = $falsePositive = $falseNegative = $support = self::getLabelIndexedArray($actualLabels, $predictedLabels); foreach ($actualLabels as $index => $actual) { $predicted = $predictedLabels[$index]; ++$support[$actual]; if ($actual === $predicted) { ++$truePositive[$actual]; } else { ++$falsePositive[$predicted]; ++$falseNegative[$actual]; } } $this->truePositive = $truePositive; $this->falsePositive = $falsePositive; $this->falseNegative = $falseNegative; $this->support = $support; } private function computeMetrics(): void { foreach ($this->truePositive as $label => $tp) { $this->precision[$label] = $this->computePrecision($tp, $this->falsePositive[$label]); $this->recall[$label] = $this->computeRecall($tp, $this->falseNegative[$label]); $this->f1score[$label] = $this->computeF1Score((float) $this->precision[$label], (float) $this->recall[$label]); } } private function computeAverage(int $average): void { switch ($average) { case self::MICRO_AVERAGE: $this->computeMicroAverage(); return; case self::MACRO_AVERAGE: $this->computeMacroAverage(); return; case self::WEIGHTED_AVERAGE: $this->computeWeightedAverage(); return; } } private function computeMicroAverage(): void { $truePositive = (int) array_sum($this->truePositive); $falsePositive = (int) array_sum($this->falsePositive); $falseNegative = (int) array_sum($this->falseNegative); $precision = $this->computePrecision($truePositive, $falsePositive); $recall = $this->computeRecall($truePositive, $falseNegative); $f1score = $this->computeF1Score((float) $precision, (float) $recall); $this->average = compact('precision', 'recall', 'f1score'); } private function computeMacroAverage(): void { foreach (['precision', 'recall', 'f1score'] as $metric) { $values = $this->{$metric}; if (count($values) == 0) { $this->average[$metric] = 0.0; continue; } $this->average[$metric] = array_sum($values) / count($values); } } private function computeWeightedAverage(): void { foreach (['precision', 'recall', 'f1score'] as $metric) { $values = $this->{$metric}; if (count($values) == 0) { $this->average[$metric] = 0.0; continue; } $sum = 0; foreach ($values as $i => $value) { $sum += $value * $this->support[$i]; } $this->average[$metric] = $sum / array_sum($this->support); } } /** * @return float|string */ private function computePrecision(int $truePositive, int $falsePositive) { $divider = $truePositive + $falsePositive; if ($divider == 0) { return 0.0; } return $truePositive / $divider; } /** * @return float|string */ private function computeRecall(int $truePositive, int $falseNegative) { $divider = $truePositive + $falseNegative; if ($divider == 0) { return 0.0; } return $truePositive / $divider; } private function computeF1Score(float $precision, float $recall): float { $divider = $precision + $recall; if ($divider == 0) { return 0.0; } return 2.0 * (($precision * $recall) / $divider); } private static function getLabelIndexedArray(array $actualLabels, array $predictedLabels): array { $labels = array_values(array_unique(array_merge($actualLabels, $predictedLabels))); sort($labels); return (array) array_combine($labels, array_fill(0, count($labels), 0)); } }