← Назад к вопросам

Какие метрики использовал для задач ранжирования?

2.0 Middle🔥 121 комментариев
#Метрики и оценка моделей#Статистика и A/B тестирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Метрики для задач ранжирования

Контекст: задача ранжирования

В отличие от классификации (предсказать класс) и регрессии (предсказать число), ранжирование отвечает на вопрос: "в каком порядке показать результаты пользователю?"

Примеры:

  • Выдача в поисковой системе (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 обычно мониторят несколько метрик одновременно для полной картины качества ранжирования.

Какие метрики использовал для задач ранжирования? | PrepBro