RAG 评测数据集建设尚处于初期阶段,缺乏针对特定领域和场景的专业数据集。市面上常见的 MS-Marco 和 BEIR 数据集覆盖范围有限,且在实际使用场景中效果可能与评测表现不符。目前最权威的检索榜单是 HuggingFace MTEB,今天我们来学习使用MTEB,并来评测自研模型recall效果。

MTEB 是一个包含广泛文本嵌入(Text Embedding)的基准测试,它提供了多种语言的数十个数据集,用于各种 NLP 任务,例如文本分类、聚类、检索和文本相似性。MTEB 提供了一个公共排行榜,允许研究人员提交他们的结果并跟踪他们的进展。MTEB 还提供了一个简单的 API,允许研究人员轻松地将他们的模型与基准测试进行比较。

安装使用

pip install mteb

使用入门

from mteb import MTEB
from sentence_transformers import SentenceTransformer # Define the sentence-transformers model name
model_name = "average_word_embeddings_komninos" model = SentenceTransformer(model_name)
evaluation = MTEB(tasks=["Banking77Classification"])
results = evaluation.run(model, output_folder=f"results/{model_name}")
  • 也可以使用官方提供的 CLI
mteb --available_tasks

mteb -m average_word_embeddings_komninos \
-t Banking77Classification \
--output_folder results/average_word_embeddings_komninos \
--verbosity 3

高级用法

测试数据集选择

MTEB支持指定数据集,可以通过下面的形式

  • 按task_type任务类型(例如“聚类”或“分类”)
evaluation = MTEB(task_types=['Clustering', 'Retrieval']) # Only select clustering and retrieval tasks
  • 按类别划分, 例如“句子到句子 "S2S" (sentence to sentence) "P2P" (paragraph to paragraph)
evaluation = MTEB(task_categories=['S2S']) # Only select sentence2sentence datasets
  • 按照文本语言
evaluation = MTEB(task_langs=["en", "de"]) # Only select datasets which are "en", "de" or "en-de"

还可以针对数据集选择语言:

from mteb.tasks import AmazonReviewsClassification, BUCCBitextMining

evaluation = MTEB(tasks=[
AmazonReviewsClassification(langs=["en", "fr"]) # Only load "en" and "fr" subsets of Amazon Reviews
BUCCBitextMining(langs=["de-en"]), # Only load "de-en" subset of BUCC
])

可为某些任务集合提供预设

from mteb import MTEB_MAIN_EN
evaluation = MTEB(tasks=MTEB_MAIN_EN, task_langs=["en"])

自定义评测 split

有的数据集有多个split,评测会比较耗时,可以指定splits,来减少评测时间,比如下面的就指定了只用test split。

evaluation.run(model, eval_splits=["test"])

自定义评测模型

如果想自定义评测模型,可以自定义一个类,只要实现一个encode函数,输入是一个句子列表,返回的是一个嵌入向量列表(嵌入可以是np.array、torch.tensor等)。可以参考 mteb/mtebscripts repo 仓库。

class MyModel():
def encode(self, sentences, batch_size=32, **kwargs):
"""
Returns a list of embeddings for the given sentences.
Args:
sentences (`List[str]`): List of sentences to encode
batch_size (`int`): Batch size for the encoding Returns:
`List[np.ndarray]` or `List[tensor]`: List of embeddings for the given sentences
"""
pass model = MyModel()
evaluation = MTEB(tasks=["Banking77Classification"])
evaluation.run(model)

如果针对query和corpus需要使用不同的encode方法,可以独立提供encode_queries and encode_corpus两个方法。

class MyModel():
def encode_queries(self, queries, batch_size=32, **kwargs):
"""
Returns a list of embeddings for the given sentences.
Args:
queries (`List[str]`): List of sentences to encode
batch_size (`int`): Batch size for the encoding Returns:
`List[np.ndarray]` or `List[tensor]`: List of embeddings for the given sentences
"""
pass def encode_corpus(self, corpus, batch_size=32, **kwargs):
"""
Returns a list of embeddings for the given sentences.
Args:
corpus (`List[str]` or `List[Dict[str, str]]`): List of sentences to encode
or list of dictionaries with keys "title" and "text"
batch_size (`int`): Batch size for the encoding Returns:
`List[np.ndarray]` or `List[tensor]`: List of embeddings for the given sentences
"""
pass

自定义评测Task(数据集)

要添加一个新任务,你需要实现一个从与任务类型相关的AbsTask继承的新类(例如,对于重排任务是AbsTaskReranking)。你可以在这里找到支持的任务类型。

比如下面的自定义重排任务:

