Jina-v3-embedding 最佳实践方式

398 阅读5分钟

RAG系统的召回过程中语义匹配是个非常典型的非对称检索场景。测试过Google的USE、OpenAI的Ada-002和text-embedding-3,结果都不太理想。

非对称的检索一般会有几个特点

  1. 长度不对称
    • 查询文本(Query)通常较短,如:"如何解决游戏卡顿问题?"
    • 目标文档(Document)通常较长,如:包含详细解决方案的技术文档
  2. 结构不对称
    • 查询文本:通常是问题形式或关键词
    • 目标文档:通常是完整的句子或段落
  3. 语言表达不对称
    • 查询文本:口语化、简短、可能有错别字
    • 目标文档:规范化、专业性、结构化

这就导致通用的embedding模型在处理这种情况时都会有各自的缺陷,例如文字越长相似度越低,多语言匹配度不一致等。所以当看到Jina v3提供了五种LoRA任务模式之后决定实际测试一下。

五种模式特点分析

  1. retrieval.query 和 retrieval.passage

这是一对互补的模式,专门为非对称检索设计

实验显示这两种模式的相似度矩阵有明显差异

  • retrieval.query用于编码搜索查询
  • retrieval.passage用于编码文档内容

它们的配对使用可以提高检索效果

  1. separation 根据实验结果可以看出此模式在语言间区分度较高 相似语义的文本(如中文的两个样本)仍保持较高相似度 适合用于文本聚类和区分不同类别的内容

  2. classification 实验结果显示对相同语言的文本有较高的相似度 对不同语言的相似内容保持中等程度的相似度 适合用于文本分类任务

  3. text-matching 在实验中展现出最均衡的相似度分布 对相似语义的文本(不同语言)保持较高的相似性识别 适合直接的文本相似度比较任务

推荐使用场景

RAG中的检索场景,我建议使用 retrieval.query 和 retrieval.passage 的组合:

  1. 使用 retrieval.query 来编码玩家的问题
  2. 使用 retrieval.passage 来编码文档库中的内容片段 然后计算它们之间的相似度

原因: 这是专门为非对称检索设计的模式组合 问题和文档是典型的非对称场景(问题通常较短,文档较长) 实验结果显示这两种模式有各自的特点,配合使用效果更好 这种组合可以更好地处理查询意图和文档内容的匹配

而其他三种LoRA任务模式 separation更适合聚类场景 classification更适合分类任务 text-matching更适合对称的文本相似度比较

Jina-V3

jina-embeddings-v3是一个多语言多任务文本嵌入模型,专为各种NLP应用程序而设计。基于Jina-XLM-RoBERTa架构,该模型支持旋转位置嵌入来处理高达8192个令牌的长输入序列。此外,它还具有5个LoRA适配器,可以有效地生成特定于任务的嵌入。

jina v3官方宣称支持100中语言,对其中 阿拉伯语、孟加拉语、中文、丹麦语、荷兰语、英语、芬兰语、法语、格鲁吉亚语、德语、希腊语、印地语、印度尼西亚语、意大利语、日语、韩语、拉脱维亚语、挪威语、波兰语、葡萄牙语、罗马尼亚语、俄语、斯洛伐克语、西班牙语、瑞典语、泰语、土耳其语、乌克兰语、乌尔都语、越南语。 这30种语言进行了专门的优化。

实际测试

  1. 初始化模型
from transformers import AutoModel

model = AutoModel.from_pretrained("/Users/randy/Downloads/models/jinaai/jina-embeddings-v3", trust_remote_code=True)
model.to('mps')
print("模型初始化完成")
  1. 定义任务和语言
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()
  1. 计算每个任务的嵌入向量和相似度
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
  1. 实际效果
  • retrieval.query任务 image.png
  • retrieval.passage任务 image.png
  • separation任务 image.png
  • classification任务 image.png
  • text-matching任务 image.png
  1. 任务模式之间的平均差异
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()

image.png