来源:克劳德3.5十四行诗
在大规模检索增强生成(RAG)应用迅速发展的环境中,开发者在处理长文档的检索时面对着各种难题。大量的数据和对精度的严格要求经常导致质量、成本与性能之间的权衡。这种权衡长期以来一直是希望构建高质量长文档检索系统的用户的困扰。
然而,最近的进步正在重塑我们应对这些挑战的方式。Jina AI在2023年10月发布了一个8K上下文长度的嵌入模型等,引发了关于如何在各种各样的应用中最佳利用长上下文嵌入的讨论。
虽然将包含数千词的整个文档编码成单一嵌入向量似乎很有吸引力,但这并不总是理想的。许多用例,尤其是在RAG流程中,需要检索较小部分的文本。传统方法通常是在嵌入之前将文档分割成更小的片段(通常约为512个token),因为基于密集向量的检索系统在处理这些较小片段时表现更佳。这种偏好源于这样一种担忧,即在将长上下文压缩成单一向量表示时,重要的上下文信息可能会被淡化。
但是既然分块这么普遍,为什么还要开发拥有如此长上下文长度的模型呢?答案就是Jina AI最近提出的一种新方法,叫做“Late Chunking”。这种方法旨在利用长上下文模型提供的丰富信息,同时解决更细粒度检索的需求。
当我们更深入地探讨这一主题时,我们将探讨 Late Chunking 如何可能为简单的低成本方案和更复杂且昂贵的方法(例如 ColBERT)之间提供一种中间地带。这种方法或许正是解锁 RAG 应用中长上下文检索有效性的关键,可能会在大规模信息检索系统中彻底改变我们如何平衡精度、成本和性能。
问题概述:在处理大型文档时,一个关键的挑战是如何呈现文档,以便我们之后可以高效地检索相关信息。传统方法在处理长文档时可能会丢失重要的上下文。
研究探索长上下文信息的检索方法,在进行晚期分块之前 基本的分块处理方法(Chunking)处理长文档时,常用的一个方法是“分块”,即将文档分成若干小块,方便处理。虽然这种方法确实有效,但它的主要问题在于会丢失这些块之间的上下文联系,特别是当这些块各自独立时。这可能会让信息检索的准确性下降,如下面的例子所示:
拉拉在出发探险前准备好了装备。长途跋涉几小时后,她在丛林深处发现了一处古老废墟。她走进去,发现了一些记录着古老文明的铭文。
当我们把这段文本分成句子时:
- Lara 在出发探险前已经准备好了她的装备。
- 徒步数小时后,她发现了一个隐藏在丛林深处的古老遗迹。
- 她走进去,发现了一些详细记载着一个古老文明历史的铭文。
当有人问“谁发现的这些铭文?”时,这个模型可能无法将“她”(在第三句话)和“劳拉”(在第一句话)联系起来。这是因为简单的分句处理过程独立处理这些句子,忽略了人类能够直观理解的那种句子之间的联系。
数据分块技术的优化为了解决这些限制,已经提出了几种高级的分块方法。这些方法旨在不显著增加资源需求的前提下改善上下文的保存。然而,即便如此,也无法保证完全保留上下文。
- 语义分块:这种方法不是基于词元数量进行分块,而是基于自然语言界限,如句子或段落。通过保持语义上有意义的块,它有助于保留逻辑连接。然而,它仍然可能遗漏不同块之间的长距离依赖。
- 重叠窗口:这种方法通过在连续的块之间包括一些共享的词元(例如50个词元)来重叠块。这确保了关键的上下文信息可以在块之间平滑过渡。即便如此,重叠也不能完全解决所有上下文信息丢失,特别是对于文档中长距离依赖。
- 层次化分块:这种方法在多个层次应用分块,首先将文档分解为段落,然后在每个段落内分解为句子。层次化分块旨在捕获广泛和详细的上下文信息,但与其他分块策略一样,它仍然可能遗漏跨段落的关系。
- 动态分块:在这种方法中,块的大小根据文本的复杂性进行调整。简单的部分被分组为更大的块,而复杂的部分则被划分为更小的块。这种策略提高了灵活性,但无法确保所有相关的跨块的上下文信息都能得到保留。
尽管这些技术使简单的分块更加复杂和具有上下文感知能力,但它们仍然未能完全捕捉分块之间的长距离依赖。这会导致在回答依赖于跨越多个部分的上下文的复杂查询时,检索不全面。
所以这些技术都遵循这种通用的机制。
简单分块
先分块,再嵌入。- 先分块:
- 其中,大型文档会被拆分成较小的部分,基于每个段落的单词或标记数量(例如:512、1024等标记)。
- 但这种方法的问题在于,由于在理解完整上下文之前就拆分文档,所以这些不同的文档部分之间的重要关系可能会丢失。
嵌入每个部分:
- 分块后,每个块则分别转换为数值表示(即嵌入)。
- 这种方法虽对小段文本有效,但在生成这些嵌入时,模型没有整个文档的上下文信息。这会使后续的信息检索变得不够准确。
与最先进分块策略相比,ColBERT的晚期交互模型为上下文保留挑战提供了一个更稳健的解决方案。ColBERT不是单独处理每个分块,而是将查询中的每个词元与文档中的每个词元进行比较。通过维护词元级别的嵌入,ColBERT确保在检索过程中细粒度的词元间关系不会被破坏。
比如说,在处理相同的文本时,ColBERT 会直接把第三句里的“她”与第一句里的“Lara”进行比较,从而可以正确地推断出 Lara 是那个发现这些铭文的人。这种方法保留了词语之间的上下文,没有分块的限制。
PS:点击下面的claude 工件链接来放大图
Claude Artifact试试由Claude用户打造的artifactsclaude.site虽然ColBERT更精确,但这需要更高的计算和存储资源。
让我们比较一下使用简单分块方法生成8,000个token级别嵌入(jina-embeddings-v2-base-en)的长上下文模型的存储需求,其中在生成嵌入时处理上下文。
注:这种比较假设简单的方法使用固定大小的分块,每个分块包含1024个标记(tokens)。在这种方法中,文本被分成每块1024个标记,并对每个分块进行单独编码。
存储需求是根据32位浮点精度计算的,向量维度为1024。
与较晚的交互相比,这种直接的方法只需要它的十分之一的存储资源。
尽管先进的分块技术减少了上下文损失,但ColBERT的词级别交互提供了更高的检索准确性。然而,当选择这两种方法时,存储效率和精确度之间的权衡成为一个重要的考虑因素。接下来我们将看到,延迟分块如何可以在减少存储需求上与简单方法相当,同时保持延迟交互提供的上下文。
中间立场:晚期分块处理如所见,即使是采用了先进的技术,简单的分块方法也是高效的,但容易丢失上下文。相比之下,ColBERT的晚期交互方法虽然非常精准,但资源消耗较大。“晚期分块”方法,在检索之前嵌入较大的文本段落然后再进行分割,提供了一个折中的解决方案。它结合了分块方法的效率和ColBERT方法对上下文的深入理解,为长上下文检索任务提供了一个潜在的平衡点。下面我们来详细探讨一下。
延迟嵌入,再分块: 先嵌入,再分块- 先整体嵌入文档
- 再分块嵌入,
延迟分割
步骤如下:晚期分块的步骤:
插入整个文档,
- 当模型在延迟分段处理文档时,它不会在开始时将文档分割成块。相反,它会查看整个文档并生成词元级别嵌入。
- 词元级别嵌入是文档中每个词或子词单位的数值表示。这些嵌入不仅捕捉了每个词元的语义含义,还捕捉了它与其他词元的关系。每个词元的上下文不仅来自其周围环境,而是来自整个文档。
保持上下文一致性:
- 由于嵌入过程会考虑整个文档,整体上下文得以保留。每个标记的嵌入不仅包含了该标记本身的信息,还包含了它与其他所有标记的关系。这对于确保后续的分块过程中,每个分块仍然包含了文档其余部分的信息,这一点至关重要。
比如说,有一份文档,其中第一部分谈论的是一个人,而第二部分谈论的是他们的工作。即使这两部分的信息相距甚远,模型也能够理解它们之间的联系,因为模型会把所有信息都结合起来。
将嵌入分解成分块:
- 现在你有了词嵌入,你可以将它们分成更小的片段。这些片段代表文档的各个部分(例如段落或句子组),但仍然保留了整个文档的上下文,因为嵌入是基于整个文档生成的。
- 与传统的方法不同(即先切分文本),你现在是将嵌入分成有意义的片段。例如,如果文档有10,000个词,你可以将它们分成每组500个词的片段,每个片段依旧保留了整个文档的关系。
汇集 token 嵌入:
- 模型为文档中的每个标记生成嵌入后,接下来是将这些标记级别的嵌入分组为块。这在完成嵌入步骤之后,也就是分块的步骤。
- 模型使用一种池化方法来将一组标记级别的嵌入组合成一个代表整个块的单个嵌入。池化方法可以有所不同:
- 平均池化:计算块内所有标记嵌入的平均值。
- 最大池化:从块中各个标记的每个维度选择最大值。
- CLS标记:某些模型会使用一个特殊的标记(如BERT中的
[CLS]
)来代表整个块。
通过汇集这些嵌入,该模型创建了一个段落级别的嵌入向量,它代表文档的特定部分,同时保留整个文档的上下文。
片段的检索与使用:
- 这些chunk embeddings现在可以用于下游任务,比如检索或检索增强生成(RAG)。当需要检索信息时,会使用这些更易于处理的chunk embeddings。相比孤立创建的chunk,它们可以更精确地检索相关信息。由于这些chunk仍然保留了整个文档的全局上下文,因此可以更精确地检索相关信息。
- 这里的关键优势在于查找文档中的特定信息时,系统可以依赖这些chunk embeddings,它们仍然保持了文档不同部分之间的关联。
现在让我们再来看看之前的理论比较,并再加上后期的分块分析。
我们可以看到,与简单方法相比,晚期合并同样减少了存储需求,同时更好地保留了晚期交互提供的上下文信息。
现在你可能有一个问题,即池化是什么,为什么我要用它?
关于晚期分块中的池化操作
在late chunking阶段的池化是指在每个块内降低token级别嵌入的维度,将它们压缩成一个单一的向量(嵌入),代表整个块。这一步骤至关重要,因为它将这些丰富的、上下文相关的token嵌入提炼成块级别嵌入,以便在下游任务(如RAG系统的检索或排名)中高效使用的块级别嵌入的过程。
这里是对late chunking中详细分解的池化步骤:
1. 分词级嵌入:在汇集之前,每个 token 在一个块中都有自己的 词嵌入。这些词嵌入是高维向量,代表了 token 在整个文档上下文中的含义。例如,如果我们有一个包含 10 个 token 的块,结果将会有 10 个这样的向量,每个向量代表一个 token 的含义。
- Token embeddings 是长度固定的向量,长度为 [D] ,其中 D 可以是 512、768 或 1024,这取决于使用的模型。
- 对于包含 n 个 token 的一段文本,token 的嵌入会形成一个大小为 [n, D] 的矩阵,其中
n
是 token 的数量,D
是嵌入的维度。
池化用于:
- 降维:通过池化,我们将大量的词嵌入总结为一个单一的向量,使计算变得更简单、更快。
- 浓缩上下文:池化确保我们为整个段落生成一个简洁且具有代表性的嵌入,反映其中所有词嵌入的特征。这对于检索任务来说至关重要,因为每个段落都需要通过一个单一的嵌入来表示。
有几种池化的方法。选择哪种方法会影响到最终的嵌入片段是如何表示标记的嵌入的。
a. 均值池化(mean pooling):- 它是怎么运作的:计算分块内所有 token 嵌入的平均值。
- 直觉:每个 token 嵌入对最终分块嵌入的贡献相等。结果得到的分块嵌入代表了分块内所有 token 的平均含义。
- 公式:
这来自于Chatgpt
- 其中
n
表示 chunk 中 token 的数量。 - 用例:它简单且计算效率高。当 chunk 中的所有 token 都同样重要时特别有用。
- 它是如何工作的:它在每个维度上取所有 token embedding(token嵌入)中的最大值。
- 直觉:这样就突显了 token embedding(token嵌入)中每个维度上最重要的特征。只保留最大值。
- 公式:…
来源:ChatGpt
- 其中
d
表示嵌入维度,n
则表示标记的数量。 - 使用场景:在特定特征(如某些单词)应该主导片段中的内容表示的情况下非常有用,例如,在片段中的某些关键词权重更大的场景中。
- 它是如何工作的:这特定于像 BERT 这样的 transformer 模型。在输入序列前插入一个特殊的标记,称为 CLS(分类)标记。经过嵌入后,模型利用 CLS 标记的嵌入表示 作为整个序列的摘要。
- 直觉:CLS 标记被设计用来捕捉整个输入序列的全局信息。直接使用它可以很好地表示这个输入片段,因为模型已经经过训练来使用该标记进行此类任务。
- 应用场景:这在像 BERT 这样的预训练模型中很常见,这些模型会生成 CLS 标记,对于分类等任务来说非常有效。
d. 基于注意力导向的汇聚:
- 它是怎么运作的:注意力汇聚机制不再只是简单地求平均或取最大值,而是为每个词嵌入分配权重,来反映它们在片段中的重要性。
- 直觉:根据某种学习到的注意力机制,更相关的词嵌入对最终片段嵌入的贡献更大。就像一个加权平均值,其中一些词的贡献比其他词更大。
- 公式(简化):
来源:来自ChatGpt(这是来自ChatGpt的回答)
- 其中,
w_i
是为第i
个 token embedding 学习到的注意力权重。 - 应用场景:这种方法更复杂,能够捕捉 token 之间重要性的不同。特别适用于 RAG 场景,在 RAG 场景中某些部分可能包含更多用于检索的信息。
- 完成池化后,每个块都会得到一个单一向量,该向量代表该块内所有标记嵌入的组合意义。
- 现在这个块嵌入可以用于下游任务,例如在RAG系统中的检索,或者用于相似度搜索。这些块嵌入的丰富性使得即使检索系统是基于块进行操作的,它们仍然能够从整个文档的整体上下文中获益。
考虑一段包含5个单词的文本:["the", "quick", "brown", "fox", "jumps"]
。每个单词都会被转换成1024维的高维向量。嵌入整个文本段落后,通过对嵌入后的向量进行池化操作,得到一个向量来表示这个文本片段。
- 均值池化 会将5个嵌入平均,生成一个单一的、[1024]维的向量。
- 最大池化 会取所有嵌入中的最大值,生成一个[1024]维的向量,强调最显著的特征。
- CLS标记 如果使用生成它的转换器模型,可以使用它。
- 注意力池化 可以对某些词赋予更高的权重(例如,“狐狸”可能比“the”赋予更高的权重,突出更关键的信息)。
现在你已经理解了汇集,需要注意的是,虽然延迟分块仍然需要边界提示,但这些提示仅在获取 token 的嵌入之后才被使用。
来源:Jina.AI(公司)
你现在可能在想,什么是边界提示?
“边界标志”指的是文档中帮助定义或划分文本自然段落的指示或信号,例如句子、段落或章节。这些标志可以包括标点符号(如句点、逗号或换行)、格式元素(如标题或项目符号)或语义标记(如主题或子主题的变化)。
在后期分块的背景下,边界线索用来决定在生成词级别嵌入之后如何将文档分割成更小的段落。这与典型的分块策略不同,后者通常是根据固定的词长度或在生成嵌入之前进行任意分割。
实现当我们将延迟分块应用到上面定义的劳拉的探险例子中时,可以立即感受到语义相似性的提升。例如,在文中的“她”和“劳拉”之间,“她”的向量现在包含了与之前提到的“劳拉”相关的关联信息,从而使其在涉及她的名字的查询中成为一个更好的匹配。同样,“遗迹”或“探险”这类词也会链接回它们最初出现的地方,从而更好地找到相关信息。
上述数值结果比较了“Lara”这个词的词嵌入在不同句子中的相似性,这些句子来自于我们之前定义的关于Lara的例子,通过使用余弦相似度。
链接到笔记本(点击即可访问) — LateChunking
注意:重要洞察为什么先用Token嵌入?
- 通过先为整个文档生成嵌入表示,每个标记都会得到一个考虑到整个文档的上下文的表示。这有助于保留文档中远距离部分之间的关联。例如,如果两个相关概念在文档的不同位置被提及,它们的嵌入将会反映出这种关系。
块是如何充满语境信息的
当在嵌入整个文档之后将词元嵌入分组成块,这些块会保留文档的更广泛上下文。这是因为每个块仍然是上下文丰富的块,它们仍然与文档的其他部分保持联系。
改进的检索:
当模型根据这些分块嵌入检索信息时,它更有可能返回准确的结果。这些嵌入是基于整个文档的,所以结果也会更相关。
所以说,就是这样的结论:在late chunking时,嵌入过程首先为整个文档进行,确保每个标记的嵌入保留整个文档的完整上下文。仅在完成整个嵌入过程之后,才将嵌入分割成更小的部分,这些部分因为保留了文档远距离部分之间的关系而内容更加丰富。这种方法确保每个片段都保持全局文档的上下文,从而使每个片段都具有全局性,从而使得检索结果更加准确和有意义。
参考文献- Borchmann, D., Reimers, N., & Gurevych, I. (2020). 具有对比学习的高效段落检索。arXiv. https://arxiv.org/abs/2004.12832
- Khattab, O., & Zaharia, M. (2021). ColBERTv2:通过轻量级晚期互动实现有效的检索。arXiv. https://arxiv.org/abs/2112.01488
- Jina AI. (n.d.). 滞后切分:高效文档检索。GitHub 仓库。https://github.com/jina-ai/late-chunking
- Qin, H., Gao, W., Zhang, X., & Liu, X. (2024). 高效的多阶段密集检索框架。arXiv. https://arxiv.org/pdf/2409.04701
- Weaviate. (n.d.). 滞后切分和晚期互动:为什么重要。Weaviate 博客。https://weaviate.io/blog/late-chunking#late-interaction-and-colbert
- Anthropic. (2024). Claude 3.5 Sonnet [AI 模型]。https://www.anthropic.com
- OpenAI. (2023). ChatGPT [AI 模型]。https://www.openai.com/chatgpt
共同学习,写下你的评论
评论加载中...
作者其他优质文章