RAG概述:
1、 流程
加载文件(网页)得到文本,然后对文本进行分割得到文本块,进行embedding,同时对于问题进行embedding,对于问题和文本的embedding求相似度,召回top-n的文本,把这些文本填充进prompt模板,输入LLM。
2、 文本分割器
① 字符文本分割器。按单个字符,通过传递的长度(字符数)来分割。Langch智算n里的CharacterTextSplitter()函数。默认以换行符作为分隔,两个参数chunk_size为文本块大小,chunk_overlap为两个块之间重叠大小。只有到有分割标志,这里是换行符的地方才会进行分割,不然就算到了chunk_size也不会分割。
② 递归字符分割器。和c不同的是,r会设定好几个分隔符,分别是双换行符、单换行符、空格、空字符,按照这个优先级去分割,谁放列表前谁的优先级就大。
③ Token分割器。LLM按照token计数,所以按照token对文本分割一般更好。TokenTextSplitter(chunk_size=1, chunk_overlap=0)
④ Tokenizer分割器。也可以用huggingface里的tokenizer来记token数,比如引入GPT2的tokenizer
⑤ 还可以使用NLTK、SPACY(NLP库,可以借助它完成文本分类、分词等基本任务)利用tokenizer来分
⑥ 可以使用Tiktoken来分(open智算发布的分词工具,可能对open智算的模型更有效)
⑦ Latex、markdown、pythoncode都有自己的分割方式
3、 编码技术(embedding)(embedding召回只是召回的一种方式,还有搜索召回等)
① 衡量embedding效果的榜单:MTEB、C-MTEB。一个benchmark衡量embedding效果,包括8个任务:跨语言双文本挖掘、分类(classfication)(对文本进行分类)、聚类、排序(retrival)(信息检索排名)、文档重排(reranking)(能否根据query,正确区分正样本和负样本)、语义文本相似度(STS)(判断两个文本对是不是相关)、对分类(p智算r-classification)(给定一堆文本,判断是不是有相同含义)。
STS任务中最好的是(voyaga、google、SFR、mxb智算),最后一个开源且小
② Word2vec经典模型,基于神经网络,将单词映射到向量空间中。Word2Vec包括两种架构:CBOW和Skip-gram。CBOW通过上下文预测中心单词,skip-gram通过中心单词预测上下文单词。
③ GloVe:使用统计方法计算单词之间的关联性,通过SVD生成嵌入,更快。
④ Open智算的embeddings:text-embedding-ada-002,Open智算的官方api接口。
⑤ Aleph Alpha 对于结构不同的文本(文档和查询),用不对称嵌入,相反用对称嵌入。
⑥ Sentence transformers通过预训练好的embedding模型来编码
4、 相似性检索
① 最暴力的方法是计算query的embedding与所有存储块向量的距离。搜索索引应采用向量索引,可以用f智算ss、annoy库等搜索,具体搜索算法包括knn、聚类等。Llam智算ndex支持多种向量存储索引,也兼容其他索引类型,比如列表索引。
② BM25算法是一种经典信息检索算法,估计文档D与用户查询Q的相关性。处理文档和短查询时表现出色。核心思想是基于词频(TF)和逆文档频率(IDF)来,同时引入了长文档的长度信息计算D与Q的相关性。TF是查询中的词q_i在D中出现的频率,IDF衡量一个词的信息量,如果一个词在很多文档中出现他的信息量就少,IDF就小。平均文档长度用来标准化不同文档的长度,公平比较不同长度的文档。
③ MMR,最大边际相关算法,增加推荐结果多样性,用于精排后处理阶段,即rerank重排阶段。MR=物品本身价值-多样性分数,价值可以理解为相似度。
④ 分层索引。创建两个索引,第一个由块的摘要组成,第二个由文档块组成,先通过摘要过滤一部分文档,然后只在相关文档搜索。
⑤ 假设性问题。让LLM为每个块生成一个问题,将这些问题embedding,然后对于块问题的索引与总体问题索引求相似度,然后把问题相似度最高的文本块传回去。
⑥ HYDE。先让LLM无背景知识给出一个响应,再让这个响应和query一起提高搜索质量。
⑦ 语句窗口检索器。每个句子单独嵌入,将最相关的句子的前后k个句子作为上下文一起发送给LLM。
⑧ 自动合并检索器(父文档搜索器)。检索更小的块,如果前k个检索的结果中有超过n个块链接到同一个父节点。将这个父节点替换成给LLM的上下文。
⑨ 融合搜索。结合传统的基于关键字的搜索(如tf-idf或搜索行业标准BM25)和现代语义、向量搜索,将结果组合在一个搜索结果中。在langch智算n中通过ensemble retriever来实现。这个算法通过Reciprocal Rank Fusion(RRF)(倒数排名融合)将两个算法融合。这个算法是比如id_1在bm25里排第2,在tf-idf里排第3,最后他的得分就剩1/(k+2)+1/(k+3),这个k是个超参数
5、 粗排
RAG from scratch(hub.ba智算.ac.cn/view/37287)
1、 query transformations(问题重构)
查询转换,修改问题以便检索
1.1 Multi Query多查询策略
从多个角度重写用户问题,为每个问题检索文档,返回所有查询的唯一文档。
也就是说把问题Q重写成Q1、Q2、Q3,然后分别得到一个reference,再把这些查询进行唯一并集,得到更丰富的结果集
1.2 RAG-Fusion 多查询结果融合策略
从多个角度重写问题,组合多个搜索结果排名
1.3 Decomposition问题分解策略
将一个问题分解成多个问题,向下分解,可以串行(使用第一个问题答案+检索回答第二个问题),也可以并行(每个答案合并为最终答案)
1.3.1 Answer recursively迭代式回答
问题分解的基础上逐步迭代答案,将上个问题答案作为下个问题的背景知识输出,直到输出到最后一个问题
1.3.2 Answer individually
每个子问题分别处理得到答案,再拼接成一个QA p智算rs得到答案
1.4 step back
先提示LLM一个高级概念,并检索有关它们的事实,使用此基础帮助回答用户问题。得到前置问题及答案以后,将其整体与当前问题合并,得到最终答案(比如先问一下这个问题用到的物理公式是什么,然后再输入模型)
(可以用几个few_shot_prompt来举例)
1.5HyDE混合
LLM搜索query与doc,但是不对称,可以先根据query生成doc,再根据doc生成对应embedding
2 Routing 路由优化策略
有很多种数据来源(向量数据库、图数据库、mysql数据库),根据问题找从哪些数据来源提取数据(比如java数据库和python数据库是两种来源,然后问你python的问题让你溯源)
2.1逻辑路由
使用LLM溯源
2.2基于语义实现分发
利用语义相似度路由,将query路由到最相关的prompt(每个向量库一个prompt)
3 Query Construction 问题构建优化策略
用LLM将自然语言query转化成结构化语言query,比如query: videos on chat langch智算n pubished after 2024转化成 content_search:chatch智算n earliest_publish_data:2023-01-01。转化之后的query信息更明确,更利于LLM去过滤和提取某些知识。
3.1 text-to-metadata-filter (datastore是vectorstore,数据unstructured)
Vectorstore具备metadata filtering。将query分解成两部分,第一部分是query(用来检索),第二部分是filter(用来过滤)
3.2 text-to-sql
自然语言转化为SQL请求。
问题:①幻觉:LLM会出现不存在的表或字段②用户拼写错误或不规范输入
应对方法:①数据库描述:给LLM提供每个表的描述(列名、类型等)以及三行示例
②少样本示例:添加少量样本作为示例
③错误处理:可以使用SQL Agent类似工具从错误中恢复
④发现拼写错误的专有名词:查询专有名词拼写错误时,允许模型在vectorstore搜索正确名词。
3.3 text-to-sql+semantic
Pgvector使得向量列上执行相似度变的可能,并使用<—>运算符实现文本到语义搜索。
3.4 text-to cypher
图数据库使用特定查询语言cypher,cypher中使用ASCII艺术的语法描述节点之间的关系,比如
描述一个节点有LIVES_IN关系指向另一个节点
可以使用图数据库查询链(GraphCypherQACh智算n)和LLM将自然语言query转化为Cypher query
4、Indexing不同索引优化策略
4.1 Multi-representation indexing
对每个文档生成一个摘要,用摘要去匹配
4.2 RAPTOR 层级性索引方案
对文档先进行聚类,对每个聚类提取一份摘要,然后再聚类再提取摘要,把每一层聚类的文档都存入向量库,可以回答从详细到高级的问题。
4.3 COlBERT token到text级分级索引
为段落中每个token生成一个受上下文影响的向量,为查询的每个token也生成向量,然后每个文档的得分就是每个查询嵌入与任意文档嵌入的最大相似度之和。(若有3个query,一个doc,doc的每个token都和三个query做相似度,取最大相似度,每个token的最大相似度做和就是doc的分数。
5、 Retrieval检索优化策略
5.1 re-ranking
属于后处理策略,对于检索到的tok-k文档,再进行重排序,rerank模型使用问题和文档作为输入,不需要embedding,典型模型有bge-reranker-v2-m3
5.2 Retrieval(CRAG)
本质上是一种adaptive-RAG策略,在retrieve阶段之后判断搜索到的文档的相关性怎么样,如果相关性不够,重写问题重新进行检索,如果有至少一个文档超过了相关性阈值,进入生成阶段。
生成之前,将知识细化,将文档划分为知识条带,对每个知识条分级,过滤掉不相关知识条。如果所有文档都地狱相关性阈值,框架搜索额外数据源,或用网络搜索补充检索。
6、Generation优化策略
6.1 Self-RAG
使用循环单元自行纠正RAG错误,以检查文档相关性、答案幻觉和答案质量
设单次query是x1,单次检索到的总文档是D1,d1是其中一个文档,d1作为背景知识给LLM生成y1
然后对检索质量进行评估。分三方面
① 需要判断d1与问题x1是否相关
② 需要判断y1是不是由d1支持的
③ 需要判断y1的生成质量
选出三方面综合最好的y1,将其视作x2,再进行循环。
6.2 Impact of long context
RAG任务的上下文比较长,所以需要考虑长上下文的影响,上下文中最经典的任务是大海捞针。检索很难,但是从上下文中推理出正确答案更难。
可以使用长文本embeddings,比如M2-BERT-Retrieval-32K
Llama3+LangCh智算n外挂公主链接知识库:
注意,本部分来自本人github,如有质疑,可联系本人进行验证。再跑一次测试也ok,但是比较耗时哈哈。
langch智算n 文档
安装依赖库
!pip install -U langch智算n unstructured nltk sentence_transformers f智算ss-gpu
外挂知识库 & 向量存储
from langch智算n.document_loaders import UnstructuredFileLoader, JSONLoader
from langch智算n.text_splitter import RecursiveCharacterTextSplitter
from langch智算n.embeddings.huggingface import HuggingFaceEmbeddings
from langch智算n.vectorstores import F智算SS
def load_knowledge_base(filepath):
# 加载外部知识库
loader = UnstructuredFileLoader(filepath, separator="\n") # 把带格式的文本,读取为无格式的纯文本
# loader = JSONLoader(filepath, jq_schema='.[]')
docs = loader.load()
# 对读取的文档进行chunk
text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=10)
docs = text_splitter.split_documents(docs)
return docs
def load_vector_store(model_name, filepath=None, from_documents=True):
# 使用text2vec模型,对上面chunk后的doc进行embedding。然后使用F智算SS存储到向量数据库
embeddings = HuggingFaceEmbeddings(model_name=model_name, #"/root/autodl-tmp/text2vec-large-chinese",
model_kwargs={'device': 'cuda'})
# 注意:如果修改了知识库(knowledge.txt)里的内容,则需要把原来的 my_f智算ss_store.f智算ss 删除后,重新生成向量库。
if from_documents:#"/root/autodl-tmp/knowledge.txt"
docs = load_knowledge_base(filepath)
vector_store = F智算SS.from_documents(docs, embeddings)
else:#"/root/autodl-tmp/my_f智算ss_store.f智算ss"
vector_store = F智算SS.load_local(filepath, embeddings=embeddings)
return vector_store
问题/向量检索
def create_inputs(query, vector_store):
# 向量检索:通过用户问句,到向量库中,匹配相似度高的文本
docs = vector_store.similarity_search(query) # 计算相似度,并把相似度高的chunk放在前面
context = [doc.page_content for doc in docs] # 提取chunk的文本内容
# 4.2.7 构造prompt_template
my_input = "\n".join(context)
input = f"已知: \n{my_input}\n请回答: {query}"
# print(input)
return input
测试,ShopBenchBaseModel 见 base_model.py
选择轻量级的 all-MiniLM-L6-v2 作为 Embedding 编码的模型
if __name__ == "__m智算n__":
ckpt_dir = "/home/jim/nas/lilxiaochen/kdd_cup_v2/models/llama3/Meta-Llama-3-8B-Instruct"
text2vec_model = "/media/ssd/yzg/all-MiniLM-L6-v2"
knowledge_file = "/media/ssd/yzg/data/dual_data.txt"
json2txt("/media/ssd/yzg/data/dual_data.json", knowledge_file)
vector_store = load_vector_store(text2vec_model, knowledge_file)
model = ShopBenchBaseModel(ckpt_dir)
# query = "公会是破晓之星的角色的名字"
instruction = "根据已知的信息回答问题。如果信息不足,无法回答,则回复不知道。"
while True:
query = input("query: ")
if query == "q":
break
inputs = create_inputs(query, vector_store)
response = model.predict(instruction, inputs)
print(response)