from mteb import MTEB
from mteb.abstasks.AbsTaskReranking import AbsTaskReranking
from sentence_transformers import SentenceTransformer class MindSmallReranking(AbsTaskReranking):
@property
def description(self):
return {
"name": "MindSmallReranking",
"hf_hub_name": "mteb/mind_small",
"description": "Microsoft News Dataset: A Large-Scale English Dataset for News Recommendation Research",
"reference": "https://www.microsoft.com/en-us/research/uploads/prod/2019/03/nl4se18LinkSO.pdf",
"type": "Reranking",
"category": "s2s",
"eval_splits": ["validation"],
"eval_langs": ["en"],
"main_score": "map",
} model = SentenceTransformer("average_word_embeddings_komninos")
evaluation = MTEB(tasks=[MindSmallReranking()])
evaluation.run(model)

源码分析

Retrieval召回评测

召回评测是通过RetrievalEvaluator类实现的。

def __init__(
self,
queries: Dict[str, str], # qid => query
corpus: Dict[str, str], # cid => doc
relevant_docs: Dict[str, Set[str]], # qid => Set[cid]
corpus_chunk_size: int = 50000,
mrr_at_k: List[int] = [10],
ndcg_at_k: List[int] = [10],
accuracy_at_k: List[int] = [1, 3, 5, 10],
precision_recall_at_k: List[int] = [1, 3, 5, 10],
map_at_k: List[int] = [100],
show_progress_bar: bool = False,
batch_size: int = 32,
name: str = "",
score_functions: List[Callable[[torch.Tensor, torch.Tensor], torch.Tensor]] = {
"cos_sim": cos_sim,
"dot": dot_score,
}, # Score function, higher=more similar
main_score_function: str = None,
limit: int = None,
**kwargs
):
super().__init__(**kwargs)
self.queries_ids = []
for qid in queries:
if qid in relevant_docs and len(relevant_docs[qid]) > 0:
self.queries_ids.append(qid)
if limit and len(self.queries_ids) >= limit:
break self.queries = [queries[qid] for qid in self.queries_ids] self.corpus_ids = list(corpus.keys())
self.corpus = [corpus[cid] for cid in self.corpus_ids] self.relevant_docs = relevant_docs
self.corpus_chunk_size = corpus_chunk_size
self.mrr_at_k = mrr_at_k
self.ndcg_at_k = ndcg_at_k
self.accuracy_at_k = accuracy_at_k
self.precision_recall_at_k = precision_recall_at_k
self.map_at_k = map_at_k self.show_progress_bar = show_progress_bar
self.batch_size = batch_size
self.name = name
self.score_functions = score_functions
self.score_function_names = sorted(list(self.score_functions.keys()))
self.main_score_function = main_score_function

构造函数几个重要的参数:

- queries: Dict[str, str],  # qid => query qid到query的dict

  - corpus: Dict[str, str],  # cid => doc docid到doc的dict

  - relevant_docs: Dict[str, Set[str]],  # qid => Set[cid] qid到相关docid的dict

因此,要自定义评测任务,需要提供这些数据。

具体的评测函数在compute_metrics里:

