深度拆解_NotebookLM背后的技术_你的个人知识库_是如何变得如此智能的
1. 快速不看版
- NLM 是一套分工明确、环环相扣的模块化系统(非端到端模型)
- 多类型的数据准备和解析做好了大量的前置工程
- 情景化分块&混合检索(关键字&模糊语义)确保召回率
- 类 Cross-Encoder 模型作为核对评分器模块,拿着用户问题和召回的文本块匹配计算相关性,确保准确率
前几篇文章已经写过了 初体验、产品发展历程、产品团队与理念,这篇文章终于来到了一探究竟的技术细节。作为一个可以帮你消化海量 PDF、网页和笔记,并随时与你进行深度对话的 " 外置大脑 ",NotebookLM 为什么能做得如此出色?背后究竟体现了谷歌哪些强大的工程能力?
2. 从 RAG 的整体流程谈起
RAG 主要分为两块:
离线部分:对知识库文档进行解析、拆分、索引构建和入库。
这部分可以说是 dirty work,因为需要对多种不同格式的文档数据进行大量的清洗和处理。
但同时又是非常重要的工作,因为 “Garbage in, Garbage out”,数据质量是影响 RAG 最终效果的重要因素。
在线部分:当用户输入问题后,我们会对 query 进行分析,如关键词提取、意图识别等,然后根据路由条件进行知识库的多种召回检索或联网搜索。
接着进行重排以提取最重要、最相关的上下文信息,然后将用户问题和上下文信息一起提交给 LLM,让 LLM 根据背景知识提供可靠的回答。
为了进一步提高系统性能,我们还会引入后置处理环节,如风控检测、结果缓存和 RAG 相关指标的监控上报等。
在这一过程中,外部非结构化数据的清洗和处理、文本分块、Query 的预处理、是否每次 Query 都要进行检索、上下文信息的检索和排序能力、如何评估检索生成质量、知识缓存等环节都会影响系统的性能。
3. 数据准备与解析
如何从大量非结构化数据中提取出内容,就需要智能解析技术了。
利用机器学习算法,对文档内容进行自动识别、理解和处理的过程。它不仅包括文本内容的识别,还涉及到图像、图表和表格等非文本元素的解析。
3.1. 开源项目推荐
项目名称:OmniParse
项目链接:https://github.com/adithya-s-k/omniparse
OmniParse 可以将绝大部分非结构化数据摄取并解析为结构化、可操作的数据,优化用于生成式人工智能(LLM)应用。
无论你处理的是文档、表格、图像、视频、音频文件还是网页,OmniParse 都会将数据准备得干净、结构化,并为 AI 应用(如 RAG、微调等)做好准备。
3.2. NotebookLM 支持的知识库类型
以图片形式的文档(如对纸质文档进行了扫描、拍照等)为例进行说明。
首先,需要对文档进行版面分析,用于识别和理解文档中的视觉和结构布局。这里会使用到区域检测和区域分类等技术。
区域检测用于识别文档中的不同区域,如文本块、图像、表格和图表等。
而区域分类则将检测到的区域进一步分类,如文本可以进一步被细分为标题、副标题、正文文本等;图像可能被分类为图片、图表或公式等;表格需要识别为包含数据的结构化形式。
然后,对这些区域分别进行识别。
对于表格区域,它们会被输入表格识别模块进行结构化识别,包含解析表格的行和列,识别单元格边界,提取结构化数据。
对于文本区域,使用 OCR 引擎将图像中的文字转为机器可读的字符。
3.3. 音视频处理方式
3.4. 公众号文章处理技巧
3.5. 具体操作方法
3.6. RAGFlow 开源项目
3.7. RAGFlow 简介
3.8. RAGFlow 特色功能
3.9. 文档布局识别
3.10. DeepDoc 模块
4. 文本分块(chunking)
4.1. 按大小分块
按大小分块是指将文本按固定字符数或单词数进行分割,这是最直接、最经济的分块方法,但也存在明显的问题,也就是语义不连贯。
这里要讲一个参数,叫做文本块之间的最大重叠长度,这个参数很重要,表示两个切分文本之间的重合度,设置重叠大小可以保持文本块之间的连续性。
4.2. 特定格式分块
特定格式分块是针对具有特定结构或语法特征的文本文件进行分块的一种方法,如 Markdown、LaTeX、Python 代码等。
这种分块方式依据各自格式的特定字符或结构标记来实现,以保证分块后的内容在结构上的完整性和逻辑上的连贯性。
比如 Markdown 文本可以使用标题、列表、引用等来进行分块。
4.3. 递归分块
递归分块以一组分隔符为参数,以递归的方式将文本分成更小的块。
如果在第一次分割时无法得到所需长度的块,它将递归地继续尝试。
每次递归都会尝试更细粒度的分割符号,直到块的长度满足要求。
这样可以确保即使初始块很大,最终也能得到较为合适的小块。
例如,可以先尝试按照句子结束符来分割(如句号或问号),如果这样分割出的文本块太长,就会依次尝试其他的标记,例如逗号或者空格。
通过这种方式,就可以找到比较合适的分割点,同时尽量避免破坏文本的语义结构。
4.4. 语义分块
语义分块首先在句子之间进行分割,句子通常是一个语义单位,它包含关于一个主题的单一想法;然后使用 Embedding 表征句子;最后将相似的句子组合在一起形成块,同时保持句子的顺序。
4.5. NLM 情景化分块
首先,系统还是会先将文档进行初步的、更细粒度的切分,通常是切分成句子。
接下来,系统会使用一个强大的 Embedding 模型,为每一个句子生成一个代表其语义的 " 向量 "(即数字指纹)。
计算语义相似度,这是最关键的一步。系统会逐一比较相邻句子之间的向量。
如果句子 A 和句子 B 的向量在空间中非常 " 接近 ",说明它们在语义上是强相关的,应该被归为同一个知识块。
如果句子 D 和句子 E 的向量在空间中突然 " 疏远 " 了,这说明话题可能在这里发生了转变,是一个理想的切分点。
根据上面计算出的相似度得分,系统会将连续的、语义相似的句子 " 聚类 " 在一起,形成一个个最终的 " 知识块 "(Chunk)。每个块都代表一个连贯的 " 情景 " 或 " 思想 "。
就像大多数人做过的初高中语文作业一样,老师给你一篇文章,你要做的就是把自然段分段之后,然后每 x 个自然段给个总结索引。
不同是如果是比较跳跃的文章,比如第一段和最后一段讲的意思是一样的,那就会将这两段融合在一起成为一个 chunk,然后再进行总结。
4.6. 命题分块
命题分块也是一种语义分块,它的原理是基于 LLM,逐步构建块。
首先从基于段落的句法分块迭代开始。
对于每个段落,使用 LLM 生成独立的陈述(或者命题),比如我们可以使用简单的提示「这段文字讨论了哪些主题」。
移除冗余命题,索引并存储生成的命题。
在查询时,从命题语料库中检索,而不是原始文档语料库。
举个例子,原始句子为:" 在 1919 年,战胜国在巴黎凡尔赛宫签署了旨在惩罚德国并重塑欧洲格局的《凡尔赛条约》。" 这个长句会被分解成多个独立的命题:
- 《凡尔赛条约》是在 1919 年签署的。
- 《凡尔赛条约》是在巴黎凡尔赛宫签署的。
- 《凡尔赛条约》的签署方是战胜国。
- 《凡尔赛条约》旨在惩罚德国。
- 《凡尔赛条约》旨在重塑欧洲格局。
命题分块就是为了将所有信息原子化。每个知识块就是一个无法再分割的、独立为真的事实。这对于需要进行精细事实问答的场景非常有效。
4.7. 分块总结
文本分块并没有固定的最佳策略。
选择哪种方式取决于具体的需求和场景,需要根据业务情况进行调整和优化。
关键是找到适合当前应用的分块策略,而不是追求单一的完美方案。
有时候,为了获得更准确的查询结果,我们甚至需要灵活地使用多种策略相结合。
另外,为了直观分析文本分割器是如何工作的,我们可以使用 ChunkViz 工具进行可视化,它会展示文本是如何被分割的,可以帮助我们调整分割参数。
5. 索引构建与向量化
5.1. 索引类型
chunk 构建完成后,将 chunk 先去生成索引,索引有多种方式:
(1)问题索引:直接将 chunk 对应到问题上,这个问题是主观的用户问哪些问题这个文本块可以回答;
(2)默认索引:固定直接等于全部文本内容;
(3)summary 索引:将 chunk 压缩成摘要;
(4)父子索引:使用父子模式时,子块用于检索,父块用作上下文。更具体的会在后边细说。
Embedding 在 RAG 系统中扮演着至关重要的角色,如果 Embedding 模型在对私域知识进行向量化表示的过程中表现不佳,那么即使 RAG 系统在其他方面设计得当,最终效果也难以达到预期水平。
对输入文本块的 tokens 长度限制,超出这个限制则会导致模型对文本进行截断,从而丢失信息,影响下游任务的性能。
向量化是通过向量模型来实现的,现在的向量模型(embedding model)不仅支持文本、也支持图像和视频等多模态数据。通过使用预训练的语言模型(如 BERT、DPR 等),我们可以将 query 和分块文本转换为向量。
这里要注意的是,切分的文本片段后续需要通过 Embedding 模型进行向量化,所以必须考虑模型的 token 限制。
- OpenAI Embedding(8K tokens)
https://platform.openai.com/docs/guides/embeddings
- JinaAI Embedding
https://huggingface.co/jinaai/jina-embeddings-v2-base-zh
- BAAI/bge Embedding
https://huggingface.co/BAAI/bge-large-zh-v1.5
其中 bge-m3 支持超过 100 种语言,支持 8K 长度的输入文本,同时支持稀疏检索、密集检索、多向量检索。至于这三种检索是什么意思,下边会详细讲解。
5.2. 向量模型评测
有这么多向量模型,我们如何衡量一种 Embedding 模型相对于其他模型的有效性呢?
MTEB(Massive Text Embedding Benchmark)是目前最全面的文本嵌入评估基准之一,支持多种语言,是目前最全面的文本嵌入评估基准之一。
MTEB 提供了一个公开的排行榜,用于展示各个模型在不同任务上的表现。
MTEB 排行榜:https://huggingface.co/spaces/mteb/leaderboard
MTEB 包含以下任务类别,每个类别对应不同的评估指标和数据集:
- 文本分类(Classification):如情感分析、意图分类等。
- 聚类(Clustering):如将相似文本分为同一类。
- 成对分类(Pair Classification):判断两个文本是否重复或具有相似含义。
- 重排序(Reranking):根据查询重新排序相关和不相关的参考文本。
- 检索(Retrieval):从大量文档中找到与查询相关的文档。
- 语义文本相似性(STS):评估句子对之间的相似性。
- 摘要(Summarization):评估机器生成摘要的质量。
5.3. 业务选择向量模型的考虑因素
5.4. 向量化模型
在特定领域,对向量模型进行 Finetune 的主要目标是提高 Recall@N (前 N 个检索结果中包含相关文档的比例)的准确率和优化正例与负例的 similarity 值域分布。
这里将文本或者其他数据向量化以后,存入向量数据库,就完成了离线的过程,接下来就是在线的过程。
6. 搜索与召回
7. Query 理解与语义空间对齐
7.1. Query 理解技术
query 理解技术是提高信息检索效率和准确性的关键。当前常用的 query 理解技术分为三大类:query 改写、query 增强和 query 分解。
7.2. query 改写
下面是一段多轮对话的示例:
User:最近有什么好看的电视剧?
Bot:最近上映了《庆余年 2》,与范闲再探庙堂江湖的故事
User:我想看第一季
在这个例子,用户的问题「我想看第一季」包含了隐含的指代信息,没有上下文信息的补全,系统无法知道具体指的是哪部电视剧。通过采用上下文信息补全,我们把前面的对话信息也纳入其中,对 query 进行改写,可以生成类似「我想看庆余年第一季」的完整 query,从而提高后续检索的清晰度和相关性。
7.3. query 分解
7.3.1. RAG fusion
Multi-query:分解成多个角度看 Query 之后搜索到的多个 Doc 一起作为 context 输送给大脑。
HyDE:利用 LLM 输出的答案去和 Doc 匹配找到对应的片段。
7.4. 语义路由
传统的路由机制通常依赖于预定义的规则或简单的条件判断(如 if/else 语句),根据输入的关键词或模式将请求分发到相应的模块。
然而,这种方法在处理复杂的自然语言查询时往往捉襟见肘,因为它难以理解上下文和细微差别。
语义路由不再单纯依赖于显式的关键词或模式匹配,而是通过语义理解,识别用户意图和需求,将请求引导至最合适的服务、数据库或相关处理组件。
语义路由可以根据 user query 路由到不同的数据来源,这里的数据源,其实也可以看成是知识库,如文档知识库、DB 知识库、API 查询。
user query 也可以传递给 Agent、Vector Store,或者直接传递给 LLM 进行处理。
例如,在智能客服系统中,简单的常见问题可以直接传递给 FAQ 知识库或 Vector Store 进行快速回答,而涉及复杂操作的请求则路由到 Agent 来调用各种工具完成任务。
对于需要深度理解的自然语言问题,查询可以直接传递给大型语言模型(LLM)来生成详细且个性化的响应,从而提高系统的整体响应能力和用户满意度。
可以根据 question 的不同路由到不同的 prompt template。
例如,在教育应用中,针对复杂数学问题的查询可以路由到详细步骤模板,提供分步解答;而一般性的科学事实问题可以使用简洁回答模板,直接给出准确答案。
又比如,在生成社交媒体内容时,用户询问关于品牌推广策略的问题,可以路由到专业模板以确保内容的准确性和正式性,而普通用户的日常互动问题则可使用轻松有趣的模板,以增强互动体验。
semantic-router 是一个开源项目,地址为 https://github.com/aurelio-labs/semantic-router,它的基本用法是为每个分支提供一系列的话术模板,或者说 query 示例。
然后将用户的 query 和预设的 query 示例进行相似性匹配,最后返回与预设 query 相似度最高的对应分支。
这个项目还提供了 jupyter notebook,大家可以参考:https://github.com/aurelio-labs/semantic-router/tree/main/docs
7.5. 核心搜索与召回
这里包含了关键词检索、向量检索(语义检索)、混合检索,也就是上文出现过的稀疏检索、密集检索、多向量检索。
这里有必要讲一下这三种检索都是什么意思。
7.6. 稀疏检索
稀疏检索是最传统、最经典的检索方法,它的核心思想是匹配关键词。
意思上是强相关什么关键词的场景,效果非常好。
优点:
- 技术成熟,计算相对简单,不需要复杂的深度学习模型。
- 可解释性强:你能清楚地知道文档被召回是因为它包含了哪些具体的关键词。
缺点也很明显:
- 无法理解同义词或近义词。搜索 " 人工智能 " 无法找到只包含 “AI” 的文档。
- 不能捕捉词语在不同语境下的含义。搜索 " 苹果 " 可能会同时返回关于水果和苹果公司的结果。
7.7. 密集检索
密集检索是现代基于深度学习的检索方法,它的核心思想是理解语义。
对于这句话 " 机器人喜欢学习 ",其密集向量可能是一个 384 维的浮点数数组,例如 [-0.012, 0.345, -0.789, …, 0.556],其中每个数字都蕴含了一部分语义信息。
优点:
- 能够轻松跨越关键词的限制,理解同义词、近义词和上下文。搜索 " 人工智能 " 可以轻松召回包含 “AI” 或 " 机器学习 " 的文档。
- 对用户的自然语言、口语化或意图模糊的查询有更好的理解能力。
缺点:
- 有时会忽略掉一些必须精确匹配的关键词,导致在需要高精度的场景下表现不佳。
- 需要强大的 GPU 资源来运行深度学习模型进行编码。
- 可解释性较差,有时难以理解为什么某个文档被认为是相关的。
技术实现:基于 BERT、Sentence-BERT 等模型生成的向量,并通过 FAISS 等工具进行近似最近邻搜索。
7.8. 多向量检索
多向量检索是一种更精细、更先进的检索策略,它试图结合稀疏和密集检索的优点,或者用更丰富的方式来表征一个文档。
它的核心思想是多角度表征。
它不是将整个文档压缩成一个单一的向量,而是通过不同策略生成多个向量与一个文档关联。这有几种常见模式:
分块向量:将文档切成许多小块(chunks),为每个小块生成一个向量用于检索。
当某个小块被命中时,返回其所属的、内容更完整的 " 父文档 " 给大模型,这样既保证了检索的精度,又提供了充足的上下文。
代理向量:为文档或文档块创建更精炼的摘要或生成一些 " 假设性问题 ",然后对这些摘要或问题进行向量化。
用这些 " 代理 " 向量进行检索,命中后再返回原始文档。
多模型向量:用不同的模型为一个文档生成多个向量,比如一个向量代表标题,一个代表正文,一个代表图片描述,从而从不同角度捕捉文档信息。
词元级向量:这是最精细的一种,它不为整个文档生成向量,而是为文档中的每一个词元(token)都生成一个上下文相关的向量。
检索时,通过计算 query 中每个词元与文档中所有词元之间的最大相似度来进行打分,精度极高。
优点:
- 能在细粒度的信息点(如一个小块)上实现精准匹配,同时又能提供宏观、完整的上下文(如整个文档)。
- 对于包含表格、代码和长篇幅的复杂文档,效果优于单一的密集检索。
- 允许开发者设计更复杂的检索逻辑,比如手动添加一些问题向量来确保某些文档在特定查询下一定能被召回。
缺点:
- 相比前两者,逻辑更复杂,需要管理文档、块、向量之间的多种映射关系。
- 一个文档需要存储多个向量,占用的存储空间更大。
7.9. 重排技术
重排就是使用一个更强大、更精细的模型(如 ColBERT)对这 20 个结果进行重新打分排序,选出最终的 Top 3 或 Top 5,确保提供给 AI 的 " 参考资料 " 质量最高。
以下介绍几种常用的检索/重排技术。
7.10. ColBERT 模型
7.11. ColBERT 技术原理
它并非一个独立的 RAG 模型,而是一个功能强大的组件——主要作为检索器和重排器——能够显著提升送给大语言模型(LLM)的信息质量和相关性。
词元级嵌入:ColBERT 不为整个文档创建单个向量,而是为文档中的每个词元(单词或子词)生成一个上下文相关的嵌入。
这使得文本的意义能够以更精细的粒度被表示。
最大相似度匹配:对于查询中的每一个词元,ColBERT 会在文档中寻找最相似的词元嵌入。
最终的相关性分数是通过将这些最大相似度分数相加得出的。
这种 " 延迟交互 " 允许进行细粒度的比较,从而能更好地捕捉查询中的特定细微差别。
7.12. LightRAG 知识图谱
它把知识组织成一张 " 关系网 "。
实体识别:这是 LightRAG 的核心。它在数据入库阶段,使用自然语言处理(NLP)模型从所有文档中自动识别出实体(如人、事、物、概念)和它们之间的关系。
图谱构建:将构建好的 " 实体 - 关系 - 实体 " 三元组存入专门的图数据库中。
图谱检索:当用户提问时,系统首先识别出问题中的关键实体,然后在图谱中定位到这些实体节点。
接着,它会执行图遍历算法,探索与这些节点相邻的其他节点和关系,从而收集到比关键词匹配丰富得多的上下文信息。
答案生成:将从图谱中检索到的、结构化的上下文信息与用户的原始问题一起提供给 LLM,生成最终答案。
7.13. RAPTOR
它是一座 " 知识金字塔 "。莫名想起了金字塔原理是怎么肥事……
RAPTOR 通过递归地对文本进行聚类和摘要,构建出一个层次化的知识结构树,使得系统可以在不同的抽象层级(从具体细节到高度概括)上进行检索,以匹配不同粒度的问题
7.13.1. 就像知识金字塔一样,不同的层级代表不同程度的抽象
金字塔底层:就是原始的、细粒度的文本块(书页)。
往上一层:建筑师阅读底层的几页内容,发现它们都在讲 " 细胞的结构 “,于是他把这几页聚类,并写下一段摘要:” 本部分总结了细胞的组成部分,如细胞核、细胞质和细胞膜 "。这个摘要就成了金字塔的第二层。
再往上一层:他继续阅读第二层的几段摘要,发现它们分别总结了 " 细胞结构 “、” 细胞分裂 “、” 细胞呼吸 "。
于是他又把这几段摘要聚类,并写下一个更高层级的摘要:" 本章综述了细胞生物学的核心概念 "。这成了金字塔的第三层。
金字塔顶端:最终,整本书被概括成一句话的摘要。
当用户提问时:
如果问题很具体:" 细胞膜的功能是什么?" 系统可能会直接在金字塔底层找到最相关的原始文本块。
如果问题很概括:" 细胞生物学讲了些什么?" 系统可能会在金字塔的高层找到那段总结性的摘要,因为它能更好地概括整体内容。
8. AI 整合生成回答
如何将检索到的知识片段和用户问题组合成一个清晰、有效的指令给大语言模型?
基本提示词模板:" 请你基于以下背景信息:‘{检索到的知识}’,来回答这个问题:‘{用户的原始问题}’。请确保你的回答完全基于所提供的背景信息,不要使用外部知识。"
8.1. 然后大语言模型(如 GPT 系列、Gemini 系列)就能根据构建好的提示词,生成流畅、相关且忠于原文的回答
8.2. 答案后处理
引用和溯源:在答案中标明信息来源是哪个或哪些知识胶囊,方便用户核实。NotebookLM 在这方面做得很好。
通用的模型里也就是 perplexity 的引用做得不错了。
我能搜索到 RAG 相关的知识就到这里了,鉴于笔者也是刚入行的新人,如有不对,欢迎评论区指正和交流。下一集将进行对 NLM 的具体评测,敬请期待~