Rerank

即重新排序,是信息检索、推荐系统、自然语言处理和机器学习领域中的一种常见技术。它的目的是提高搜索结果、推荐列表或生成内容的相关性,使其更符合用户的需求或期望。

在RAG里,我们主要利用Rerank 模型来对初步检索出的文档(比如通过BM25或者向量检索召回的文档)进行重排序过滤


支持一种语言的嵌入模型

  • 英语 bge-large-en
  • 中文 bge-large-zh

支持双语的嵌入模型

  • 网易有道 BCEmbedding

网易有道 BCEmbedding

优势

  • 双语和跨语种能力:基于有道翻译引擎的强大能力,BCEmbedding实现强大的中英双语和跨语种语义表征能力。
  • RAG适配:面向RAG做针对性优化,可适配大多数相关任务,比如翻译,摘要,问答等。此外,针对 问题理解(query understanding) 也做了针对优化
  • 高效且精确的语义检索EmbeddingModel采用双编码器,可以在第一阶段实现高效的语义检索。RerankerModel采用交叉编码器,可以在第二阶段实现更高精度的语义顺序精排。
  • 更好的领域泛化性:为了在更多场景实现更好的效果,我们收集了多种多样的领域数据。
  • 用户友好:语义检索时不需要特殊指令前缀。也就是,你不需要为各种任务绞尽脑汁设计指令前缀。
  • 有意义的重排序分数RerankerModel可以提供有意义的语义相关性分数(不仅仅是排序),可以用于过滤无意义文本片段,提高大模型生成效果。
  • 产品化检验BCEmbedding已经被有道众多产品检验。

模型列表

https://github.com/netease-youdao/BCEmbedding/blob/master/README_zh.md#-%E6%A8%A1%E5%9E%8B%E5%88%97%E8%A1%A8

模型名称 模型类型 支持语种 参数量 开源权重
bce-embedding-base_v1 EmbeddingModel 中英 279M Huggingface(推荐), 国内通道, ModelScope, WiseModel
bce-reranker-base_v1 RerankerModel 中英日韩 279M Huggingface(推荐), 国内通道, ModelScope, WiseModel

模型下载

嵌入模型:https://www.modelscope.cn/models/maidalun/bce-embedding-base_v1/files

1
git clone https://www.modelscope.cn/maidalun/bce-embedding-base_v1.git

Rerank 模型:https://www.modelscope.cn/models/maidalun/bce-reranker-base_v1/summary

1
git clone https://www.modelscope.cn/maidalun/bce-reranker-base_v1.git

模型性能:

image-20240627102045642

关键参数

  • RerankerModel支持 长 passage(超过512 tokens,不超过32k tokens)rerank
  • RerankerModel可以给出有意义 相关性分数区别于cosine分数),帮助 过滤低质量召回
  • EmbeddingModel 768 维度

安装 BCEmbedding

1
pip install BCEmbedding

理解Rerank 和 cosine 语义相似 的不同

问题:你最喜欢的电影是什么? 和下面这些回答的cosine语义相似度

bce-embedding-base_v1 和 bge_large_zh 结果一致

1
2
3
4
5
6
7
8
9
10
[1] 我最喜欢的电影是《阿甘正传》。        Score:0.6105921907574262
[2] 我最喜欢的电影类型是恐怖片。 Score:0.5381861816884799
[3] 我最喜欢的电影导演是斯皮尔伯格。 Score:0.4905046919817628
[4] 我最喜欢的书是《哈利波特》。 Score:0.4652198126696215
[5] 我不喜欢看电影。 Score:0.44154025662995605
[6] 我喜欢看科幻电影,尤其是《星际穿越》。 Score:0.4249389530579074
[7] 我最喜欢的音乐是爵士乐。 Score:0.39165986473334957
[8] 昨天晚上我看了一部很好的电影。 Score:0.35591581297344105
[9] 我最喜欢的运动是篮球。 Score:0.3554765233404198
[10] 我喜欢吃披萨。 Score:0.34989368275228544