def compute_metrics(self, model, corpus_model=None, corpus_embeddings: torch.Tensor = None) -> Dict[str, float]:
if corpus_model is None:
corpus_model = model max_k = max(
max(self.mrr_at_k),
max(self.ndcg_at_k),
max(self.accuracy_at_k),
max(self.precision_recall_at_k),
max(self.map_at_k),
) # Compute embedding for the queries
logger.info("Encoding the queries...")
# We don't know if encode has the kwargs show_progress_bar
kwargs = {
"show_progress_bar": self.show_progress_bar
} if "show_progress_bar" in inspect.signature(model.encode).parameters else {}
query_embeddings = np.asarray(model.encode(self.queries, batch_size=self.batch_size, **kwargs))
queries_result_list = {}
for name in self.score_functions:
queries_result_list[name] = [[] for _ in range(len(query_embeddings))] # Iterate over chunks of the corpus
logger.info("Encoding chunks of corpus, and computing similarity scores with queries...")
for corpus_start_idx in trange(
0,
len(self.corpus),
self.corpus_chunk_size,
desc="Corpus Chunks",
disable=not self.show_progress_bar,
):
# Encode chunk of corpus
if corpus_embeddings is None:
corpus_end_idx = min(corpus_start_idx + self.corpus_chunk_size, len(self.corpus))
sub_corpus_embeddings = np.asarray(corpus_model.encode(
self.corpus[corpus_start_idx:corpus_end_idx],
batch_size=self.batch_size,
))
else:
corpus_end_idx = min(corpus_start_idx + self.corpus_chunk_size, len(corpus_embeddings))
sub_corpus_embeddings = corpus_embeddings[corpus_start_idx:corpus_end_idx] # Compute cosine similarites
for name, score_function in self.score_functions.items():
pair_scores = score_function(query_embeddings, sub_corpus_embeddings) # Get top-k values
pair_scores_top_k_values, pair_scores_top_k_idx = torch.topk(
pair_scores,
min(max_k, len(pair_scores[0])),
dim=1,
largest=True,
sorted=False,
)
pair_scores_top_k_values = pair_scores_top_k_values.cpu().tolist()
pair_scores_top_k_idx = pair_scores_top_k_idx.cpu().tolist() for query_itr in range(len(query_embeddings)):
for sub_corpus_id, score in zip(
pair_scores_top_k_idx[query_itr],
pair_scores_top_k_values[query_itr],
):
corpus_id = self.corpus_ids[corpus_start_idx + sub_corpus_id]
queries_result_list[name][query_itr].append({"corpus_id": corpus_id, "score": score}) # Compute scores
logger.info("Computing metrics...")
scores = {name: self._compute_metrics(queries_result_list[name]) for name in self.score_functions} return scores
  • model(embedding模型),corpus_model(如果doc用单独的embedding模型,需要传入这个参数,否则默认使用和query一样的model)
  • 首先会计算query_embedding query_embeddings = np.asarray(model.encode(self.queries, batch_size=self.batch_size, **kwargs))
  • 然后计算corpus_embeddings
  • 通过score_function,计算tok_k, 结果放到queries_result_list
  • 根据召回结果计算指标_compute_metrics, 会计算"mrr@k", "ndcg@k", "accuracy@k", "precision_recall@k", "map@k"等指标

Reranking 精排

精排是通过RerankingEvaluator来实现的。

class RerankingEvaluator(Evaluator):
"""
This class evaluates a SentenceTransformer model for the task of re-ranking.
Given a query and a list of documents, it computes the score [query, doc_i] for all possible
documents and sorts them in decreasing order. Then, MRR@10 and MAP is compute to measure the quality of the ranking.
:param samples: Must be a list and each element is of the form:
- {'query': '', 'positive': [], 'negative': []}. Query is the search query, positive is a list of positive
(relevant) documents, negative is a list of negative (irrelevant) documents.
- {'query': [], 'positive': [], 'negative': []}. Where query is a list of strings, which embeddings we average
to get the query embedding.
""" def __init__(
self,
samples,
mrr_at_k: int = 10,
name: str = "",
similarity_fct=cos_sim,
batch_size: int = 512,
use_batched_encoding: bool = True,
limit: int = None,
**kwargs,
):

给定一个query和一组文档,模型计算文档得分,并按降序排列,最后计算MRR@10和MAP指标来衡量排名的质量。

__init__方法接收以下参数:

  • samples:必须是一个列表,每个元素的形式为:

    • {'query': '', 'positive': [], 'negative': []}。查询是搜索查询,正文档是相关(正面)文档的列表,负文档是无关(负面)文档的列表。
    • {'query': [], 'positive': [], 'negative': []}。其中查询是一个字符串列表,我们将这些字符串的平均嵌入作为查询嵌入。
  • mrr_at_k:默认值为10,表示计算MRR时考虑的前k个结果。
  • name:默认值为空字符串,表示评估器的名称。
  • similarity_fct:默认值为cos_sim,表示用于计算相似度的函数。

compute_metrics_batched 计算得分,还是计算的cos得分,这里相当于直接计算的embedding的排序能力,如果要计算cross模型的排序能力,默认的代码不适用,需要重新定制。

评测实践

说了这么多,现在切入正题:

  • 评测自研模型的召回能力 —— 自定义模型
  • 自定义评测集,对比开源模型和自研模型的效果 —— 自定义评测任务

自研模型召回效果评测

我们先评估模型召回效果,训练好的模型导出为onnx,因此我们通过onnxrutime来进行推理,先自定义模型:

