Метрики точности и ранжирования

Урок вводит ключевые метрики качества рекомендаций — precision@k, recall@k, MAP и NDCG — и реализует их на чистом Python.

Метрика @k оценивает не весь прогноз, а только top-k выданных рекомендаций — потому что пользователь видит лишь верхушку списка.

Почему оцениваем именно топ

Рекомендатель выдаёт упорядоченный список, но человек смотрит первые несколько позиций. Поэтому метрики смотрят на top-k: насколько хороши первые k рекомендаций. Нам понадобится relevant — множество объектов, которые пользователю реально понравились (из отложенной тестовой части), и recommended — упорядоченный список модели.

Precision@k и Recall@k

Precision@k — какая доля из k показанных оказалась релевантной («не показали ли мы мусор»). Recall@k — какую долю всех релевантных мы поймали в top-k («не упустили ли мы нужное»). Это та же пара точность/полнота, что в учебнике «Машинное обучение», но обрезанная по k.

def precision_at_k(recommended, relevant, k):
    rec_k = recommended[:k]
    hits = sum(1 for x in rec_k if x in relevant)
    return hits / k

def recall_at_k(recommended, relevant, k):
    rec_k = recommended[:k]
    hits = sum(1 for x in rec_k if x in relevant)
    return hits / len(relevant) if relevant else 0.0

recommended = ["A", "B", "C", "D", "E"]
relevant = {"B", "D", "F"}
print("P@3 =", round(precision_at_k(recommended, relevant, 3), 3))
print("R@5 =", round(recall_at_k(recommended, relevant, 5), 3))

Вывод:

P@3 = 0.333
R@5 = 0.667

MAP: учитываем позицию попаданий

Precision@k не различает, попал релевантный объект на первое место или на пятое. Average Precision (AP) награждает за то, что релевантные стоят выше, а MAP усредняет AP по всем пользователям.

def average_precision(recommended, relevant, k):
    score, hits = 0.0, 0
    for i, item in enumerate(recommended[:k], start=1):
        if item in relevant:
            hits += 1
            score += hits / i
    return score / min(len(relevant), k) if relevant else 0.0

users = [
    (["A", "B", "C", "D", "E"], {"B", "D", "F"}),
    (["X", "Y", "Z"], {"X", "Z"}),
]
maps = [average_precision(r, rel, 5) for r, rel in users]
print("AP user1 =", round(maps[0], 3))
print("AP user2 =", round(maps[1], 3))
print("MAP =", round(sum(maps) / len(maps), 3))

Вывод:

AP user1 = 0.333
AP user2 = 0.833
MAP = 0.583

NDCG: градуированная релевантность

Иногда релевантность не бинарна: один фильм «понравился», другой «обожаю». NDCG учитывает градуированную полезность и логарифмически штрафует за низкую позицию, а затем нормирует на идеальный порядок (значение 1 = идеально отсортировано).

import math

def dcg(rels):
    return sum(rel / math.log2(i + 2) for i, rel in enumerate(rels))

def ndcg(rels):
    ideal = sorted(rels, reverse=True)
    idcg = dcg(ideal)
    return dcg(rels) / idcg if idcg else 0.0

rels = [3, 2, 0, 1, 2]  # релевантность выданного списка по позициям
print("DCG  =", round(dcg(rels), 3))
print("NDCG =", round(ndcg(rels), 3))

Вывод:

DCG  = 5.466
NDCG = 0.96

Как работает под капотом

Все эти метрики считают на отложенной выборке: часть взаимодействий прячут, обучают модель на остальном, а затем проверяют, попали ли спрятанные объекты в top-k. Для неявных данных берут ранжирующие метрики (precision/recall/MAP/NDCG), а не RMSE оценок. Усреднение всегда идёт по пользователям, а не по всем строкам, иначе активные пользователи перетянут результат на себя.

Частые ошибки

  • Считать метрику по всему списку. Пользователь видит топ; оценивайте @k.
  • Применять RMSE к неявным данным. Там нет «правильной оценки» — нужны ранжирующие метрики.
  • Усреднять по взаимодействиям, а не по пользователям. Иначе несколько сверхактивных пользователей исказят картину.

Итоги

  • Метрики @k оценивают только видимую пользователю верхушку списка.
  • Precision@k ловит мусор, Recall@k ловит упущенное.
  • MAP и NDCG награждают за то, что релевантное стоит выше.
  • Метрики считают на отложенной выборке и усредняют по пользователям.
Проверьте себя
1. Что измеряет precision@k?
AДолю всех релевантных объектов, попавших в top-k
BДолю релевантных среди первых k показанных рекомендаций
CСреднюю оценку всех товаров
DСкорость работы модели
2. Чем NDCG отличается от precision@k?
AНичем
BNDCG учитывает градуированную релевантность и позицию, нормируя на идеальный порядок
CNDCG игнорирует порядок
DNDCG работает только с оценками 0 и 1