AI知识库

53AI知识库

学习大模型的前沿技术与行业应用场景


使用RAG技术构建企业级文档问答系统:切分(5)Late Chunking

发布日期:2025-02-28 06:59:39 浏览次数: 1579 来源:超乎想象的科技圈
推荐语

探索Jina最新发布的文本切分技术,提升企业文档问答系统的准确性。

核心内容:
1. Jina发布的Late Chunking技术背景及动机
2. Late Chunking技术原理及其与现有方法的区别
3. 中文环境下Late Chunking的实现与效果分析

杨芳贤
53A创始人/腾讯云(TVP)最具价值专家

概述

Jina在2024年推出了一种新的文本切分方法,在论文Late chunking: contextual chunk embeddings using long-context embedding models中做了系统阐述。

来源:https://jina.ai/news/late-chunking-in-long-context-embedding-models/对中文实现Late Chunking,本文应该是第一篇,本文尽可能简单地介绍这项工作的动机、原理以及中文实现,并对结果做一个分析。

动机

正常在撰写文章的过程中,使用一些代词是非常常见的,Jina指出,由于切分导致文本会切分到不同的片段中,而有些片段中只有代词而没有代词所指对象,这可能会导致检索失败。

例如下面的这段文本,比如文档按下图右侧的方式切分,用户的问题是“柏林有多少居民”,很明显使用第二个片段可以回答是3.85百万,但由于它使用的是Its,导致这个片段有可能是检索不到的,或者即使检索到了,大模型也没有办法明确知道Its指代的是Berlin。

所以说,此处给了我们一个论文思路,是不是可以在切分前,统一做一个指代消解,然后再切分,这样第二段就会变“Berlin’s more than 3.85 million inhabitants make it the European Union’s most populous city, as measured by population within city limits.” 这样检索时就不会有代词的问题了。感兴趣的朋友可以做个实验。

原理

RAG一般的流程是先做文本切分,然后再向量化,而Jina给出的解决方案正如其名——Late Chunking,它是先直接对整段文本做向量化,然后再切分。

具体是怎么做的呢,首先取知识库中的一整篇文档过Embedding模型,然后会得到一整篇文档每个token位置的Embedding向量,然后按照选取的切分方式,取特定范围的向量做平均,从而得到这个片段的Embedding向量。

例如“DeepSeek是由杭州幻方量化所成立的子公司所开发的大语言模型。它真的很强大!”,

我们假设这段话tokenize后是这样的:

[, , , , , , , , , , , , , , , , , , , , , , ]

共计23个token,对Embedding模型设置参数,输出每个问题的hidden state(也是个向量,也就是下图中的Token Embedding),如果维度是1024,那就意味着“DeepSeek”这个位置就会有一个1024维的向量,“是”这个位置也是一样。因为是作为一整个句子送入Embedding模型的,所以“它”这个token的hidden state其实融合了整个句子的信息,知道“它”其实指代的是DeepSeek。那如果我们按照句子切分,取“它真的很强”对应位置(上面19-23位置)的hidden state然后做平均,那平均后得到的向量其实表达的含义是“DeepSeek真的很强大”。

这里面涉及3个问题:

  1. 为什么是取一整篇文档而不是整个知识库:因为实际情况几乎不会出现跨文档还使用代词指代的情况,Late Chunking要解决的是切分导致代词不知其所指的问题,取整个知识库拼接没有意义;

  2. 所以最后是怎么切分的:Jina官方的代码仓库中有几种方法,分别是按语意、按句子、按token长度,切分之后片段的Embedding,是对片段切分点范围内的Token Embeddings取平均得到的;

  3. 一整篇文档过Embedding模型不担心超长吗:所以此处Jina要求尽量使用支持长输入的Embedding模型,如果还是超长,那就按照Embedding模型支持的最大长度(例如8192)先切分,假设向量维度是1024,按8192切分后得到7个片段,最终把这7个片段的8192 个维度为1024的向量拼接起来,然后还是按2中提到的方法获取片段的Embedding。这里面其实涉及到很多操作问题,首先比如是按8192切分,其实是token的长度是8192,而不是句子长度,其次是切分后字符的索引要和token的索引对应,最后如果超过Embedding模型的最大长度,需要考虑模型的第一个位置是否有特殊字符,要把它移除。