from mteb import MTEB
import onnxruntime as ort
from paddlenlp.transformers import AutoTokenizer
import math
from tqdm import tqdm
# 模型路径
model_path = "onnx/fp16_model.onnx"
tokenizer_path = "model_520000" class MyModel():
def __init__(self, use_gpu=True):
providers = ['CUDAExecutionProvider'] if use_gpu else ['CPUExecutionProvider']
sess_options = ort.SessionOptions()
self.predictor = ort.InferenceSession(
model_path, sess_options=sess_options, providers=providers)
self.tokenizer = AutoTokenizer.from_pretrained(tokenizer_path) def encode(self, sentences, batch_size=64, **kwargs):
all_embeddings = []
# 向上取整
batch_count = math.ceil(len(sentences) / batch_size) for i in tqdm(range(batch_count)):
# 按batch
sub_sentences = sentences[i * batch_size : min(len(sentences), (i + 1) * batch_size)]
features = self.tokenizer(sub_sentences, max_seq_len=128,
pad_to_max_seq_len=True, truncation_strategy="longest_first")
vecs = self.predictor.run(None, features.data)
all_embeddings.extend(vecs[0])
return all_embeddings

由于传进来的sentences是所有的数据,我们需要按照batch_size,分批进行embedding计算,计算好的放入all_embeddings,最后返回即可。

自定义召回评测任务

上面分析源代码时提到了,自定义时需要提供qurey,doc,以及query的相关doc

假设我们的自定义测试为jsonline格式,每行包含query,以及相关的doc,json格式如下:

{

    "query": "《1984》是什么",
    "data": [
        {             "title": "《1984》介绍-知乎",
            "summary": "《1984》是伪装成小说的政治思想...",
            "url": "",
            "id": 5031622209044687985,
            "answer": "完全相关",
            "accuracy": "无错",
            "result": "good"
        }
    ]
}

那么我们可以编写自定义召回评测任务:

class SSRetrieval(AbsTaskRetrieval):
@property
def description(self):
return {
'name': 'SSRetrieval',
'description': 'SSRetrieval是S研发部测试团队准备的召回测试集',
'type': 'Retrieval',
'category': 's2p',
'json_path': '/data/xapian-core-1.4.24/demo/result.json',
'eval_splits': ['dev'],
'eval_langs': ['zh'],
'main_score': 'recall_at_10',
} def load_data(self, **kwargs):
if self.data_loaded:
return self.corpus = {} # doc_id => doc
self.queries = {} # qid => query
self.relevant_docs = {} # qid => Set[doc_id]
query_index = 1
with open(self.description['json_path'], 'r', encoding='utf-8') as f:
for line in f:
if "完全相关" not in line:
continue
line = json.loads(line)
query = line['query']
query_id = str(query_index)
self.queries[query_id] = query
query_index = query_index + 1
query_relevant_docs = []
for doc in line['data']:
doc_id = str(doc['id'])
self.corpus[doc_id] = {"title": doc["title"], "text": doc["summary"]}
if doc['answer'] == "完全相关":
if query_id not in self.relevant_docs:
self.relevant_docs[query_id] = {}
self.relevant_docs[query_id][doc_id] = 1 # debug使用
# if query_index == 100:
# break self.queries = DatasetDict({"dev": self.queries})
self.corpus = DatasetDict({"dev": self.corpus})
self.relevant_docs = DatasetDict({"dev": self.relevant_docs}) self.data_loaded = True

用自定义模型,评测自定义任务

if __name__ == '__main__':

    model = MyModel()

    # task_names = [t.description["name"] for t in MTEB(task_types='Retrieval',
# task_langs=['zh', 'zh-CN']).tasks] task_names = ["SSRetrieval"] for task in task_names:
model.query_instruction_for_retrieval = None
evaluation = MTEB(tasks=[task], task_langs=['zh', 'zh-CN'])
evaluation.run(model, output_folder=f"zh_results/256_model", batch_size=64)

总结

mteb 最为embedding召回效果测试,是一个权威的榜单,本身提供的工具框架也具备较好的扩展性,方便开发者自定义模型和自定义评测任务。

