Какие метрики использовал для задач ранжирования?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Метрики для задач ранжирования
Контекст: задача ранжирования
В отличие от классификации (предсказать класс) и регрессии (предсказать число), ранжирование отвечает на вопрос: "в каком порядке показать результаты пользователю?"
Примеры:
- Выдача в поисковой системе (Google, Yandex)
- Рекомендации продуктов (Amazon, Netflix)
- Ленты в соцсетях (Facebook, Instagram)
- Рейтинги (IMDb, Airbnb)
Реальный пример из моей работы
Разрабатывал систему ранжирования отелей для booking платформы. Задача: для поиска "5-звёздочный отель в Париже" показать лучшие отели в топе.
Метрики, которые использовал:
1. NDCG (Normalized Discounted Cumulative Gain)
Это главная метрика для ранжирования. Учитывает:
- Relevance (релевантность) — насколько хорош результат
- Position (позиция) — штраф за низкую позицию
- Ideal ranking (идеальное ранжирование) — нормализация
from sklearn.metrics import ndcg_score
import numpy as np
# y_true: истинные оценки релевантности [идеальное ранжирование]
# y_score: предсказанные оценки [наше ранжирование]
y_true = np.array([10, 9, 8, 7, 6]) # Идеальный порядок (высокие оценки отелей)
y_score = np.array([10, 8, 9, 7, 6]) # Наш порядок (мы ошиблись: 8 и 9 поменялись)
ndcg = ndcg_score([y_true], [y_score])
print(f"NDCG@5: {ndcg:.3f}") # ~0.98 (близко к идеалу)
# Более плохое ранжирование
y_score_bad = np.array([10, 6, 7, 8, 9]) # Совсем не то
ndcg_bad = ndcg_score([y_true], [y_score_bad])
print(f"NDCG@5 (плохо): {ndcg_bad:.3f}") # Низкое значение
Как работает NDCG:
1. DCG (Discounted Cumulative Gain)
- Gain = оценка релевантности
- Discount = log2(position + 1)
DCG = rel_1 + rel_2/log2(3) + rel_3/log2(4) + ...
Пример:
- Position 1: 10 * 1 = 10 (высокий discount)
- Position 2: 8 / log2(3) = 8 / 1.585 = 5.05 (средний discount)
- Position 3: 9 / log2(4) = 9 / 2 = 4.5
- DCG = 10 + 5.05 + 4.5 + ... = 27.35
2. IDCG (Ideal DCG) - лучшее возможное ранжирование
- Идеальный порядок: [10, 9, 8, 7, 6]
- IDCG = 10 + 9/1.585 + 8/2 + 7/2.32 + 6/2.58 = 28.0
3. NDCG = DCG / IDCG = 27.35 / 28.0 = 0.98
Использование в коде:
from sklearn.metrics import ndcg_score
# В контексте полной метрики на тестовом наборе
queries = [
{'true': [10, 9, 8, 7, 6], 'pred': [10, 8, 9, 7, 6]},
{'true': [9, 8, 7, 6, 5], 'pred': [9, 7, 8, 6, 5]},
{'true': [8, 7, 6, 5, 4], 'pred': [8, 5, 6, 7, 4]}
]
ndcg_scores = []
for query in queries:
ndcg = ndcg_score([query['true']], [query['pred']])
ndcg_scores.append(ndcg)
avg_ndcg = np.mean(ndcg_scores)
print(f"Mean NDCG: {avg_ndcg:.3f}")
2. MAP (Mean Average Precision)
Используется когда релевантность бинарна (релевантен/не релевантен).
from sklearn.metrics import average_precision_score
# y_true: 1 если релевантен, 0 если нет
y_true = np.array([1, 0, 1, 1, 0, 1])
# y_score: оценка релевантности (вероятность)
y_score = np.array([0.9, 0.7, 0.8, 0.6, 0.4, 0.5])
map_score = average_precision_score(y_true, y_score)
print(f"MAP: {map_score:.3f}")
# Как считается:
# Precision@1 = 1/1 = 1.0 (правильный релевантный)
# Precision@3 = 2/3 = 0.67 (два релевантных из трёх)
# Precision@4 = 2/4 = 0.5
# Precision@6 = 3/6 = 0.5
# MAP = (1.0 + 0.67 + 0.5 + 0.5) / 4 = 0.67
3. MRR (Mean Reciprocal Rank)
Учитывает позицию первого релевантного результата. Хороша для задач, где нужен хотя бы один хороший результат в топе.
def mean_reciprocal_rank(y_true):
"""y_true: массив релевантности (1 или 0)"""
for i, rel in enumerate(y_true):
if rel == 1:
return 1 / (i + 1)
return 0
# Примеры
rankings = [
[1, 0, 0, 0, 0], # MRR = 1/1 = 1.0 (релевант на позиции 1)
[0, 1, 0, 0, 0], # MRR = 1/2 = 0.5
[0, 0, 1, 0, 0], # MRR = 1/3 = 0.33
[0, 0, 0, 0, 0], # MRR = 0 (не нашли)
]
mrr_scores = [mean_reciprocal_rank(r) for r in rankings]
mean_mrr = np.mean(mrr_scores)
print(f"Mean MRR: {mean_mrr:.3f}") # 0.457
Когда использовать:
- Search (нужно найти первый релевантный результат)
- Question Answering (нужен один правильный ответ)
- Diagnostic systems (нужен один правильный диагноз)
4. P@K (Precision at K)
Сколько процентов от топ K результатов релевантны.
def precision_at_k(y_true, k):
"""Какой процент топ K результатов релевантны"""
return np.sum(y_true[:k]) / k
y_true = np.array([1, 1, 0, 1, 0, 1, 0, 0])
print(f"P@3 = {precision_at_k(y_true, 3):.3f}") # 2/3 = 0.667
print(f"P@5 = {precision_at_k(y_true, 5):.3f}") # 3/5 = 0.6
print(f"P@10 = {precision_at_k(y_true, 10):.3f}") # 4/8 = 0.5
Примеры в Google:
- P@10 — в выдаче 10 результатов, сколько релевантных
- P@3 — в топ 3 результатах, сколько релевантных
5. R@K (Recall at K)
Из всех релевантных результатов, сколько мы показали в топ K.
def recall_at_k(y_true, k):
"""Какую долю всех релевантных результатов мы нашли в топ K"""
relevant_total = np.sum(y_true)
relevant_in_k = np.sum(y_true[:k])
return relevant_in_k / relevant_total if relevant_total > 0 else 0
y_true = np.array([1, 1, 0, 1, 0, 1, 0, 0]) # 4 релевантных
print(f"R@3 = {recall_at_k(y_true, 3):.3f}") # 2/4 = 0.5 (нашли 2 из 4)
print(f"R@5 = {recall_at_k(y_true, 5):.3f}") # 3/4 = 0.75
print(f"R@10 = {recall_at_k(y_true, 10):.3f}") # 4/4 = 1.0
6. RR (Reciprocal Rank) для отдельного примера
Позиция первого релевантного результата (обратная величина).
def reciprocal_rank(y_true):
for i, rel in enumerate(y_true):
if rel == 1:
return 1 / (i + 1)
return 0
y_true = [0, 1, 0, 1] # Релевант на позиции 2
rr = reciprocal_rank(y_true)
print(f"RR: {rr:.3f}") # 1/2 = 0.5
Сравнение метрик
import numpy as np
from sklearn.metrics import ndcg_score, average_precision_score
# Пример: результаты поиска отелей
y_true = np.array([5, 4, 3, 2, 1]) # Оценки отелей
y_pred = np.array([5, 3, 4, 2, 1]) # Наше ранжирование
# Конвертировать для MAP
y_true_binary = y_true > 3 # [T, T, F, F, F]
y_pred_score = y_pred / np.max(y_pred) # Нормализовать
ndcg = ndcg_score([y_true], [y_pred])
map_score = average_precision_score(y_true_binary, y_pred_score)
p_at_3 = np.sum(y_true_binary[:3]) / 3
print(f"NDCG@5: {ndcg:.3f}")
print(f"MAP: {map_score:.3f}")
print(f"P@3: {p_at_3:.3f}")
Мой выбор метрик для разных задач
Поиск отелей (как в примере):
- Primary: NDCG@10 (учитывает релевантность и позицию)
- Secondary: P@3, P@10 (правильные результаты в топе)
- Business: Средняя позиция клика (click position)
Рекомендации (Netflix, Amazon):
- Primary: nDCG@10 (качество рекомендаций)
- Secondary: Recall@10 (покрыли ли все релевантные)
- Business: Click-through rate (CTR)
Поиск информации (Google):
- Primary: NDCG@1, NDCG@3, NDCG@10
- Secondary: MRR (первый релевантный результат)
- Business: User satisfaction (опросы)
Question Answering:
- Primary: MRR (нужен правильный ответ в топе)
- Secondary: R@1, R@5 (покрыли ли правильный ответ)
Реализация в production
class RankingMetricsEvaluator:
def __init__(self, k_values=[1, 3, 5, 10]):
self.k_values = k_values
def evaluate(self, y_true, y_pred_scores):
"""y_true: binary relevance, y_pred_scores: predicted scores"""
# Получить ранжирование по scores
ranked_indices = np.argsort(y_pred_scores)[::-1]
y_true_ranked = y_true[ranked_indices]
metrics = {}
# NDCG
from sklearn.metrics import ndcg_score
metrics['ndcg'] = ndcg_score([y_true], [y_pred_scores])
# MAP
from sklearn.metrics import average_precision_score
metrics['map'] = average_precision_score(y_true, y_pred_scores)
# MRR
metrics['mrr'] = self._mrr(y_true_ranked)
# P@K и R@K
for k in self.k_values:
metrics[f'p@{k}'] = np.sum(y_true_ranked[:k]) / k
metrics[f'r@{k}'] = self._recall_at_k(y_true_ranked, k)
return metrics
@staticmethod
def _mrr(y_true):
for i, rel in enumerate(y_true):
if rel:
return 1 / (i + 1)
return 0
@staticmethod
def _recall_at_k(y_true, k):
relevant_total = np.sum(y_true)
if relevant_total == 0:
return 0
return np.sum(y_true[:k]) / relevant_total
# Использование
evaluator = RankingMetricsEvaluator()
y_true = np.array([1, 0, 1, 1, 0])
y_scores = np.array([0.9, 0.5, 0.8, 0.7, 0.3])
metrics = evaluator.evaluate(y_true, y_scores)
print(metrics)
Заключение
Выбирайте метрику в зависимости от задачи:
- NDCG — когда есть градации релевантности (5 звёзд, 4 звёзды и т.д.)
- MAP — когда релевантность бинарна (да/нет)
- MRR — когда важен первый правильный результат
- P@K — когда нужно знать точность в топе
- R@K — когда нужно покрыть все релевантные результаты
В production обычно мониторят несколько метрик одновременно для полной картины качества ранжирования.