经过re-rank,效果非常明显!

1
2
3
4
5
6
7
8
9
10
我最喜欢的电影是《阿甘正传》。 [1]
我最喜欢的电影类型是恐怖片 [2]
我喜欢看科幻电影,尤其是《星际穿越》。 [6]
我最喜欢的电影导演是斯皮尔伯格。 [3]
昨天晚上我看了一部很好的电影。 [8]
我最喜欢的书是《哈利波特》。 [4]
我不喜欢看电影。 [5]
我最喜欢的音乐是爵士乐。 [7]
我喜欢吃披萨。 [10]
我最喜欢的运动是篮球。 [9]

嵌入模型使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 初始化本地的 embedding 模块
embedding_model_name = 'D:\LLM\\bce_modesl\\bce-embedding-base_v1'
# 使用 GPU
# embedding_model_kwargs = {'device': 'cuda:0'}

# 使用 CPU
embedding_model_kwargs = {'device': 'cpu'}

embedding_encode_kwargs = {'batch_size': 32, 'normalize_embeddings': True, }

# 初始化
embed_model = HuggingFaceEmbeddings(
model_name=embedding_model_name,
model_kwargs=embedding_model_kwargs,
encode_kwargs=embedding_encode_kwargs
)
# 小测试
query = '你好呀兄弟!'
passages = ['天真热', '嗯呢']
query_embedding = embed_model.embed_query(query)
passages_embeddings = embed_model.embed_documents(passages)

Rerank 模型使用方法

非langchain 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
query = '你最喜欢的电影是什么?'
passages = [
'我最喜欢的电影是《阿甘正传》。',
'我最喜欢的电影类型是恐怖片',
'我最喜欢的电影导演是斯皮尔伯格。',
'我最喜欢的书是《哈利波特》。',
'我不喜欢看电影。',
'我喜欢看科幻电影,尤其是《星际穿越》。',
'我最喜欢的音乐是爵士乐。',
'昨天晚上我看了一部很好的电影。',
'我最喜欢的运动是篮球。',
'我喜欢吃披萨。'
]

# construct sentence pairs
sentence_pairs = [[query, passage] for passage in passages]

# rerank 模型所在目录
reranker_model = 'D:\LLM\\bce_modesl\\bce-reranker-base_v1'

# 初始化
model = RerankerModel(model_name_or_path=reranker_model)

# 得到模型评估的分数
scores = model.compute_score(sentence_pairs)

# rerank 过滤
rerank_results = model.rerank(query, passages)
doc_list = rerank_results['rerank_passages']
for line in doc_list:
print(line)
pass

langchain 调用方法: 可以作为compressor直接传给ContextualCompressionRetriever

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 定义 reranker
reranker_args = {'model': 'D:\LLM\\bce_modesl\\bce-reranker-base_v1', 'top_n': 10, 'device': 'cuda:0'}
reranker = BCERerank(**reranker_args)

# 生成一个langchain 的语境压缩检索器
compression_retriever = ContextualCompressionRetriever(
base_compressor=reranker, base_retriever=retriever
)
# 搜索
docs = compression_retriever.get_relevant_documents('你最喜欢的电影是什么?')

# ------- 一条龙处理

# 文档分割器
text_splitter = RecursiveCharacterTextSplitter(chunk_size=400)

# 向量过滤
embedding_filter = EmbeddingsFilter(embeddings=embed_model, similarity_threshold=0.6)

# 文档去重
redundant_filter = EmbeddingsRedundantFilter(embeddings=embed_model)

# 内容抽取
llm_extractor = LLMChainExtractor.from_llm(qianfan_chat)

pipeline_compressor = DocumentCompressorPipeline(
transformers=[text_splitter, embedding_filter, redundant_filter, reranker, llm_extractor]
)
pipeline_retriever = ContextualCompressionRetriever(
base_compressor=pipeline_compressor, base_retriever=retriever
)
docs = pipeline_retriever.get_relevant_documents(query)

