Метрики качества
Как честно измерить качество модели — матрица ошибок, accuracy, precision, recall, F1, ROC-AUC, MSE и MAE — и почему метрика почти никогда не совпадает с loss.
Зачем это нужно
Заголовок раздела «Зачем это нужно»Функция потерьфункция потерьEN loss function нужна оптимизатору, а метрика — людям, которые принимают решения. Это разные вещи. Модель учат минимизировать cross-entropy, а в отчёте пишут что-нибудь вроде «accuracy 87% на C-Eval», «pass@1 = 71%» или «recall@5 = 0.93». Все эти слова мы разберём в уроке.
В работе с LLM метрики повсюду. Почти любой бенчмаркбенчмаркEN benchmark (стандартный набор задач для сравнения моделей) считают через accuracy или pass@k. Классификаторы, которые чистят данные для претрейна, оценивают по precision и recall. RAGгенерация с дополнением извлечёнными данными (RAG)EN retrieval-augmented generation (RAG) — схема, где модель сначала ищет подходящие документы в базе, а потом отвечает по ним (ступень 8). Ту часть, что ищет, называют ретривером, и её оценивают по recall@k. Выберете не ту метрику — будете улучшать не то. Этот урок даёт словарь и интуицию, без которых не прочитать ни один техотчёт.
Математики здесь немного: доли, дроби и среднее. Всё считается на маленьких примерах, которые легко проверить на калькуляторе.
Матрица ошибок
Заголовок раздела «Матрица ошибок»Продолжим пример из прошлого урока. Классификатор отбирает математические страницы для претрейна. На проверку взяли 1000 страниц, из них 100 действительно математические. Модель отметила 120 страниц, и 80 из них оказались математикой. Разложим всё по четырём клеткам. Это матрица ошибокматрица ошибокEN confusion matrix:
| Модель: «математика» | Модель: «не математика» | |
|---|---|---|
| Правда: математика (100) | TP = 80 | FN = 20 |
| Правда: не математика (900) | FP = 40 | TN = 860 |
Как читать обозначения. Вторая буква — что сказала модель: P (positive, «да») или N (negative, «нет»). Первая — права ли она: T (true) или F (false). FP — ложная тревога: модель сказала «да», а там проза. FN — пропуск: модель сказала «нет», а там формулы.
Все метрики ниже — разные способы свернуть эти четыре числа в одно.
Accuracy и её ловушка
Заголовок раздела «Accuracy и её ловушка»Accuracyдоля правильных ответов (accuracy)EN accuracyПо-русски accuracy иногда тоже называют «точностью» — из-за этого её путают с precision. Безопаснее писать «accuracy». — доля правильных ответов:
Звучит отлично. Но классификатор «всегда отвечай НЕТ» получает , ни разу не найдя математику. На реальном вебе математических страниц гораздо меньше 10%. Чем их меньше, тем ближе бесполезная константа к 100%. Это дисбаланс классовдисбаланс классовEN class imbalance, и при нём accuracy почти ничего не говорит.
Accuracy хороша, когда классы сбалансированы и ошибки одинаково дороги. Пример — тесты с выбором ответа вроде MMLU и C-Eval: четыре варианта, случайное угадывание даёт 25%, и accuracy честно показывает, насколько модель лучше.
Precision и recall
Заголовок раздела «Precision и recall»Две метрики смотрят только на положительный класс — тот, ради которого всё затевалось.
- Precisionточность (precision)EN precisionTP / (TP + FP). У Чжоу Чжихуа термин буквально означает «доля точных среди найденного». (точность) — «из отобранного, сколько правда математика?» Это чистота корпуса: треть отобранных страниц — мусор.
- Recallполнота (recall)EN recallTP / (TP + FN). Recall — буквально «отозвать, вернуть»: та же метрика recall@k в поиске и RAG. (полнота) — «из всей математики, сколько нашли?» Это охват: пятая часть математики потерялась.
Китайские названия запоминаются легко: в учебниках precision буквально называют «долей точно найденного», а recall — «долей найденного из всего». В статьях recall чаще называют «долей отозванного». Слово «отозвать, вернуть» вы ещё встретите в поиске и RAG: так по-китайски называют этап, на котором отбирают кандидатов.
Что важнее — зависит от цены ошибки
Заголовок раздела «Что важнее — зависит от цены ошибки»| Задача | Что дороже | Главная метрика |
|---|---|---|
| Спам-фильтр почты | Потерять важное письмо (FP) | Precision |
| Поиск уязвимостей в коде | Пропустить уязвимость (FN) | Recall |
| Фильтр токсичных текстов для претрейна | Пропустить токсичный текст в корпус | Recall по токсичным |
| Первый этап RAG (ретривер) | Не найти нужный документ — LLM его не увидит | Recall@k |
| Второй этап RAG (реранкер) | Засорить контекст лишним | Precision в top-k |
Реранкер — второй, более тщательный фильтр: он перечитывает найденные ретривером документы и оставляет лучшие. Контекст — текст, который подают модели вместе с вопросом.
В поиске метрики считают по первым результатам выдачи. Recall@k — какая доля всех нужных документов попала в top-. Precision в top-k (precision@k) — какая доля из этих документов действительно нужна.
Порог: precision против recall
Заголовок раздела «Порог: precision против recall»Модель выдаёт не метку, а скор — вероятность. Метку получают сравнением с порогом: скор ≥ порога — «математика». Порог классификациипорог классификацииEN classification threshold — главный рычаг. Возьмём 10 страниц: 4 математические со скорами 0.95, 0.80, 0.60, 0.35 и 6 обычных со скорами 0.70, 0.45, 0.30, 0.20, 0.10, 0.05.
| Порог | TP | FP | FN | Precision | Recall | F1 |
|---|---|---|---|---|---|---|
| 0.30 | 4 | 3 | 0 | 0.571 | 1.00 | 0.727 |
| 0.50 | 3 | 1 | 1 | 0.750 | 0.75 | 0.750 |
| 0.75 | 2 | 0 | 2 | 1.000 | 0.50 | 0.667 |
Порог выше — модель осторожнее: precision растёт, recall падает. Порог ниже — наоборот. Порог не обучают вместе с весами. Его выбирают после обучения на валидационной выборке под нужный баланс. Например, «максимальный recall при precision не ниже 0.9».
F1: одно число на двоих
Заголовок раздела «F1: одно число на двоих»Сравнивать модели удобнее по одному числу. F1-мераF1-мераEN F1 scoreГармоническое среднее precision и recall. — гармоническое среднее precision и recall. Гармоническое среднее двух чисел — это «удвоенное произведение, делённое на сумму»:
Почему не обычное среднее? Возьмём модель, которая отметила 2 страницы из 100 математических и не ошиблась: , . Среднее арифметическое — 0.51, будто модель «наполовину хороша». F1 . Гармоническое среднее тянется к меньшему из чисел и не даёт одной метрике спрятать провал другой.
Если recall важнее precision, берут с :
Число («бета») говорит, во сколько раз recall важнее precision. При формула превращается в обычную . Для нашего фильтра при : — выше, чем , потому что recall у фильтра (0.8) лучше precision (0.667) и теперь весит больше. А : больше весит precision, и оценка ниже.
ROC-AUC интуитивно
Заголовок раздела «ROC-AUC интуитивно»Precision, recall и F1 зависят от порога. А как сравнить две модели «вообще», до выбора порога? Будем двигать порог от 1 до 0 и следить за двумя величинами:
- TPR (true positive rate) — это recall: ;
- FPR (false positive rate) — доля ложных тревог среди отрицательных: .
Когда порог выше всех скоров, модель ничего не отмечает: . При пороге 0 она отмечает всё: . Кривая между ними — ROC-кривая. Площадь под ней — ROC-AUCROC-AUC (площадь под ROC-кривой)EN ROC AUC (area under the ROC curve)Вероятность того, что случайный положительный пример получит скор выше случайного отрицательного..
Построим кривую для наших 10 страниц. Будем ставить порог по очереди на каждый скор, от большего к меньшему. Каждый раз ответ «да» (скор ≥ порога) получает ровно одна новая страница:
| Порог | Кто добавился | TPR (recall) | FPR |
|---|---|---|---|
| 0.95 | математика | 1/4 = 0.25 | 0 |
| 0.80 | математика | 2/4 = 0.50 | 0 |
| 0.70 | обычная | 0.50 | 1/6 ≈ 0.17 |
| 0.60 | математика | 3/4 = 0.75 | 0.17 |
| 0.45 | обычная | 0.75 | 2/6 ≈ 0.33 |
| 0.35 | математика | 4/4 = 1.00 | 0.33 |
| 0.30 | обычная | 1.00 | 3/6 = 0.50 |
| 0.20, 0.10, 0.05 | обычные | 1.00 | 0.67, 0.83, 1.00 |
Математическая страница поднимает кривую на 1/4 (их четыре), обычная сдвигает вправо на 1/6 (их шесть). Получается лесенка:
Площадь под лесенкой не нужно интегрировать. Это шесть столбцов шириной 1/6 — по одному на каждую обычную страницу. Высота столбца — доля математических страниц, которые обогнали эту обычную по скору: 2/4, 3/4 и четыре раза по 4/4. Площадь равна . В числителе стоит число пар «математическая × обычная», в которых математическая страница получила скор выше. Отсюда главная интерпретация AUC:
Проверим с другой стороны — пройдём по математическим страницам. Всего пар .
- 0.95 и 0.80 выше всех шести обычных: пар.
- 0.60 проигрывает только обычной странице со скором 0.70: 5 пар.
- 0.35 выше четырёх обычных (0.30, 0.20, 0.10, 0.05): 4 пары.
Итого . Ничьи (равные скоры) считаются за половину пары. Перебор всех пар на миллионе объектов слишком медленный; как посчитать AUC за через сортировку и средние ранги — классическая задача собеседования из урока 11.3.4.
Что из этого следует:
- AUC = 0.5 — случайное угадывание, 1.0 — идеальное ранжирование. AUC меньше 0.5 значит, что модель перепутала классы: переверните её ответы.
- AUC зависит только от порядка скоров. Логиты и вероятности после сигмоиды дают одинаковый AUC.
- AUC не зависит от порога и от доли классов. Это плюс для сравнения моделей и минус при сильном дисбалансе. Когда положительных доли процента, даже малый FPR — это тысячи ложных тревог. Тогда смотрят на PR-кривую (precision от recall) и площадь под ней — average precision (AP). AP — это средняя precision в моменты, когда порог «захватывает» очередной положительный пример. У наших 10 страниц это .
flowchart TD
Q["Какую метрику выбрать?"] --> R{"Регрессия или<br/>классификация?"}
R -->|"регрессия"| M{"Важны ли<br/>редкие большие ошибки?"}
M -->|"да"| MSE["MSE / RMSE"]
M -->|"нет, нужна устойчивость к выбросам"| MAE["MAE, медиана, квантили"]
R -->|"классификация"| B{"Классы<br/>сбалансированы?"}
B -->|"да, ошибки равноценны"| ACC["Accuracy"]
B -->|"нет"| T{"Порог уже выбран?"}
T -->|"да"| PRF["Precision, recall, F1 / Fβ"]
T -->|"нет, сравниваем модели"| AUC["ROC-AUC или PR-AUC"]Метрики регрессии: MSE, MAE, RMSE
Заголовок раздела «Метрики регрессии: MSE, MAE, RMSE»MSEсреднеквадратичная ошибка (MSE)EN mean squared error (MSE)Корень из MSE — RMSE (root mean squared error), измеряется в тех же единицах, что и целевая переменная. вы знаете по прошлым урокам. Её ближайшая родственница — средняя абсолютная ошибка (MAE)средняя абсолютная ошибка (MAE)EN mean absolute error (MAE):
Вернёмся к прогнозу времени ответа LLM. Ошибки на пяти запросах: и секунды. Последний запрос «завис» в очереди.
| MAE | MSE | RMSE | |
|---|---|---|---|
| Без зависшего запроса | 0.10 | 0.015 | 0.12 |
| С зависшим запросом | 0.68 | 1.812 | 1.35 |
Один выброс увеличил MAE в 6.8 раза, а MSE — в 120 раз. MSE «паникует» из-за больших ошибок, MAE спокойнее.
Разница глубже, чем кажется. Если предсказывать одну константу, то лучшая константа для MSE — среднее, а для MAE — медиана (число посередине упорядоченного списка, урок 0.3.3). Почему: производная по равна и обнуляется, когда — среднее. А производная равна «число точек ниже минус число точек выше ». Она обнуляется, когда по обе стороны от точек поровну, то есть в медиане. Пусть времена ответа равны 1, 1, 1, 1 и 10 секунд. Среднее — с, медиана — 1 с (третье из пяти упорядоченных чисел). Какое число вы покажете пользователю как «типичное время ответа»?
Поэтому в инференсе (так называют работу уже обученной модели — ответы на запросы) задержку описывают квантилями — перцентилями из урока 0.3.3. p50 — медиана: половина запросов обслужена быстрее. p99 — «хвост»: быстрее этого времени обслужено 99% запросов, и только 1% ждал дольше. Это тоже метрики, и они устойчивы к выбросам.
Почему метрика ≠ loss
Заголовок раздела «Почему метрика ≠ loss»Если в итоге нас интересует accuracy, почему бы не учить модель прямо на ней? Есть четыре причины.
-
Метрика не дифференцируема. Accuracy — ступенчатая функция весов. Сдвиньте веса чуть-чуть, и ни один ответ, скорее всего, не поменяется: accuracy та же, градиент ноль. Градиентному спуску не за что зацепиться. В PyTorch
argmaxпросто рвёт вычислительный граф (цепочку операций, по которой считаются градиенты, урок 1.2.2). -
Loss — гладкий суррогат (заменитель). Cross-entropy реагирует на любое изменение вероятностей. К тому же она связана с долей ошибок: если модель ошиблась, вероятность правильного класса (иначе он победил бы в argmax), и . Значит, средняя cross-entropy в битах (с ) не меньше доли ошибок: уменьшая loss, мы опускаем потолок для числа ошибок.
-
Loss видит уверенность, метрика — нет. Две модели решают задачу с двумя классами. На четырёх примерах они дают правильному классу такие вероятности (cross-entropy — в натах, с ; биты и наты — просто логарифмы по разным основаниям, урок 1.3.3):
Вероятности правильного класса Accuracy Cross-entropy Модель A 0.51, 0.51, 0.51, 0.49 75% 0.683 Модель B 0.99, 0.99, 0.99, 0.01 75% 1.159 Accuracy одинаковая. Но B один раз уверенно ошиблась, и cross-entropy это видит. Если LLM так же уверенно ошибается, при сэмплировании она почти наверняка выдаст неверный токен и продолжит уверенно неверный ответ.
-
Метрику выбирает задача, а не математика. Порог, в , цена ложной тревоги — бизнес-решения. Их принимают после обучения, на валидации.
С LLM то же самое, только на другом уровне. Претрейн минимизирует cross-entropy следующего токена (её экспонента — перплексияперплексияEN perplexity из урока 1.3.3). А модели сравнивают по accuracy на бенчмарках, pass@k на задачах с кодом (доля задач, где хотя бы одна из попыток прошла тесты), доле побед в Chatbot Arena (сайт, где люди вслепую сравнивают ответы двух моделей). Связь между ними есть, но не жёсткая. И есть закон Гудхартазакон ГудхартаEN Goodhart's law«Когда мера становится целью, она перестаёт быть хорошей мерой».: когда мера становится целью, она перестаёт быть хорошей мерой. Для подгонки под конкретный бенчмарк в китайском AI-сообществе есть жаргонное словечко «шуабан» («накручивать рейтинг»). Подробно об оценке LLMоценка (эвалы)EN evaluation (evals) — в уроке 7.3.1.
Практика
Заголовок раздела «Практика»Реализуйте метрики на чистом Python: матрицу ошибок, precision, recall, F1 (без деления на ноль) и ROC-AUC через
подсчёт пар. NumPy не нужен: хватит циклов, словаря и zip из модуля 0.2. Тесты используют примеры из этого урока,
поэтому ответы можно сверить с таблицами выше.
def confusion_counts(y_true, y_pred):
"""Матрица ошибок для бинарной классификации.
y_true, y_pred — списки из 0 и 1 одинаковой длины (1 = положительный класс).
Возвращает словарь {'tp': ..., 'fp': ..., 'fn': ..., 'tn': ...}.
"""
# TODO
...
def precision(y_true, y_pred):
"""Precision = TP / (TP + FP). Если модель не назвала ни одного положительного — вернуть 0.0."""
# TODO
...
def recall(y_true, y_pred):
"""Recall = TP / (TP + FN). Если положительных в y_true нет — вернуть 0.0."""
# TODO
...
def f1_score(y_true, y_pred):
"""F1 = 2·P·R / (P + R) — гармоническое среднее. Если P + R = 0 — вернуть 0.0."""
# TODO
...
def roc_auc(y_true, scores):
"""ROC-AUC через пары: доля пар (положительный, отрицательный), где положительный
получил БОЛЬШИЙ скор. Ничья (равные скоры) засчитывается как 0.5.
scores — список чисел (вероятностей или любых «баллов» модели).
"""
# TODO
...
# Проверьте себя кнопкой «Запустить» — пример из урока (порог 0.5):
y_true = [1, 1, 1, 1, 0, 0, 0, 0, 0, 0]
y_pred = [1, 1, 1, 0, 1, 0, 0, 0, 0, 0]
print(confusion_counts(y_true, y_pred)) # {'tp': 3, 'fp': 1, 'fn': 1, 'tn': 5}
print(precision(y_true, y_pred), recall(y_true, y_pred), f1_score(y_true, y_pred)) # 0.75 0.75 0.75
Метрика против loss на PyTorch (локально)
Заголовок раздела «Метрика против loss на PyTorch (локально)»Убедимся, что accuracy не пропускает градиент, а cross-entropy пропускает:
import torchimport torch.nn.functional as F
# 4 примера, 2 класса; правильные классы y# requires_grad=True — «PyTorch, запоминай операции с этим тензором, потом посчитаешь по нему градиент»logits = torch.tensor([[2.0, 0.5], [0.3, 0.1], [-1.0, 1.0], [0.2, -0.4]], requires_grad=True)y = torch.tensor([0, 1, 1, 0])
ce = F.cross_entropy(logits, y) # loss: softmax + минус лог правильного класса, среднееacc = (logits.argmax(dim=1) == y).float().mean() # метрика: доля примеров, где argmax совпал с yce.backward() # градиенты CE по логитам → в logits.gradprint(f"CE = {ce.item():.3f}, accuracy = {acc.item():.2f}")print("accuracy.requires_grad:", acc.requires_grad) # argmax рвёт граф: градиента у accuracy нетprint("dCE/dlogits:\n", logits.grad)
# Чуть-чуть сдвинем логиты второго примера к правильному классуwith torch.no_grad(): # здесь градиенты не нужны logits2 = logits.clone() # копия, чтобы не испортить исходный тензор logits2[1, 1] += 0.1 # второй пример (строка 1), правильный класс 1 (столбец 1)print(f"после сдвига: CE = {F.cross_entropy(logits2, y).item():.3f}, " f"accuracy = {(logits2.argmax(1) == y).float().mean().item():.2f}")Ожидаемый вывод:
CE = 0.391, accuracy = 0.75accuracy.requires_grad: FalsedCE/dlogits: tensor([[-0.0456, 0.0456], [ 0.1375, -0.1375], [ 0.0298, -0.0298], [-0.0886, 0.0886]])после сдвига: CE = 0.378, accuracy = 0.75Градиент cross-entropy указывает, куда двигать каждый логит. Сильнее всего «просит» второй пример — тот, на котором модель ошибается. Сдвиг в нужную сторону уменьшил CE, а accuracy даже не заметила. Градиенты здесь равны — та же формула из прошлого урока, делённая на размер батча.
Проверь себя
Заголовок раздела «Проверь себя»- Матрица ошибок (TP, FP, FN, TN) — основа всех метрик классификации. Accuracy обманывает при дисбалансе классов.
- Precision — чистота найденного, recall — полнота. Их баланс задаёт порог, а выбирают его на валидации. F1 — гармоническое среднее, — со смещением в нужную сторону. Как выбрать порог по цене ошибок, а не по F1, — в разборе модерации из урока 11.2.4.
- ROC-AUC — вероятность правильно упорядочить пару «положительный/отрицательный». Не зависит от порога. При сильном дисбалансе дополняйте её PR-кривой.
- MSE чувствительна к выбросам и ведёт к среднему, MAE устойчива и ведёт к медиане.
- Loss ≠ метрика: учим гладкий суррогат (cross-entropy), отчитываемся задачной метрикой. И помним про закон Гудхарта и «шуабан» — накрутку рейтинга.
Дальше: модуль 2.3 — kNN, деревья решений и бустинг. Это классические алгоритмы, которые на табличных данных до сих пор часто обходят нейросети. Метрики из этого урока понадобятся, чтобы честно их сравнить.