RAG系统的召回过程中语义匹配是个非常典型的非对称检索场景。测试过Google的USE、OpenAI的Ada-002和text-embedding-3,结果都不太理想。
非对称的检索一般会有几个特点
- 长度不对称
- 查询文本(Query)通常较短,如:"如何解决游戏卡顿问题?"
- 目标文档(Document)通常较长,如:包含详细解决方案的技术文档
- 结构不对称
- 查询文本:通常是问题形式或关键词
- 目标文档:通常是完整的句子或段落
- 语言表达不对称
- 查询文本:口语化、简短、可能有错别字
- 目标文档:规范化、专业性、结构化
这就导致通用的embedding模型在处理这种情况时都会有各自的缺陷,例如文字越长相似度越低,多语言匹配度不一致等。所以当看到Jina v3提供了五种LoRA任务模式之后决定实际测试一下。
五种模式特点分析
- retrieval.query 和 retrieval.passage
这是一对互补的模式,专门为非对称检索设计
实验显示这两种模式的相似度矩阵有明显差异
- retrieval.query用于编码搜索查询
- retrieval.passage用于编码文档内容
它们的配对使用可以提高检索效果
-
separation 根据实验结果可以看出此模式在语言间区分度较高 相似语义的文本(如中文的两个样本)仍保持较高相似度 适合用于文本聚类和区分不同类别的内容
-
classification 实验结果显示对相同语言的文本有较高的相似度 对不同语言的相似内容保持中等程度的相似度 适合用于文本分类任务
-
text-matching 在实验中展现出最均衡的相似度分布 对相似语义的文本(不同语言)保持较高的相似性识别 适合直接的文本相似度比较任务
推荐使用场景
RAG中的检索场景,我建议使用 retrieval.query 和 retrieval.passage 的组合:
- 使用 retrieval.query 来编码玩家的问题
- 使用 retrieval.passage 来编码文档库中的内容片段 然后计算它们之间的相似度
原因: 这是专门为非对称检索设计的模式组合 问题和文档是典型的非对称场景(问题通常较短,文档较长) 实验结果显示这两种模式有各自的特点,配合使用效果更好 这种组合可以更好地处理查询意图和文档内容的匹配
而其他三种LoRA任务模式 separation更适合聚类场景 classification更适合分类任务 text-matching更适合对称的文本相似度比较
Jina-V3
jina-embeddings-v3是一个多语言多任务文本嵌入模型,专为各种NLP应用程序而设计。基于Jina-XLM-RoBERTa架构,该模型支持旋转位置嵌入来处理高达8192个令牌的长输入序列。此外,它还具有5个LoRA适配器,可以有效地生成特定于任务的嵌入。
jina v3官方宣称支持100中语言,对其中 阿拉伯语、孟加拉语、中文、丹麦语、荷兰语、英语、芬兰语、法语、格鲁吉亚语、德语、希腊语、印地语、印度尼西亚语、意大利语、日语、韩语、拉脱维亚语、挪威语、波兰语、葡萄牙语、罗马尼亚语、俄语、斯洛伐克语、西班牙语、瑞典语、泰语、土耳其语、乌克兰语、乌尔都语、越南语。 这30种语言进行了专门的优化。
实际测试
- 初始化模型
from transformers import AutoModel
model = AutoModel.from_pretrained("/Users/randy/Downloads/models/jinaai/jina-embeddings-v3", trust_remote_code=True)
model.to('mps')
print("模型初始化完成")
- 定义任务和语言
import seaborn as sns
import pandas as pd
import matplotlib.pyplot as plt
tasks = ['retrieval.query', 'retrieval.passage', 'separation', 'classification', 'text-matching']
texts = [
"Follow the white rabbit.", # English
"Sigue al conejo blanco.", # Spanish
"Suis le lapin blanc.", # French
"跟着白兔走。", # Chinese
"اتبع الأرنب الأبيض.", # Arabic
"Folge dem weißen Kaninchen.", # German
"在前面那只白色的兔子后面走。", # Similar Chinese
"一只白兔从你面前跑过,你好奇地跟了上去。",
"一只纯白的兔子从你面前轻盈跃过,你被它的身影吸引,不由自主地跟了上去。"
]
languages = ['English', 'Spanish', 'French', 'Chinese', 'Arabic', 'German', 'Similar', 'Similar2', 'SimilarPassage']
similarities = {}
def style_similarity_matrix(df):
# 创建样式对象
return df.style.background_gradient(
cmap='RdYlBu_r', # 使用红-黄-蓝的颜色映射,_r表示反转(使得高相似度为红色)
vmin=0,
vmax=1
).format("{:.3f}") # 保留3位小数
def plot_similarity_matrix(task_index, plt_size=(8, 6)):
df = pd.DataFrame(
similarities[tasks[0]],
index=languages,
columns=languages
)
plt.figure(figsize=plt_size)
sns.heatmap(
df,
annot=True, # 显示数值
fmt='.3f', # 数值格式(3位小数)
cmap='RdYlBu_r', # 颜色映射
vmin=0,
vmax=1,
center=0.5
)
plt.title(f'Similarity Matrix - {tasks[task_index]}')
plt.tight_layout()
plt.show()
- 计算每个任务的嵌入向量和相似度
from sklearn.metrics.pairwise import cosine_similarity
# 存储每个任务的相似度矩阵
for task in tasks:
embeddings = model.encode(texts, task=task)
sim_matrix = cosine_similarity(embeddings, embeddings)
similarities[task] = sim_matrix
- 实际效果
retrieval.query
任务retrieval.passage
任务separation
任务classification
任务text-matching
任务
- 任务模式之间的平均差异
import numpy as np
print("\n=== 不同任务模式之间的平均差异 ===")
task_diff_matrix = np.zeros((len(tasks), len(tasks)))
for i in range(len(tasks)):
for j in range(len(tasks)):
if i != j:
task1, task2 = tasks[i], tasks[j]
diff = np.abs(similarities[task1] - similarities[task2]).mean()
task_diff_matrix[i, j] = diff
# 创建任务间差异的DataFrame
df_diff = pd.DataFrame(
task_diff_matrix,
index=tasks,
columns=tasks
)
# 绘制任务间差异的热力图
plt.figure(figsize=(10, 8))
sns.heatmap(
df_diff,
annot=True, # 显示数值
fmt='.3f', # 数值格式(3位小数)
cmap='RdYlBu_r', # 颜色映射
vmin=0,
vmax=1,
center=0.5
)
plt.title('Task Differences Heatmap')
plt.tight_layout()
plt.show()