句子嵌入(sentence embeddings)正如单词嵌入是单词的向量表示一样,句子嵌入也是句子的向量表示。我们还可以计算段落和文档的嵌入!
让我们一起来了解一下。
我们可以采用三种方法:[CLS] 池法Pooling、最大池法和均值池法。
- 均值池法是指平均计算句子中的所有单词嵌入。
- 最大值池法是指取词嵌入的每个维度的最大值。
- [CLS]池化是指使用与[CLS]标记相对应的嵌入作为句子嵌入。
让我们深入了解一下最后一种最不直观的方法。
[CLS] 池化Pooling
如前所述,BERT 在句子开头添加了一个特殊标记 [CLS]。这个标记用来表示整个句子。例如,当有人想对 BERT 模型进行微调以执行文本分类时,常见的方法是在 [CLS] 嵌入的基础上添加一个线性层。我们的想法是,[CLS] 标记将捕捉整个句子的意思。
我们可以采用同样的方法,将 [CLS] 标记的嵌入作为句子嵌入。让我们看看代码中是如何实现的。我们将使用与之前相同的句子。
encoded_input = tokenizer("This is an example sentence", return_tensors="pt") |
输出:torch.Size([1, 768])
好极了!我们获得了模型输出的第一个嵌入,与 [CLS] 标记相对应。让我们把这段代码封装成一个函数。
def cls_pooling(model_output): |
embeddings = [get_sentence_embedding(sentence) for sentence in sentences] |
输出
tensor([[0.9261]]) The weather today is beautiful
tensor([[0.8903]]) It's raining!
tensor([[0.9317]]) Dogs are awesome
嗯......这里看起来有点不对劲 ,人们本以为这东西开箱就能用。
事实证明,BERT 还有一个额外的技巧。如前所述,在训练 BERT 时,CLS 标记被用来预测两个句子是否连续。为此,BERT 处理[CLS]对应的嵌入,并通过线性层和 tanh 激活函数(代码见此处code here)。这样做的目的是让线性层和 tanh 激活函数能更好地表示 [CLS] 标记。这是 BERT 模型的池器组件,用于获取 model_output.pooler_output。
这听起来可能令人困惑,让我们重复一下这里发生的事情。
- BERT 输出每个标记的嵌入。
- 第一个嵌入对应于 [CLS] 标记。
- 通过线性层和 tanh 激活函数处理 [CLS] 标记,得到pooler_output 。
在训练过程中,pooler_output用于预测两个句子是否连续(BERT 的预训练任务之一)。这使得对 [CLS] 标记的处理比原始的 [CLS] 嵌入更有意义。
为了证明这里并没有发生什么神奇的事情,我们可以将单词嵌入列表传递给 model.pooler,或者直接从模型输出中获取 pooler_output。让我们试试看!
model.pooler(model_output["last_hidden_state"])[0][:10] |
tensor([-0.9302, -0.4884, -0.4387, 0.8024, 0.3668, -0.3349, 0.9438, 0.3593,
-0.3216, -1.0000], grad_fn=
model_output["pooler_output"][0][:10] |
tensor([-0.9302, -0.4884, -0.4387, 0.8024, 0.3668, -0.3349, 0.9438, 0.3593,
-0.3216, -1.0000], grad_fn=
可以看到,嵌入的前十个元素是相同的!
现在,让我们用这种新的嵌入技术重新计算距离:
def cls_pooling(model_output): |
tensor([[0.9673]], grad_fn=
tensor([[0.9029]], grad_fn=
tensor([[0.8930]], grad_fn=
好多了,好多了!我们刚刚获得了最接近 "Today is a sunny day "的句子。
句子变形Transformers
使用transformers 库
[CLS] 标记没有被训练成一个好的句子嵌入。它被训练成一个很好的句子嵌入来预测下一个句子!
Sentenve Sentence Transformers(也称为 SBERT)拥有一种特殊的训练技术,专注于产生高质量的句子嵌入。
我们使用全 MiniLM-L6-v2模型。一开始,我们使用了该sentence-transformers库,它是围绕transformers. 让我们先尝试走难的路吧!流程如下:
- 我们对输入句子进行标记。
- 我们通过模型处理令牌。
- 我们计算令牌嵌入的平均值。
- 我们对嵌入进行归一化以确保嵌入向量具有单位长度。
就像以前一样,我们可以加载模型和分词器,对句子进行分词并将其传递给模型
tokenizer = AutoTokenizer.from_pretrained("sentence-transformers/all-MiniLM-L6-v2") |
到目前为止,我们所做的与之前所做的非常相似,只是我们使用了不同的模型。
下一步是进行池化。虽然之前我们做了 [CLS] 池化,但句子转换器通常使用均值或最大池化。我们来尝试一下吧!
token_embeddings = model_output["last_hidden_state"] |
输出:torch.Size([1, 384])
最后一步是执行标准化。归一化确保嵌入向量具有单位长度,这意味着其长度(或幅度)为 1。
当我们想要比较向量时,这特别有用。例如,如果我们想计算两个向量之间的余弦相似度,我们通常会比较它们的方向而不是它们的大小。对向量进行归一化可确保每个向量对相似性的贡献相同。我们很快就会详细讨论嵌入比较!我们来尝试一下吧!
更多点击标题
余弦相似度
到目前为止,我们一直在计算嵌入之间的余弦相似度。这是一个介于 0 和 1 之间的数字,表示两个嵌入的相似程度。值 1 表示嵌入相同,而 0 表示嵌入完全不同。到目前为止,我们已经将它用作黑匣子,所以让我们进一步研究一下它。
余弦相似度使我们能够比较两个向量的相似程度,无论它们的大小如何。例如,如果我们有两个向量,[1,2,3]和[2,4,6],它们在方向上非常相似,但它们的大小不同。余弦相似度会接近1,表明它们非常相似。
更多点击标题
选择和评估模型
您应该对句子嵌入以及我们可以用它们做什么有很好的理解。今天,我们使用了两种不同的模型,all-MiniLM-L6-v2和quora-distilbert-multilingual。我们如何知道该使用哪一个?我们如何知道一个模型好不好?
第一步是知道在哪里发现句子嵌入模型。如果您使用开源的,Hugging Face Hub 允许您过滤它们。社区已分享超过 4000 个模型!虽然查看 Hugging Face 上的趋势模型是一个很好的指标(例如,我可以看到 Microsoft Multilingual 5 Large 模型,一个不错的模型),但我们需要更多信息来选择模型。
(https://huggingface.co/spaces/mteb/leaderboard)为我们提供了帮助。该排行榜包含针对各种任务的多个评估数据集。让我们快速了解一下选择模型时我们感兴趣的一些标准。
- 序列长度。如前所述,您可能需要根据预期的用户输入对更长的序列进行编码。例如,如果您要对长文档进行编码,则可能需要使用具有较大序列长度的模型。另一种选择是将文档拆分为多个句子并单独对每个句子进行编码。
- 语言。排行榜主要包含英语或多语言模型,但您也可以找到其他语言的模型,例如中文、波兰语、丹麦语、瑞典语、德语等。
- 嵌入维度。如前所述,嵌入维度越大,嵌入可以捕获的信息越多。然而,较大的嵌入的计算和存储成本更高。
- 跨任务的平均指标。排行榜包含多个任务,例如聚类、重新排名和检索。您可以查看所有任务的平均性能,以了解模型的性能。
- 特定于任务的指标。您还可以查看模型在特定任务中的表现。例如,如果您对聚类感兴趣,您可以查看模型在聚类任务中的表现。
了解模型的目的也很重要。有些模型将是通用模型。其他的,例如Spectre 2,则专注于特定任务,例如科学论文。我不会过多地讨论排行榜中的所有任务,但您可以查看MTEB 论文以获取更多信息。我先简单总结一下MTEB。
MTEB 提供了跨 8 个任务的 56 个数据集的基准,包含 112 种语言。可以轻松扩展将数据集和模型添加到排行榜。总的来说,它是一个简单的工具,可以为您的用例找到合适的速度与精度权衡。
今天(2024 年 1 月 7 日)的顶级模型是一个大型模型 E5-Mistral-7B-instruct,其大小为 14.22Gb,56 个数据集的平均值为 66.63。其次最好的开源模型之一是 BGE-Large-en-v1.5,它只有 1.34Gb,平均性能为 64.23。BGE 的基本模型更小 (0.44Gb),质量为 63.55!作为比较,text-embedding-ada-002 即使提供了 1536 维的更大嵌入,其性能也为 60.99。这是 MTEB 基准测试中的第 23 位!Cohere 提供了更好的嵌入,质量为 64.47,嵌入维度为 1024。
我建议查看2022 年的 Twitter 帖子,其中将 OpenAI 嵌入与其他嵌入进行了比较。结果非常有趣!与较小的型号相比,其成本高出许多数量级,并且质量也大大降低。
综上所述,不要过度关注单一数字。您应该始终查看任务的具体指标以及特定的资源和速度要求
查看 MTEB 中涵盖的不同任务以更好地了解潜在的句子嵌入应用程序很有趣。
- 比特文本挖掘。此任务涉及在两组句子中找到最相似的句子,每个句子都使用不同的语言。它对于机器翻译和跨语言搜索至关重要。
- 分类。在此应用中,使用句子嵌入来训练逻辑回归分类器以执行文本分类任务。
- 聚类。在这里,k-means 模型在句子嵌入上进行训练,将相似的句子分组在一起,这在无监督学习任务中很有用。
- 对分类。此任务需要预测一对句子是否相似,例如确定它们是否重复或释义,从而有助于释义检测。
- 重新排名。在这种情况下,参考文本列表会根据其与查询句子的相似性进行重新排序,从而改进搜索和推荐系统。
- 恢复。该应用程序涉及嵌入查询和关联文档,以查找与给定查询最相似的文档,这在搜索相关任务中至关重要。
- 语义相似性。该任务侧重于确定一对句子之间的相似度,输出连续的相似度得分,可用于释义检测和相关任务。
- 总结。这涉及通过计算一组摘要与参考(人工编写)摘要之间的相似性来对一组摘要进行评分,这在摘要评估中很重要。
生态系统状况
围绕嵌入的生态系统非常庞大。
1、构建在嵌入之上:
- 有一些很酷的工具,例如top2vec和 专bertopic为构建主题嵌入而设计。
- keybert是一个库,允许使用 BERT 嵌入提取类似于文档的关键字和关键短语。
- setfit是一个库,允许对句子转换器进行有效的几次微调,以将它们用于文本分类。
2、嵌入数据库
2023 年是嵌入数据库的一年。LangChain 集成部分显示 65 个向量存储。从 Weaviate、Pinecone 和 Chroma 到 Redis、ElasticSearch 和 Postgres。嵌入数据库专门用于加速嵌入的相似性搜索,通常使用近似搜索算法。新一波的嵌入式数据库初创公司吸引了大量资金投入其中。与此同时,现有的经典数据库公司已经将向量索引集成到他们的产品中,例如Cassandra和MongoDB。
3、研究
围绕嵌入的研究也相当活跃。如果您遵循 MTEB 基准,它每隔几周就会发生变化。其中的一些参与者包括微软(E5 模型)、Cohere、BAAI(BGE)、阿里巴巴(GTE)、香港大学 NLP 小组(讲师)和 Jina 等。