搜索引擎RAG召回效果评测MTEB介绍与使用入门的更多相关文章

  1. Lucene.net站内搜索—4、搜索引擎第一版技术储备(简单介绍Log4Net、生产者消费者模式)

    目录 Lucene.net站内搜索—1.SEO优化 Lucene.net站内搜索—2.Lucene.Net简介和分词Lucene.net站内搜索—3.最简单搜索引擎代码Lucene.net站内搜索—4 ...

  2. mxgraph进阶(二)mxgraph的初步介绍与开发入门

    mxgraph的初步介绍与开发入门 前言 由于小论文实验需求,需要实现根据用户日志提取出行为序列,然后根据行为序列生成有向图的形式,并且连接相邻动作的弧上标有执行此次相邻动作的频次.为此,在大师兄徐凯 ...

  3. _00017 Kafka的体系结构介绍以及Kafka入门案例(0基础案例+Java API的使用)

    博文作者:妳那伊抹微笑 itdog8 地址链接 : http://www.itdog8.com(个人链接) 博客地址:http://blog.csdn.net/u012185296 博文标题:_000 ...

  4. Web安全之Web 安全介绍与基础入门知识

    web安全介绍与基础入门知识 安全与安全圈 甲方与乙方 甲方:如腾讯,阿里等需要安全服务的公司 乙方:提供安全服务产品的服务型安全公司 web与二进制 web,研究web安全 二进制,研究如客户端安全 ...

  5. 聚类效果评测-Fmeasure和Accuracy及其Matlab实现

    聚类结果的好坏,有很多种指标,其中F-Measue即F值是常用的一种,其中包括precision(查准率或者准确率)和recall(查全率或者召回率). F-Measue是信息检索中常用的评价标准. ...

  6. jq实现搜索引擎的提示效果

    (function ($) { $.fn.Search = function (options) { var defaults = { inputid: "search", div ...

  7. 搜索引擎的提示效果完整的JavaScript代码

    function divShow() { <%--判断输入的是否为空 如果为空则隐藏div 如果不为空则显示div --%> if ($("#tbxSearchKeywords& ...

  8. 007_苹果Mac系统锁屏不待机效果设置方法介绍

    Mac如何设置锁屏不断网?Mac如何设置锁屏不待机?这是一个非常麻烦的设置,有时候一锁屏幕电脑就跟着待机了,这非常的麻烦,所以今天小编就用图文教程的方式教大家Mac如何设置锁屏不断网Mac如何设置锁屏 ...

  9. 2.使用Lucene开发自己的搜索引擎–indexer索引程序中基本类介绍

    (1)Directory:Directory类描述了Lucene索引的存放位置,它是一个抽象,其子类负责具体制定索引的存储路径.FSDirectory.open方法来获取真实文件在文件系统中的存储路径 ...

  10. Lucene介绍及简单入门案例(集成ik分词器)

    介绍 Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,但它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和 ...

随机推荐

  1. [转帖]【性能】中断绑定和查看|irqbalance 中断负载均衡|CPU瓶颈

    常用命令 ``` # 查看当前运行情况 service irqbalance status # 终止服务 service irqbalance stop 取消开机启动: chkconfig irqba ...

  2. 【转帖】通过pip命令安装好包之后,在pycharm中不显示此库,也不能调用

    目录 1. 问题描述 2. 解决方法1 3. 解决方法2 1. 问题描述 在cmd输入pip list 命令可以看到我的库都已经安装好了,但是pycharm中却没有显示. 在PyCharm查找,并没有 ...

  3. Windows 磁盘部分性能数据获取

    Windows 磁盘部分性能数据获取 摘要 每次晚上加班总有收获 这次发现了一个fio for windows版本的压测程序, 准备学习和使用一下. https://github.com/axboe/ ...

  4. 【图】苹果Safari 6.0停止支持Windows PC (转载)

    [图]果Safari 6.0停止支持Windows PC (转载) http://bbs.tianya.cn/post-414-41510-1.shtml 2012年之后 苹果就不在开发 window ...

  5. 2022 倒带 - NutUI

    作者:京东零售 于明明 前言 时光飞逝,流年似水,让我们倒带 2022,回首这跌宕起伏一年走过的 "升级之路". NutUI 表现如何? 成绩单等着您打分! 2022 是 NutU ...

  6. fasthttp 中如何使用 linux 系统调用 `sendfile`

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 接上一篇:fasthttp 中如何使用Transfer-E ...

  7. [P9253 [PA 2022] Ornitolog 2] 题解

    题目 P9253 [PA 2022] Ornitolog 2 题目描述 给你一个长度为 \(n\) 的数列,求至少要修改多少个数才能让数列成为交替鹡鸰鸟鸣的音高序列. 思路 这道题有两种情况. 第一种 ...

  8. 【三】分布式训练---单机多卡与多机多卡组网(飞桨paddle2.0+)更加推荐spawn方式!

    1. 单机多卡启动并行训练 飞桨2.0增加paddle.distributed.spawn函数来启动单机多卡训练,同时原有的paddle.distributed.launch的方式依然保留. padd ...

  9. 【深度学习项目五】:利用LSTM网络进行情感分析(NLP)

    相关文章: [深度学习项目一]全连接神经网络实现mnist数字识别](https://blog.csdn.net/sinat_39620217/article/details/116749255?sp ...

  10. C/C++ 发送与接收HTTP/S请求

    HTTP(Hypertext Transfer Protocol)是一种用于传输超文本的协议.它是一种无状态的.应用层的协议,用于在计算机之间传输超文本文档,通常在 Web 浏览器和 Web 服务器之 ...