在上一篇文章中,我们学习了 RAG 的数据准备工作,其中包括数据导入、数据准备和分块。
由于我们需要在 RAG 中搜索相关的上下文块,因此我们必须将数据从文本格式转换为向量嵌入。
因此,我们将探索通过句子转换器转换文本的最有效方法。
嵌入模型
嵌入是一种单词表示法(使用数字向量),可以让含义相似的单词具有相似的表示法。
这些向量可以通过各种机器学习算法和大型文本数据集来学习。词嵌入的主要作用之一是为文本分类和信息检索等下游任务提供输入特征。
在过去的十年中,人们提出了几种单词嵌入方法,以下是其中的几种。
独立于上下文的嵌入
独立于上下文的嵌入重新定义了单词表示法,无论上下文如何变化,都能分配唯一的向量。本文将重点探讨其对同义词消歧的影响。
与上下文无关的嵌入式技术提供了高效率,但对细微的语言理解,尤其是同音异义词的理解提出了挑战。这种模式的转变促使我们更仔细地研究自然语言处理中的权衡问题。
一些常见的基于频率的上下文无关嵌入:
词袋会将所有句子中最常见的词编成词典,然后对句子进行编码,如下图所示。
TF-IDF 是一种从句子中查找特征的简单技术。在计数特征中,我们对文档中的所有单词/词组进行计数,而在 TFIDF 中,我们只对重要的单词进行计数。我们如何做到这一点呢?如果你想到语料库中的一篇文档,我们将考虑该文档中任何单词的两个方面:
一些常见的基于预测的上下文无关嵌入:
Word2Vec 中的单词嵌入是通过双层神经网络学习的,在训练过程中会不经意地捕捉语言上下文。嵌入是算法主要目标的副产品,展示了这种方法的效率。Word2Vec 通过两种不同的模型架构提供灵活性: CBOW 和连续跳格。
连续词袋(CBOW):
连续跳格:
Word2Vec 的双模型架构在捕捉语言细微差别方面具有多功能性,允许从业人员根据自然语言处理任务的具体要求在 CBOW 和连续跳格之间做出选择。了解这些架构之间的相互作用,可以增强 Word2Vec 在不同语境中的应用。
上下文相关的嵌入
上下文相关方法可根据上下文为同一个词学习不同的嵌入。
基于 RNN
基于变换器
BERT
谷歌人工智能公司开发的自然语言处理利器 BERT(双向变换编码器表示法)重塑了语言模型的格局。将深入探讨预训练方法及其双向架构的复杂性。
BERT 的架构由多个编码器层组成,每个编码器层对输入进行自我关注,并将其传递给下一层。即使是最小的变体 BERT BASE,也拥有 12 个编码器层、一个包含 768 个隐藏单元的前馈神经网络块和 12 个注意力头。
输入表示法
BERT 将由句子或句子对(如 < 问题,答案>)组成的序列作为输入序列,这些序列由一个标记序列组成,用于问题解答任务。
在将输入序列输入模型之前,先使用 WordPiece Tokenizer(词汇量为 30k 的标记化器)进行准备。它的工作原理是将一个词拆分成几个子词(标记)。
特殊标记包括:
我们引入了分段嵌入技术,以指示给定标记属于第一句还是第二句。位置嵌入则表示标记在句子中的位置。与最初的转换器相比,BERT 从绝对序数位置学习位置嵌入,而不是使用三角函数。
为了获得标记嵌入,嵌入层使用了一个嵌入查找表(如上图所示),其中行代表词汇表中所有可能的标记 ID(例如 30k 行),列代表标记嵌入的大小。
为什么是句子 BERT(S-BERT)而不是 BERT?
到目前为止,效果还不错,但这些转换器模型在构建句子向量时存在一个问题: 转换器使用单词或标记级嵌入,而不是句子级嵌入。
在使用句子转换器之前,使用 BERT 计算准确句子相似性的方法是使用交叉编码器结构。这意味着,我们会将两个句子传递给 BERT,在 BERT 的顶部添加一个分类头,并以此输出相似度得分。
BERT 交叉编码器结构由一个 BERT 模型组成,该模型处理句子 A 和 B。两者都以相同的顺序进行处理,并由[SEP]标记分隔。所有这些之后都是一个输出相似性分数的前馈神经网络分类器。
交叉编码器网络确实能产生非常准确的相似性得分(比 SBERT 更好),但它不具备可扩展性。如果我们想对一个 10 万个句子的小型数据集进行相似性搜索,就需要完成 10 万次交叉编码器推理计算。
为了对句子进行聚类,我们需要对 10 万个数据集中的所有句子进行比较,这样就需要进行近 5 亿次比较--这根本不现实。
理想情况下,我们需要预先计算句子向量,然后将其存储起来,在需要时使用。如果这些向量表示良好,我们所需要做的就是计算每个向量之间的余弦相似度。利用原始的 BERT(和其他转换器),我们可以通过平均 BERT 输出的所有标记嵌入值来构建句子嵌入(如果输入 512 个标记,则输出 512 个嵌入)。
或者,我们可以使用第一个 [CLS] 标记(BERT 特有的标记,其输出嵌入用于分类任务)的输出。
使用这两种方法中的一种,我们可以更快地存储和比较句子嵌入,将搜索时间从 65 小时缩短到 5 秒左右。但是,准确率并不高,比使用平均 GloVe 嵌入(2014 年开发)的准确率还低。
因此,使用 BERT 从 10K 个句子中找出最相似的句子对需要 65 个小时。而使用 SBERT,只需 5 秒钟就能创建嵌入模型,用余弦相似度进行比较只需 0.01 秒钟。
自 SBERT 论文发表以来,已经有更多的句子转换器模型利用训练原始 SBERT 的类似概念建立起来。它们都是在许多相似和不相似的句子对上训练出来的。
这些模型使用损失函数(如 softmax 损失、多重否定排序损失或 MSE margin 损失)进行优化,为相似句子生成相似的嵌入,反之则生成不相似的嵌入。
得出独立的句子嵌入是 BERT 的主要问题之一。为了解决这个问题,我们开发了 SBERT。
句子变换器
我们解释了 BERT 的句子相似性交叉编码器架构。SBERT 与之类似,但去掉了最后的分类头,而是一次处理一个句子。然后,SBERT 在最终输出层上使用均值池生成句子嵌入。
与 BERT 不同,SBERT 采用连体结构对句子对进行微调。我们可以将其理解为并行运行两个完全相同的 BERT,它们共享完全相同的网络权重。
实际上,我们使用的是一个 BERT 模型。不过,由于我们在训练过程中将句子 A 和句子 B 作为成对处理,因此更容易将其视为权重相同的两个模型。
连体 BERT 预训练
句子变换器的训练方法多种多样。我们将介绍在最初的 SBERT 论文中最突出的原始过程,该过程在 softmax-loss 上进行了优化。
softmax-loss 方法使用的是在斯坦福自然语言推理(SNLI)和多流派 NLI(MNLI)语料库上进行微调的 "siamese "架构。
SNLI 包含 570K 个句子对,MNLI 包含 430K 个句子对。这两个语料库中的句对都包含前提和假设。每对句子都有三种标签:
根据这些数据,我们将句子 A(假设前提)输入siamese BERT A,将句子 B(假设)输入siamese BERT B。
siamese BERT 输出我们的集合句子嵌入。在 SBERT 论文中,有三种不同的集合方法。它们分别是均值池法、最大值池法和 [CLS] 池法。在 NLI 和 STSb 数据集上,均值池方法的性能最好。
现在有两种句子嵌入。我们将嵌入式 A 称为 u,将嵌入式 B 称为 v。下一步是连接 u 和 v:
|u-v| 计算出两个向量的元素差。除了原始的两个嵌入向量(u 和 v),这些向量都会被输入一个前馈神经网络(FFNN),该网络有三个输出。
这三个输出与我们的 NLI 相似性标签 0、1 和 2 一致。我们需要计算 FFNN 的 softmax,这是在交叉熵损失函数中完成的。softmax和标签用于优化 "softmax-loss"。
请注意,softmax-loss 指的是交叉熵损失(默认情况下包含 softmax 函数)。
这将导致相似句子(标签 0)的集合句子嵌入变得更加相似,而不相似句子(标签 2)的嵌入变得不那么相似。
请记住,我们使用的是连体 BERT,而不是双 BERT。也就是说,我们使用的不是两个独立的 BERT 模型,而是一个处理句子 A 和句子 B 的 BERT。
这就意味着,当我们优化模型权重时,权重会被推向一个方向,让模型输出更多相似向量,我们会看到一个包含标签,而输出更多不相似向量,我们会看到一个矛盾标签。
SBERT 目标函数
通过使用这两个向量 u 和 v,下面将讨论优化不同目标的三种方法。
分类
将三个向量 u、v 和 |u-v| 连接起来,乘以可训练的权重矩阵 W,然后将乘法结果输入 softmax 分类器,该分类器会输出不同类别句子的归一化概率。交叉熵损失函数用于更新模型的权重。
回归
在这种方法中,得到向量 u 和 v 后,直接通过选定的相似度量来计算它们之间的相似度得分。将预测的相似度得分与真实值进行比较,然后使用 MSE 损失函数更新模型。
三重损失
三元组目标引入了三元组损失,该损失是根据三个句子计算得出的,这三个句子通常被称为锚句子、正句子和负句子。假定锚句子和正句子非常接近,而锚句子和负句子非常不同。在训练过程中,模型会评估句子对(锚句、正句)与句子对(锚句、负句)的接近程度。
现在,让我们来看看如何初始化和使用这些句子转换器模型。
句子转换器实践
开始使用句子转换器的最快、最简单的方法是通过 SBERT 创建者创建的句子转换器库。我们可以用 pip 安装它。
!pip install sentence-transformers
我们将从最初的 SBERT 模型 bert-base-nli-mean-tokens 开始。首先,我们下载并初始化模型。
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('bert-base-nli-mean-tokens')
model
输出:
SentenceTransformer(
(0): Transformer({'max_seq_length': 128, 'do_lower_case': False}) with Transformer model: BertModel
(1): Pooling({'word_embedding_dimension': 768, 'pooling_mode_cls_token': False, 'pooling_mode_mean_tokens': True, 'pooling_mode_max_tokens': False, 'pooling_mode_mean_sqrt_len_tokens': False})
)
我们在这里看到的输出是 SentenceTransformer 对象,它包含三个部分:
有了模型后,我们就可以使用编码方法快速构建句子嵌入。
sentences = [
"the fifty mannequin heads floating in the pool kind of freaked them out",
"she swore she just saw her sushi move",
"he embraced his new life as an eggplant",
"my dentist tells me that chewing bricks is very bad for your teeth",
"the dental specialist recommended an immediate stop to flossing with construction materials"
]
embeddings = model.encode(sentences)
embeddings.shape
输出:
(5, 768)
选择哪种嵌入模型?
我们很快就会发现,目前使用的大多数嵌入模型都属于转换器类别。这些模型由不同的供应商提供,有些是开源的,有些是专有的,每种模型都是针对特定目标量身定制的:
最简单的方法是利用现有的学术基准。不过,需要注意的是,这些基准可能无法全面反映人工智能应用中检索系统的实际使用情况。
另外,你也可以使用各种嵌入模型进行测试,并编制最终评估表,以确定最适合你的特定用例的模型。我强烈建议在此过程中加入重新排序器,因为它可以显著提高检索器的性能,最终获得最佳结果。
为了简化你的决策过程,Hugging Face 提供了卓越的大容量文本嵌入基准(MTEB)排行榜。该资源提供了有关所有可用嵌入模型及其各自在各种指标上得分的全面信息:
如果你选择第二种方法,Medium 上有一篇出色的博文介绍了如何利用 LlamaIndex 的检索评估模块。该资源可帮助你从初始模型列表中有效评估并确定嵌入和重定位器的最佳组合。
我相信,你现在已经具备了更好的能力,可以在为你的 RAG 架构选择最合适的嵌入和重排模型时做出明智的决定!
总结
本文探讨了用于生成文本向量表示的各种嵌入模型,包括词袋、TF-IDF、Word2Vec、GloVe、FastText、ELMO、BERT 等。报告深入探讨了 BERT 的架构和预训练,介绍了用于高效句子嵌入的句子 BERT (SBERT),并提供了一个使用句子转换器库的实践示例。结论强调了选择正确的嵌入模型所面临的挑战,并建议利用 Hugging Face Massive Text Embedding Benchmark (MTEB) Leaderboard 等资源进行评估。