召回了20个文档,经过 分割向量过滤文档去重rerank,只有3个文档到了 信息抽取 环节

image-20240627102059130

相关代码

  • 测试数据放到数据库
  • langchain中rerank 查询
  • 非langchain 调用方法

测试数据放到数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import os
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.chat_models import QianfanChatEndpoint
from langchain_community.document_loaders import TextLoader, DirectoryLoader
from langchain_community.embeddings import QianfanEmbeddingsEndpoint, HuggingFaceEmbeddings
from langchain import hub
from langchain_community.vectorstores.elasticsearch import ElasticsearchStore
from langchain_core.documents import Document
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

import numpy as np


def cosine_similarity(vec1, vec2):
dot_product = np.dot(vec1, vec2)
norm_vec1 = np.linalg.norm(vec1)
norm_vec2 = np.linalg.norm(vec2)
return dot_product / (norm_vec1 * norm_vec2)


if __name__ == '__main__':

# 使用本地网易有道模型 BCE
embedding_model_name = 'D:\LLM\\bce_modesl\\bce-embedding-base_v1'
embedding_model_kwargs = {'device': 'cuda:0'}
embedding_encode_kwargs = {'batch_size': 32, 'normalize_embeddings': True, }

embed_model = HuggingFaceEmbeddings(
model_name=embedding_model_name,
model_kwargs=embedding_model_kwargs,
encode_kwargs=embedding_encode_kwargs
)

doc_list = [
'我最喜欢的电影是《阿甘正传》。',
'我最喜欢的电影类型是恐怖片',
'我最喜欢的电影导演是斯皮尔伯格。',
'我最喜欢的书是《哈利波特》。',
'我不喜欢看电影。',
'我喜欢看科幻电影,尤其是《星际穿越》。',
'我最喜欢的音乐是爵士乐。',
'昨天晚上我看了一部很好的电影。',
'我最喜欢的运动是篮球。',
'我喜欢吃披萨。'
]
docs = []
index =1
for line in doc_list:
tmp_doc = Document(page_content=line)
tmp_doc.metadata['num'] = index
index += 1
docs.append(tmp_doc)
pass

vectorstore = ElasticsearchStore(
es_url=os.environ['ELASTIC_HOST_HTTP'],
index_name="index_ex_768_vectors",
embedding=embed_model,
es_user="elastic",
vector_query_field='question_vectors',
es_password=os.environ['ELASTIC_ACCESS_PASSWORD']
)
# 将本文加入数据库(注意,只需要运行一次;多次运行会有冗余数据)
vectorstore.add_documents(docs)
print('Done!')

langchain中rerank 查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import os
from uuid import uuid4

from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import DocumentCompressorPipeline, EmbeddingsFilter, LLMChainExtractor
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.chat_models import QianfanChatEndpoint
from langchain_community.document_loaders import TextLoader, DirectoryLoader
from langchain_community.document_transformers import EmbeddingsRedundantFilter
from langchain_community.embeddings import QianfanEmbeddingsEndpoint, HuggingFaceEmbeddings
from langchain import hub
from langchain_community.vectorstores.elasticsearch import ElasticsearchStore
from langchain_core.documents import Document
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from BCEmbedding.tools.langchain import BCERerank
import numpy as np

os.environ["QIANFAN_ACCESS_KEY"] = os.getenv('MY_QIANFAN_ACCESS_KEY')
os.environ["QIANFAN_SECRET_KEY"] = os.getenv('MY_QIANFAN_SECRET_KEY')

# 千帆 chatModel
qianfan_chat = QianfanChatEndpoint(
model="ERNIE-Bot-4"
)

# Langsmith 配置,不用可注掉
unique_id = uuid4().hex[0:8]
os.environ["LANGCHAIN_PROJECT"] = f" [一条龙] qianfan Tracing Walkthrough - {unique_id}"
# os.environ["LANGCHAIN_TRACING_V2"] = 'true'
os.environ["LANGCHAIN_API_KEY"] = os.getenv('MY_LANGCHAIN_API_KEY')