结果

官方使用了“Berlin”作为Query,分别计算与下面3个句子的相似度。对这3个句子分别使用了两种方式做向量化,一种是传统的先切分再向量化,记为Traditional,另一种是Late Chunking。

第一个句子不存在指代不明的问题,所以两种方法计算的余弦相似度很接近,这个是符合预期的。

第二个句子中有代词Its、it,第三个句子中有The city,使用传统方式计算的余弦相似度就相对较低,而使用Late Chunking方式计算的余弦相似度比较高,这就体现出了Late Chunking的优势。

TextSimilarity TraditionalSimilarity Late Chunking 
Berlin is the capital and largest city of Germany, both by area and by population."0.848621850.849546 
Its more than 3.85 million inhabitants make it the European Union's most populous city, as measured by population within city limits.0.70843380.82489026 
The city is also one of the states of Germany, and is the third smallest state in the country in terms of area.0.75345530.84980094 

相似度高意味着什么,意味知识库比较大时,如果输入“柏林的常驻人口有多少”,使用Late Chunking第2个句子会排在候选中更靠前的位置,从而有更大的概率被召回,而传统方法则会排在比较靠后的位置。

效果

虽然动机看起来站得住脚,原理好像也说得通,但实验的结果却表现很差。后文会进行分析。此处不是完全控制变量实验,有两个变量,除了切分方法不同外,向量模型也不同,其余的生成模型、评估模型与其他实验保持一致。

核心代码

本文代码已开源,地址在:https://github.com/Steven-Luo/MasteringRAG/blob/main/split/05_late_chunking.ipynb

Late Chunking最核心的部分,其实不是切分动作在前还是在后,而是片段中的向量表示,要能融合上下文。源代码提供的是按照英文句子、token数等方式的切分,与中文习惯差异较大,所以本文中的实现还是按换行进行了切分,但每个片段的Embedding使用的是融合了整个文档语意信息的。

 (model, tokenizer, document, batch_size=):
    tokenized_document = tokenizer(document, return_tensors=)
    tokens = tokenized_document.tokens()

    outputs = []
 i  trange(, (tokens), batch_size):

        start = i
        end   = (i + batch_size, (tokens))

        batch_inputs = {k: v[:, start:end].to(device)  k, v  tokenized_document.items()}

 torch.no_grad():
            model_output = model(**batch_inputs)

        outputs.append(model_output.last_hidden_state)

    model_output = torch.cat(outputs, dim=)
 model_output


 (token_embeddings, span_annotation, max_length=):
    outputs = []
 embeddings, annotations  (token_embeddings, span_annotation):
 (max_length   ):
            annotations = [
                (start, (end, max_length - ))
 (start, end)  annotations
 start < (max_length - )
            ]
        pooled_embeddings = []

 idx, (start, end)  (annotations):
 chunks[idx] == tokenizer.decode(doc_input_ids[start: end]), 
 (end - start) >= :
                pooled_embeddings.append(

                    embeddings[start:end].mean(dim=).cpu().numpy()
                )
:
 ()

        pooled_embeddings = [
            embedding / np.linalg.norm(embedding)  embedding  pooled_embeddings
        ]
        outputs.append(pooled_embeddings)
 outputs


span_annotations = []

doc_input_ids = doc_tokens[][]
start_pos = 
seperator_len = (tokenizer(, return_tensors=)[][]) - 

 chunk_idx, chunk  (chunks):

    chunk_input_ids = tokenizer(chunk, return_tensors=)[][][:-]
    chunk_token_len = (chunk_input_ids)

 (doc_input_ids[start_pos: start_pos + chunk_token_len] == chunk_input_ids).detach().numpy().mean() != :
        start_pos += 

 (doc_input_ids[start_pos: start_pos + chunk_token_len] == chunk_input_ids).detach().numpy().mean() == , chunk_idx
    span_annotations.append((start_pos, start_pos + chunk_token_len))
    start_pos += chunk_token_len


