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

Что такое MRR (Mean Reciprocal Rank)?

2.4 Senior🔥 61 комментариев
#Python#Машинное обучение

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

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

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

Что такое MRR (Mean Reciprocal Rank)?

MRR (Mean Reciprocal Rank) — это метрика качества для задач информационного поиска и ранжирования, которая измеряет, насколько хорошо система ранжирует релевантные результаты. Это популярная метрика в поисковых системах, рекомендательных системах и вопросно-ответных системах.

Определение

MRR вычисляется как среднее значение обратных рангов релевантных результатов для набора запросов:

MRR = (1/Q) * sum(1/rank_i) для каждого query i

Где:
- Q — количество запросов
- rank_i — позиция первого релевантного результата для query i

Пример вычисления

# Пример: три запроса и результаты поиска

# Query 1: "best laptops"
# Результаты: [Laptop A (релевантный), Article (не релевантный), Laptop B (релевантный)]
# Первый релевантный на позиции 1
rank_1 = 1
reciprocal_1 = 1 / rank_1  # = 1.0

# Query 2: "programming languages"
# Результаты: [History article (не релевантный), Opinion (не релевантный), Python guide (релевантный)]
# Первый релевантный на позиции 3
rank_2 = 3
reciprocal_2 = 1 / rank_2  # = 0.333

# Query 3: "coffee recipes"
# Результаты: [Espresso machine (не релевантный), Coffee history (релевантный)]
# Первый релевантный на позиции 2
rank_3 = 2
reciprocal_3 = 1 / rank_3  # = 0.5

# MRR
MRR = (reciprocal_1 + reciprocal_2 + reciprocal_3) / 3
print(f"MRR = ({1.0} + {0.333:.3f} + {0.5}) / 3 = {MRR:.3f}")
# MRR = (1.0 + 0.333 + 0.5) / 3 = 0.611

Реализация на Python

import numpy as np
from typing import List

def calculate_mrr(relevance_scores: List[List[int]]) -> float:
    """
    Вычисляет MRR (Mean Reciprocal Rank).
    
    Args:
        relevance_scores: List[List[int]]
            Для каждого запроса список релевантностей результатов.
            1 = релевантный, 0 = не релевантный
    
    Returns:
        float: MRR значение от 0 до 1
    
    Example:
        relevance_scores = [
            [0, 1, 0, 0],  # Query 1: релевантный на позиции 2
            [1, 0, 0, 0],  # Query 2: релевантный на позиции 1
            [0, 0, 0, 1]   # Query 3: релевантный на позиции 4
        ]
        MRR = 1/2 + 1/1 + 1/4 / 3 = 0.458
    """
    reciprocal_ranks = []
    
    for query_relevances in relevance_scores:
        # Найди позицию первого релевантного результата
        for rank, is_relevant in enumerate(query_relevances, 1):
            if is_relevant == 1:
                reciprocal_ranks.append(1 / rank)
                break
        else:
            # Если нет релевантных результатов, добавляем 0
            reciprocal_ranks.append(0)
    
    # Вычисляем среднее
    mrr = np.mean(reciprocal_ranks)
    return mrr

# Пример использования
relevance_scores = [
    [0, 1, 0, 0],      # Релевантный на позиции 2
    [1, 0, 0, 0],      # Релевантный на позиции 1
    [0, 0, 0, 1],      # Релевантный на позиции 4
    [0, 0, 0, 0]       # Нет релевантных
]

mrr = calculate_mrr(relevance_scores)
print(f"MRR: {mrr:.4f}")  # (0.5 + 1.0 + 0.25 + 0) / 4 = 0.4375

# Детальный вывод
print("\nПодробно:")
for i, rel in enumerate(relevance_scores, 1):
    for rank, is_rel in enumerate(rel, 1):
        if is_rel == 1:
            print(f"Query {i}: релевантный на позиции {rank}, 1/rank = {1/rank:.4f}")
            break
    else:
        print(f"Query {i}: нет релевантных результатов")

MRR vs другие метрики

import numpy as np
from typing import List