if __name__ == '__main__':

# 使用本地网易有道模型 BCE, 使用CPU
embedding_model_name = 'D:\LLM\\bce_modesl\\bce-embedding-base_v1'
embedding_model_kwargs = {'device': 'cpu'}
embedding_encode_kwargs = {'batch_size': 32, 'normalize_embeddings': True, }

embed_model = HuggingFaceEmbeddings(
model_name=embedding_model_name,
model_kwargs=embedding_model_kwargs,
encode_kwargs=embedding_encode_kwargs
)

vectorstore = ElasticsearchStore(
es_url=os.environ['ELASTIC_HOST_HTTP'],
index_name="index_ex_768_vectors",
embedding=embed_model,
es_user="elastic",
vector_query_field='question_vectors',
es_password=os.environ['ELASTIC_ACCESS_PASSWORD']
)

retriever = vectorstore.as_retriever(search_kwargs={"k": 20})

query = '粥余知识库'
# 使用GPU
reranker_args = {'model': 'D:\LLM\\bce_modesl\\bce-reranker-base_v1', 'top_n': 10, 'device': 'cuda:0'}
reranker = BCERerank(**reranker_args)

compression_retriever = ContextualCompressionRetriever(
base_compressor=reranker, base_retriever=retriever
)

# rerank 过滤文档
# docs = compression_retriever.get_relevant_documents(query)

# 文档分割器
text_splitter = RecursiveCharacterTextSplitter(chunk_size=400)

# 向量过滤
embedding_filter = EmbeddingsFilter(embeddings=embed_model, similarity_threshold=0.6)

# 文档去重
redundant_filter = EmbeddingsRedundantFilter(embeddings=embed_model)

# 内容抽取
llm_extractor = LLMChainExtractor.from_llm(qianfan_chat)

pipeline_compressor = DocumentCompressorPipeline(
transformers=[text_splitter, embedding_filter, redundant_filter, reranker, llm_extractor]
)
pipeline_retriever = ContextualCompressionRetriever(
base_compressor=pipeline_compressor, base_retriever=retriever
)
docs = pipeline_retriever.get_relevant_documents(query)
pass

非langchain 调用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from BCEmbedding import RerankerModel, EmbeddingModel

if __name__ == '__main__':

# 测试嵌入模块
embedding_model_name = 'D:\LLM\\bce_modesl\\bce-embedding-base_v1'

# list of sentences
sentences = ['哈喽啊兄弟', '明天去上学了']

# init embedding model
model = EmbeddingModel(model_name_or_path=embedding_model_name)

# extract embeddings
# embeddings = model.encode(sentences)
# pass

# your query and corresponding passages
query = '你最喜欢的电影是什么?'
passages = [
'我最喜欢的电影是《阿甘正传》。',
'我最喜欢的电影类型是恐怖片',
'我最喜欢的电影导演是斯皮尔伯格。',
'我最喜欢的书是《哈利波特》。',
'我不喜欢看电影。',
'我喜欢看科幻电影,尤其是《星际穿越》。',
'我最喜欢的音乐是爵士乐。',
'昨天晚上我看了一部很好的电影。',
'我最喜欢的运动是篮球。',
'我喜欢吃披萨。'
]

# construct sentence pairs
sentence_pairs = [[query, passage] for passage in passages]
reranker_model = 'D:\LLM\\bce_modesl\\bce-reranker-base_v1'
# init reranker model
model = RerankerModel(model_name_or_path=reranker_model)

# method 0: calculate scores of sentence pairs
scores = model.compute_score(sentence_pairs)

# method 1: rerank passages
rerank_results = model.rerank(query, passages)
doc_list = rerank_results['rerank_passages']
for line in doc_list:
print(line)
pass

其实如果要达到一个好的效果,还是需要根据使用场景去选择适合的ReRank和embedding