document_embeddings = document_to_token_embeddings(model, tokenizer, processed_doc, batch_size=)

late_embeddings = late_chunking(document_embeddings, [span_annotations])

结果分析

考虑到我们的测试集是中文的,而Jina的官方代码是英文的,所以我一度以为是我的实现有bug,但对英文的数据分析后,发现同样存在一样的问题。

此处只展示最关键的分析,更多分析大家可以查看分析部分的源代码:https://github.com/Steven-Luo/MasteringRAG/blob/main/split/05_late_chunking_en_debug.ipynb

对于一个知识库片段,使用它自身作为Query去检索,如果只保留Top1,绝大多数情况下应该检索到它自身才对,而在Late Chunking中却不是这样。

下面的分析使用维基百科中DeepSeek词条的部分文本作为知识库,使用Jina公布的代码切分得到片段,然后分别拿每个片段作为Query,分别过Embedding模型得到Query的向量,与作为知识库片段的向量两两之间计算相似度,从这个结果来看,最相似的片段,并不总是自己,前5个句子,似乎都跟第一个句子最相似。

由于Late Chunking是将整个片段每个位置的hidden state做平均,所以可以想想,短句、代词较多的句子,应该会跟其他句子更相似。简便起见,此处检查句子长度。

下图中,横轴的0表示以每个片段作为Query,最相似的不是自身,1表是是自身,从结果可以看出,长句普遍都和自身最相似,而短句则和其他句子最相似。这也好理解,因为短句大都要借助前面的内容作为上下文,其中会有相对较多的代词。

从RAG全流程评估结果和英文的分析结果来看,这种方法似乎不是一种非常通用的能提升切分效果的方法,欢迎大家尝试,如果发现了我代码中的bug,欢迎反馈。

往期文章
Agent系列文章
Langchain中使用Ollama提供的Qwen大模型进行Function Call实现天气查询、网络搜索
Langchain中使用千问官方API进行Function Call实现天气查询、网络搜索
使用Ollama提供的Llama3 8B搭建自己的斯坦福多智能体AI小镇
使用Ollama提供的Qwen2 7B搭建自己的中文版斯坦福多智能体AI小镇
RAG系列文章
数据准备
使用RAG技术构建企业级文档问答系统之QA抽取
Baseline
使用RAG技术构建企业级文档问答系统之基础流程
评估
使用TruLens进行评估
使用GPT4进行评估
解析优化
解析(1)使用MinerU将PDF转换为Markdown
切分优化
切分(1)Markdown文档切分
切分(2)使用Embedding进行语义切分
切分(3)使用Jina API进行语义切分
切分(4)Meta Chunking
检索优化
检索优化(1)Embedding微调
检索优化(2)Multi Query
检索优化(3)RAG Fusion
检索优化(4)BM25和混合检索
检索优化(5) 常用Rerank对比
检索优化(6) Rerank模型微调
检索优化(7)HyDE
检索优化(8)Step-Back Prompting
检索优化(9)Parent Document Retriever
检索优化(10)上下文压缩
检索优化(11)上下文片段数调参
检索优化(12)RAPTOR
检索优化(13)Contextual Retrieval
检索优化(14)CRAG——自动判断是否联网检索的RAG
生成优化
生成优化(1)超长上下文LLM vs. RAG
新架构

新架构(1)LightRAG

使用Flowise零代码构建RAG
使用Flowise零代码构建RAG(1)——基础流程
使用Flowise零代码构建RAG(2)——HyDE
使用Flowise零代码构建RAG(3)——Reciprocal Rank Fusion

53AI,企业落地大模型首选服务商

产品:场景落地咨询+大模型应用平台+行业解决方案

承诺:免费场景POC验证,效果验证后签署服务协议。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

添加专属顾问

回到顶部

加载中...

扫码咨询