class RankingMetrics:
    @staticmethod
    def mrr(relevance: List[int]) -> float:
        """
        MRR: средний ранг первого релевантного результата
        """
        for rank, is_relevant in enumerate(relevance, 1):
            if is_relevant == 1:
                return 1 / rank
        return 0
    
    @staticmethod
    def precision_at_k(relevance: List[int], k: int = 5) -> float:
        """
        P@K: доля релевантных среди первых K результатов
        """
        if len(relevance) < k:
            k = len(relevance)
        return np.sum(relevance[:k]) / k
    
    @staticmethod
    def recall_at_k(relevance: List[int], k: int = 5) -> float:
        """
        R@K: доля релевантных из всех релевантных результатов
        """
        total_relevant = np.sum(relevance)
        if total_relevant == 0:
            return 0
        if len(relevance) < k:
            k = len(relevance)
        return np.sum(relevance[:k]) / total_relevant
    
    @staticmethod
    def map_at_k(relevance: List[int], k: int = 5) -> float:
        """
        MAP@K: Mean Average Precision
        Средняя точность на каждой позиции релевантного результата
        """
        if len(relevance) < k:
            k = len(relevance)
        
        precisions = []
        num_relevant = 0
        
        for i in range(k):
            if relevance[i] == 1:
                num_relevant += 1
                precision_at_i = num_relevant / (i + 1)
                precisions.append(precision_at_i)
        
        if len(precisions) == 0:
            return 0
        return np.mean(precisions)
    
    @staticmethod
    def ndcg_at_k(relevance: List[float], k: int = 5) -> float:
        """
        NDCG@K: Normalized Discounted Cumulative Gain
        Учитывает позицию релевантного результата с экспоненциальным штрафом
        """
        if len(relevance) < k:
            k = len(relevance)
        
        # Discounted Cumulative Gain
        dcg = 0
        for i in range(k):
            # Дисконт по логарифме позиции
            dcg += relevance[i] / np.log2(i + 2)
        
        # Ideal DCG (идеальный ранжирование)
        ideal_relevance = sorted(relevance, reverse=True)[:k]
        idcg = 0
        for i in range(len(ideal_relevance)):
            idcg += ideal_relevance[i] / np.log2(i + 2)
        
        if idcg == 0:
            return 0
        
        return dcg / idcg

# Сравнение метрик
relevance = [1, 0, 1, 0, 0, 1, 0, 0, 0, 1]

print(f"MRR: {RankingMetrics.mrr(relevance):.4f}")
print(f"P@5: {RankingMetrics.precision_at_k(relevance, 5):.4f}")
print(f"R@5: {RankingMetrics.recall_at_k(relevance, 5):.4f}")
print(f"MAP@5: {RankingMetrics.map_at_k(relevance, 5):.4f}")
print(f"NDCG@5: {RankingMetrics.ndcg_at_k(relevance, 5):.4f}")

print("\nТаблица сравнения:")
print("""
| Метрика | Описание | Диапазон | Когда использовать |
|---------|---------|----------|--------------------|
| MRR | Позиция первого релевантного | 0-1 | User centric (первый результат) |
| P@K | Доля релевантных из K | 0-1 | Все релевантные одинаково важны |
| R@K | Покрытие релевантных | 0-1 | Важно найти все релевантные |
| MAP | Средняя точность | 0-1 | Учитывает порядок релевантных |
| NDCG | Дисконтированный выигрыш | 0-1 | Релевантность с градациями |
""")

Практический пример: поисковая система

from typing import List, Dict
import numpy as np

class SearchEngine:
    def __init__(self):
        self.documents = [
            {"id": 1, "title": "Python Tutorial"},
            {"id": 2, "title": "Java Basics"},
            {"id": 3, "title": "Python Best Practices"},
            {"id": 4, "title": "C++ Guide"},
            {"id": 5, "title": "Python Data Science"},
        ]
    
    def evaluate_search_results(
        self,
        queries: List[str],
        ground_truth: List[List[int]],
        results: List[List[int]]
    ) -> Dict[str, float]:
        """
        Оценивает качество поисковой системы по MRR и другим метрикам.
        
        Args:
            queries: список поисковых запросов
            ground_truth: список релевантных документов для каждого запроса
            results: ранжированные результаты (ID документов) для каждого запроса
        """
        mrr_scores = []
        
        for query, relevant_ids, result_ids in zip(queries, ground_truth, results):
            # Создаём бинарный список релевантности
            relevance = [1 if doc_id in relevant_ids else 0 for doc_id in result_ids]
            
            # Вычисляем MRR для этого запроса
            mrr = self._compute_mrr(relevance)
            mrr_scores.append(mrr)
            
            print(f"Query: '{query}'")
            print(f"Результаты: {result_ids}")
            print(f"Релевантные: {relevant_ids}")
            print(f"Релевантность: {relevance}")
            print(f"MRR: {mrr:.4f}\n")
        
        # Общий MRR
        overall_mrr = np.mean(mrr_scores)
        
        return {
            "mrr": overall_mrr,
            "per_query_mrr": mrr_scores,
            "min_mrr": np.min(mrr_scores),
            "max_mrr": np.max(mrr_scores)
        }
    
    @staticmethod
    def _compute_mrr(relevance: List[int]) -> float:
        for rank, is_relevant in enumerate(relevance, 1):
            if is_relevant == 1:
                return 1 / rank
        return 0

