← Назад к вопросам
Что такое 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 — это простая, но мощная метрика для оценки ранжирования в информационном поиске. Она особенно полезна, когда главное найти первый релевантный результат, что идеально для поисковых систем и вопросно-ответных приложений.