在使用RAG(Retrieval Augmented Generation)检索增强技术时,会涉及到知识库的构建和文档的切分。文档切分的好坏对下游任务的效果至关重要。
文档切分粒度不好把控,既担心噪声太多,又担心语义信息丢失。
一、数据切片:RAG技术的"阿喀琉斯之踵"
朋友们,想象一下:你辛辛苦苦收集了海量数据,却因为切片不当,让你的AI变成了"结巴"或"健忘症患者"。看看下面的回答就能直观的展示大模型的回答之殇
1. 句子中断:AI变"结巴"
错误示例:
"人工智能技术正在快速" + "发展。"
这就像给AI喂了半块饼干,它能吃饱吗?当然不能!
2. 段落中断:AI得了"健忘症"
用户:公司2023年的重大项目有哪些?
AI:对不起,我没有找到相关信息。
(实际上是因为关键信息被切断了)
这就像让AI做一道缺了关键信息的填空题,它怎么可能答对?
二、如何让大模型LLM简要、准确的回答细粒度问题?
举例1:
例如下面这段文字,
「初步核算,上半年国内生产总值593034亿元,按不变价格计算,同比增长5.5%,比一季度加快1.0个百分点。分产业看,第一产业增加值30416亿元,同比增长3.7%;第二产业增加值230682亿元,增长4.3%;第三产业增加值331937亿元,增长6.4%。分季度看,一季度国内生产总值同比增长4.5%,二季度增长6.3%。从环比看,二季度国内生产总值环比增长0.8%。
农业生产形势稳定,畜牧业平稳增长
上半年,农业(种植业)增加值同比增长3.3%。夏粮生产再获丰收。全国夏粮总产量14613万吨,比上年减少127.4万吨,下降0.9%,产量居历史第二高位。上半年,猪牛羊禽肉产量4682万吨,同比增长3.6%,其中猪肉、牛肉、羊肉、禽肉产量分别增长3.2%、4.5%、5.1%、4.3%;牛奶产量增长7.5%,禽蛋产量增长2.9%。二季度末,生猪存栏43517万头,同比增长1.1%。上半年,生猪出栏37548万头,增长2.6%。」
具体举例和标准答案介绍如下:
user提问:
2023年我国上半年的国内生产总值是多少?
llm回答:
根据文档内容,2023年我国上半年的国内生产总值是593034亿元。
要求约束:
一是简要,不要废话;
二是准确、不是随意编造。
举例2:
用户提问:
根据文档内容,征信中心有几点声明?
llm回答:
根据文档内容,有四点声明,分别是:一、......;二、......;三、......;四、......。
要求约束:
要实现语义级别的分割,而不是简单的基于html或者pdf的换行符进行分割。
当前常见的问题是,文档分割不够准确,导致模型有可能只回答了两点,而实际上是因为向量相似度召回的结果是残缺的。
如果我们把切割粒度设置大一些,比如每10个段落为一块。显然这样不是最优的,因为召回片段太大,噪声也就越多。
大模型LLM本身就有幻觉问题,回答的不会很准确。因此我们的文档切片最好是按照语义切割。
解决方案:
1、技术方案
RAG的技术架构整体包含两部分,先检索,再推理。核心在检索(推荐系统),推理交给大模型进行整合就行。
检索要求:
(1)尽可能提高召回率
(2)尽可能减少无关信息
(3)速度要快。
索引构建:在进行构建索引的时候,将所有的文本组织成二级索引:
第一级索引是【关键信息】
第二级索引是【原始文本】,两者一一映射。
检索方法:
检索部分只对关键信息做embedding,参与相似度的计算,把召回的结果的映射原始文本喂给大模型LLM。
整体技术架构如下图所示:
段落分割架构图
2、构建关键信息
从上图技术架构图看到,句子、段落、文章都要关键信息,为了提高效率,可以不用对句子构建关键信息。
(1)文章的切分及关键信息的提取
关键信息:为各语义段关键信息的集合,或者是各个子标题语义扩充之后的集合。
语义切分方法1:
利用NLP的文章分析(discourse parsing)工具,提取出段落之间的主要关系,如果段落之间是从属关系,那就把包含主从关系的段落合并成一段。这样对文章切分完之后保证每一段在说同一件事情。
语义切分方法2:
除了discourse parsing的工具之外,还可以撰写算法利用BERT等模型来实现语义分割。因为BERT等模型在预训练的时候采用了NSP(next sentence prediction)的训练任务,因此BERT完全有能力能够判断两个句子(段落)是否具有语义衔接的关系。比如我们可以设置相似度阈值为ratio,从前向后依次判断相邻两个段落的相似度分数是否大于ratio,如果大于就合并,否则断开。
解释
def is_nextsent(sentence, next_sentence): encoding = tokenizer(sentence, next_sentence, return_tensors="pt", truncation=True, padding=False) with torch.no_grad(): outputs = model(**encoding, labels=torch.LongTensor([1])) logits = outputs.logits probs = torch.softmax(logits/temperature, dim=1) next_sentence_prob = probs[:,0].item() if next_sentence_prob <= ratio: return False else: return True
(2)语义段的切分及段落(句子)关键信息抽取
通过向量检索,获取语义段落之后,可以按照真实段落及句号切分,用于缓解细粒度知识点检索时大语块噪声多的场景。提取关键信息的其他思路介绍:
解决思路1:
利用NLP中的成分句法分析(constituency parsing)工具和命名实体识别(NER)工具提取,其中成分句法分析具可以提取核心部分(名词短语、动词短语......);命名实体识别(NER)工具,可以提取重要实体(货币名、人名、企业名......)。
解决思路2:
可以用语义角色标注(Semantic Role Labeling)来分析句子的谓词论元结构,提取“谁对谁做了什么”的信息作为关键信息。
解决思路3:
直接法,直接利用NLP技术中的关键词提取相关工具,提取段落中的关键信息,如HanLP工具,中文效果比较好,KeyBERT工具,英文效果好。
解决思路4:
垂直领域的方法,训练一个生成关键词的模型(KeyLLM)
在实际应用中,经常会采用基于规则的方法进行分块,即采用固定的块大小或相邻块的重叠等技术。这种基于规则的分块方法很容易导致检索上下文不完整或包含噪声的块大小过大等问题。因此,对于分块,最优雅的方法显然是基于语义的分块。语义分块旨在确保每个分块包含尽可能多的语义独立信息。
常用的基于语义分块的方法:Embedding-based、Model-based、LLM-based
LlamaIndex框架图
LlamaIndex和Langchain都提供了一个基于embedding的语义分块器。
Langchain框架
这两个框架的实现思路基本是一样的。
同学们,在这个AI智能浪潮中,掌握RAG技术就像学会了"驯服AI猛兽"的秘诀。而数据切片,就是这个秘诀中最关键的一环!
我们一定要记住:
没有最好的方法,只有最适合你的方法,高质量的数据是基础中的基础