首页 > 教程攻略 > ai资讯 >解锁RAG应用:利用FastRAG中的Dense Retrievers优化开放域问答

解锁RAG应用:利用FastRAG中的Dense Retrievers优化开放域问答

来源:互联网 时间:2026-08-25 20:06:53

检索增强生成(RAG)近年来在自然语言处理领域声量不小。说白了,它就是把信息检索和文本生成这两项能力嫁接在一起,核心目标很简单:当用户抛出一个问题时,系统先去检索相关的信息段落,再基于这些素材生成更靠谱、更对路的回答。

解锁RAG应用:利用FastRAG中的Dense Retrievers优化开放域问答

这项技术潜力巨大,尤其在开放域问答这块。开放域问答研究的是如何构建一个能理解并回答各类问题的系统,关键一步就是从海量的非结构化数据中把有用的信息捞出来。

早期的信息检索技术,很大程度上依赖于基于单词统计的稀疏表示方法。那时候,文档通常用词袋模型来表示,一篇文档和一个查询的相关性,基本就靠关键词有没有出现来判定。

评分函数,比如我们熟悉的BM25和TF-IDF,就是通过关键词在文档里出现的频次,以及这个关键词在整个语料库里的普遍程度,来给文档打分。实践中最常用的工具之一是Elasticsearch,它基于Lucene搜索引擎,结合了单词统计和编辑距离(一种基于语法的方法)来做文本搜索。

下面这段代码展示了怎么用Python的Elasticsearch库执行基于TF-IDF和BM25的查询:

from elasticsearch import Elasticsearch
es = Elasticsearch()
index_name = "example_index"
query = "sample query"

bm25_query = {
   "query": {
       "match": {
           "content": {
               "query": query,
               "operator": "and"
           }
       }
   }
}
bm25_response = es.search(index=index_name, body=bm25_query)
print("BM25 Query Results:")
for hit in bm25_response['hits']['hits']:
   print(hit['_source'])

tfidf_query = {
   "query": {
       "match": {
           "content": {
               "query": query,
               "operator": "and"
           }
       }
   },
   "similarity": {
       "default": {
           "type": "classic"
       }
   }
}
tfidf_response = es.search(index=index_name, body=tfidf_query)
print("TF-IDF Query Results:")
for hit in tfidf_response['hits']['hits']:
   print(hit['_source'])

不过,随着机器学习技术的发展,信息检索领域也迎来了一场变革,大家纷纷转向更强大的密集嵌入表示。这些嵌入技术能精准捕捉单词和短语的深层语义,实现对内容更深层次的理解。

相比于传统的统计方法,密集表示在匹配查询和文档时准确度更高,因为它能揭示那些微妙的语义相似性——恰恰是这些相似性,传统方法往往难以触及。

可以说,从稀疏到密集的这一跃迁,不仅大幅提升了检索系统的整体性能,也让检索结果的精度上了一个台阶。

如果对神经信息检索(Neural IR)感兴趣,可以查阅Mitra和Craswell在2018年的工作;想深入了解信息检索与问答的结合,Abbasiantaeb和Momtazi在2021年的综述文章也是很好的起点。

本文的重点是密集检索,这里介绍两种最具代表性的密集文档检索模型:密集段落检索(DPR,Karpukhin等人,2020)和基于BERT的情境化后期交互(ColBERT,Khattab和Zaharia,2020)。两者都依赖语义搜索来找到相关文档。

所谓语义搜索,就是用文本的密集表示来度量查询和文档之间的相似性。不过,这两种模型在如何存储文档向量、如何计算相似度上,思路截然不同。

我们将在一个公认的基准数据集——自然问题(NQ,Kwiatkowski等人,2019)上,从准确性和延迟两个维度对它们进行比较。NQ是一个由用户提交的问题组成的集合,这些问题的答案可以在维基百科文章中找到。

我们使用的工具是FastRAG——一个由英特尔实验室开发并最近开源的框架。它的目标是加速RAG应用的研发,也就是把检索和生成结合起来,用于问答、摘要、对话系统、内容创作等任务,同时利用外部的知识库来锚定大语言模型的输出。

在FastRAG中,应用程序被抽象为一个管道,通常由知识库、检索器、排序器和阅读器(通常是一个LLM)构成。阅读器负责“阅读”查询和检索到的文档,并生成输出。开发者可以自由尝试不同的架构和模型,并对性能和延迟进行基准测试。值得一提的是,FastRAG中提供的几种模型针对英特尔硬件做了优化,能在保持相当精度的前提下,实现更低的延迟。

近期的研究推动了基于Transformer的编码器模型在检索中的应用。这些模型的优势很明显:知识库中的文档被转换成向量并存储在索引里。

查询时,查询内容被编码成同一个向量空间中的向量,然后通过向量相似性搜索来召回最相关的文档。这个过程不仅可以高效地筛选文档,还能用来对召回结果进行重排,进一步优化顺序。这样一来,在运行时就能实现更精准、更实时的检索。

在密集检索领域,主要有几种不同的方法。一种是用单个token的嵌入来代表整个文档。DPR就是这种思路的典型代表。

在DPR中,编码器被训练成把整个文档的内容“总结”到第一个token(通常是[CLS])的嵌入里。这属于双编码器架构,因为它用了两个独立的编码器:一个处理查询,一个处理文档。查询和文档各自被编码成向量,然后进行匹配。

另一种方法是后期交互(late interaction),由ColBERT首次提出(Khattab和Zaharia,2020)。它的想法是保存(并索引)文档中所有单词的编码向量。在运行时,查询向量会跟所有文档单词的向量逐一比较——所以才叫“后期”交互。这种方法通常能检索到比DPR更相关的文档。不过,为每个token都建立索引(而不是每个文档只用一个token),索引大小自然就上去了。

后续的改进版本,ColBERT v2和PLAID(Santhanam等人,2022),通过两种方式大幅压缩了索引大小、降低了延迟:一是对索引中的向量进行量化和压缩,二是引入了一套启发式方法,用K-means对向量聚类,然后基于簇的质心,分层地为查询选择相关文档的token。使用PLAID索引的ColBERT v2,在保持低延迟的同时实现了顶尖的检索性能,延迟水平已经接近BM25、Lucene等稀疏检索方法,但精度却高出一大截。

在原代码块的位置,我们来看如何在FastRAG中使用ColBERT。首先,当然得安装FastRAG:

pip install fastrag

接下来,初始化文档存储:

from fastrag.stores import PLAIDDocumentStore

document_store = PLAIDDocumentStore(
   index_path="data-index/",
   checkpoint_path="ColBERT-model/",
   collection_path="my-documents.tsv")

然后,基于这个文档存储定义一个检索器:

from fastrag.retrievers.colbert import ColBERTRetriever

retriever = ColBERTRetriever(document_store=document_store)

我们可以用Haystack的管道API来连接各个组件。下面这个例子只包含检索器:

from haystack import Pipeline

p = Pipeline()
p.add_node(component=retriever, name="Retriever", inputs=["Query"])

最后,执行查询:

p.run(query, params={"Retriever": {"top_k": k}})

结果是一个哈希映射,包含文档键和对应的结果列表,每个文档都带有相关性分数。