# Использование
engine = SearchEngine()

queries = [
    "Python tutorial",
    "Java programming",
    "Data Science"
]

ground_truth = [
    [1, 3, 5],  # Для "Python tutorial" релевантны документы 1, 3, 5
    [2],        # Для "Java programming" релевантен документ 2
    [5]         # Для "Data Science" релевантен документ 5
]

results = [
    [1, 2, 3, 4, 5],      # Результаты первого запроса
    [4, 2, 1, 3, 5],      # Результаты второго запроса
    [4, 3, 2, 5, 1]       # Результаты третьего запроса
]

metrics = engine.evaluate_search_results(queries, ground_truth, results)
print(f"Overall MRR: {metrics['mrr']:.4f}")
print(f"Min MRR: {metrics['min_mrr']:.4f}")
print(f"Max MRR: {metrics['max_mrr']:.4f}")

Интерпретация MRR

def interpret_mrr(mrr: float) -> str:
    """
    Интерпретирует значение MRR
    """
    if mrr >= 0.9:
        return "Отлично: первые результаты почти всегда релевантны"
    elif mrr >= 0.7:
        return "Хорошо: релевантные результаты обычно в первых 2-3 позициях"
    elif mrr >= 0.5:
        return "Удовлетворительно: релевантные результаты в первых 2-4 позициях"
    elif mrr >= 0.3:
        return "Слабо: релевантные результаты глубже в списке"
    else:
        return "Плохо: часто нет релевантных результатов в топе"

print(interpret_mrr(0.85))  # Хорошо
print(interpret_mrr(0.50))  # Удовлетворительно
print(interpret_mrr(0.20))  # Плохо

Когда использовать MRR

"""
MRR идеален для:

1. Поисковых систем
   - Пользователям главное найти первый релевантный результат
   - Не важно, есть ли другие релевантные результаты ниже

2. Вопросно-ответных систем
   - Нужно найти правильный ответ на первой позиции

3. Ранжирования документов
   - Когда главное — быстро найти нужный документ

4. Рекомендательных систем
   - Когда пользователь смотрит только на топ результатов

НЕ подходит MRR:
- Когда все релевантные результаты одинаково важны (используй Recall)
- Когда нужна точность по всему списку результатов (используй NDCG)
- Когда есть релевантность с градациями (используй MAP или NDCG)
"""

Улучшение MRR

# Стратегия 1: Улучшение ранкера
# Переобучить модель ранжирования с большим весом на позиции 1-3

# Стратегия 2: Query понимание
# Улучшить парсинг запроса для лучшего matching

# Стратегия 3: Diversification
# Добавить разнообразия в результаты (но это может снизить MRR)

# Пример: weighted loss для обучения ранкера
import torch
import torch.nn as nn

class RankerLoss(nn.Module):
    def forward(self, predictions, labels):
        # Даём больший вес ошибкам на ранних позициях
        position_weights = 1.0 / (torch.arange(1, len(predictions) + 1).float())
        weighted_loss = nn.BCEWithLogitsLoss(weight=position_weights)
        return weighted_loss(predictions, labels)

Вывод: MRR — это простая, но мощная метрика для оценки ранжирования в информационном поиске. Она особенно полезна, когда главное найти первый релевантный результат, что идеально для поисковых систем и вопросно-ответных приложений.

Что такое MRR (Mean Reciprocal Rank)? | PrepBro