Как считаются микро- и макроусреднения метрик
Что проверяют
Знаете ли вы, в каком порядке идут операции: сначала суммировать, потом делить, или наоборот. От этого зависит, что покажет метрика на несбалансированных классах. Следом часто спрашивают, чему равен micro-F1 в обычной многоклассовой задаче.
Как отвечать
Определения:
- микроусреднение: складываете TP, FP и FN по всем классам в общие суммы и по ним один раз считаете метрику. Каждый объект весит одинаково, поэтому доминируют крупные классы;
- макроусреднение: считаете метрику отдельно для каждого класса и берёте простое среднее. Каждый класс весит одинаково, редкий влияет на итог так же, как массовый;
- взвешенное: метрики классов усредняются с весами, равными числу объектов класса (support). Результат ближе к микро, но с ним не совпадает.
В scikit-learn это параметр average у precision_score, recall_score и f1_score: значения "micro", "macro", "weighted", для многометочной задачи ещё "samples", а None вернёт метрику по каждому классу.
Пример: классы на 900, 90 и 10 объектов, модель хорошо узнаёт первый и почти не узнаёт третий. Micro-F1 будет высоким, его определяет большой класс, а macro-F1 заметно просядет из-за провала на редком. Отсюда выбор: если важны все классы, включая редкие (диагнозы, типы обращений), смотрите macro; если важна доля верных ответов в целом, micro.
Деталь, которую любят спрашивать: в многоклассовой задаче с одной меткой на объект micro-precision, micro-recall и micro-F1 совпадают с accuracy. Поэтому classification_report показывает строку accuracy, а не micro avg.
Где спотыкаются
Путают определения местами, это встречается даже в учебных материалах. «Сначала по каждому классу, потом среднее» это макро, а не микро.
Считают macro-F1 как F1 от macro-precision и macro-recall. Это другая величина: в scikit-learn macro-F1 это среднее F1 по классам, и цифры расходятся.
Не замечают классы без единого предсказания. Precision для них не определён, scikit-learn по умолчанию подставляет 0 с предупреждением, и макро падает. Поведение задаётся параметром zero_division, об этом стоит сказать явно.