为什么我不推荐使用BGE-m3的稀疏编码

2,149 阅读4分钟

BGE-m3 是北京智源研究院(BAAI)在 2024 年初开源的一款 Embedding 模型,作为 BGE 系列模型的最新版本,可以说是当前文本转向量模型中的佼佼者。

值得一提的是,BGE 系列模型不仅开源了权重,还开放了微调、知识蒸馏等完整的技术栈。其原始论文 M3-Embedding: Multi-Linguality, Multi-Functionality, Multi-Granularity Text Embeddings Through Self-Knowledge Distillation 的标题直接揭示了 m3 的核心内涵:Multi-Linguality, Multi-Functionality, Multi-Granularity

  • Multi-Linguality:支持多语言处理,并具备跨语言能力。
  • Multi-Functionality:涵盖稠密检索、稀疏检索、多向量检索等多种功能。
  • Multi-Granularity:支持从句子、段落到篇章(最多 8192 tokens)的多粒度处理能力。

m3的含义

尽管其技术和功能令人瞩目,但在实际生产环境中,我并不推荐使用 BGE-m3 的稀疏编码。接下来,我们从多个角度探讨原因。

BGE-m3 的稀疏编码生成方法

根据论文 3.2 节 Hybrid Retrieval(混合检索)的 Lexical Retrieval(词法检索)部分,BGE-m3 的稀疏编码生成过程如下:

  1. 对输入句子的每个 token 生成 embedding。
  2. 通过全连接层和非线性层,计算出该 token 的权重。

Lexical Retrieval

这种生成方式,虽然兼顾了词法分析和深度学习特性,但在实际应用中暴露了一些问题

BGE-m3 的稀疏编码的可解释性

稀疏表示在 NLP 中实质上是一种词法编码(Lexical Representation)。我们可以通过模型的 tokenizer 来观察句子被模型切分后的表现。

以下是一个简单的分词示例:

from transformers import AutoTokenizer
BGE_M3_DIR = "your_path_to_bge-m3_model"
tokenizer = AutoTokenizer.from_pretrained(BGE_M3_DIR)

contents = [
    "据路透援引未具名消息人士的话报道",
    "出具了一份报告",
    "已经腊月二十九了,嘉靖三十九年入冬以来京师地面和邻近数省便没有下过一场雪",
    "In this paper, we introduce a new embedding model called M3-Embedding"
]
token_ids = [tokenizer.encode(content) for content in contents]
tokens = [[tokenizer.decode(_id) for _id in _ids] for _ids in token_ids]

for _ts in tokens:
    print("  ".join([f"'{t}'" for t in _ts]))

输出结果如下:

'据' '路' '透' '援' '引' '未' '具' '名' '消息' '人士' '的话' '报道'

'出' '具' '了' '一份' '报告'

'已经' '腊' '月' '二十' '九' '了' ',' '嘉' '靖' '三' '十九' '年' '入' '冬' '以来' '京' '师' '地面' '和' '邻' '近' '数' '省' '便' '没有' '下' '过' '一场' '雪'

'In' 'this' 'paper' ',' 'we' 'introduce' 'a' 'new' '' 'embe' 'dding' 'model' 'called' 'M' '3-' 'Em' 'be' 'dding'

可以看出,BGE-m3 的 tokenizer 会对部分常见词进行拆分,这种操作可能导致语义的丢失

虽然在稠密表示领域,这种切分方式通常能带来较好的效果,但在稀疏表示中,实际表现却不尽如人意。例如:

  • 对于句子据路透援引未具名消息人士的话报道出具了一份报告,从语义上看二者完全无关,但使用 BGE-m3 的稀疏表示计算相似度,却得到了 0.0266 的分值。

代码如下:

BGE_M3_DIR = "/data/embeddings/bge-m3"
model = BGEM3FlagModel(BGE_M3_DIR, use_fp16=True, device="cuda:3")

sentences_1 = ["据路透援引未具名消息人士的话报道"]
sentences_2 = ["出具了一份报告"]

output_1 = model.encode(sentences_1, return_dense=False, return_sparse=True, return_colbert_vecs=False)
output_2 = model.encode(sentences_2, return_dense=False, return_sparse=True, return_colbert_vecs=False)

lexical_scores = model.compute_lexical_matching_score(output_1['lexical_weights'][0], output_2['lexical_weights'][0])
print(lexical_scores)  # 0.026641845703125

Tokenizer 的消融实验

论文附录 C.2 节展示了采用不同 tokenizer 对稀疏表示性能的影响。目的是为了说明BGE-m3采用的稀疏表示具有更强的表达能力。

但我们这里从另一个角度分析这个消融实验的结果。对比前两行,在三个数据集上的实验对比中,同时采用 BM25 方法时,XLM-RoBERTa 的 tokenizer 明显弱于传统的 Lucene Analyzer

Tokenizer消融实验

这一消融实验结果和上面的讨论验证了一个重要结论:在稀疏表示领域,传统分词方法(如 BM25)在解释性和效果上往往优于深度学习模型的 tokenizer

总结

从编码生成方式、可解释性和消融实验三个方面,我们分析了 BGE-m3 稀疏表示的不足之处。最终建议:

  • 在实际生产环境中,优先使用传统分词方法(如 BM25)以获得更好的解释性和性能

感谢阅读如果这篇文章对你有所帮助,欢迎关注【算法工程笔记】公众号