大模型的缺陷:
知识局限性:缺少垂直领域、非公开知识。
知识实时性:训练周期长、成本高。
幻觉问题:模型生成的内容与现实世界事实或用户输入不一致。
优化大模型缺陷最简单的方式:
In-Context Learning
。通过给大模型提供更丰富及时的信息,让大模型去推理,得到正确的答案。

检索增强生成是指对大型语言模型输出进行优化:
- 使其能够在生成响应之前引用训练数据来源之外的权威知识库。
RAG在知识库问答情景中,将检索到的相关知识和用户问题重新组织回复问题。
整体框架:

通用架构:
RAG
的主要组成:数据提取、向量化、创建索引、检索、自动排序和LLM
归纳生成。

关键词搜索
AI如何匹配RAG知识库:
在实现RAG的过程中,有语义搜索也有关键词搜索,用jieba库以及TF-IDF实现关键词搜索RAG。
jieba库简介:
jieba(结巴)是一个在Python中广泛使用的分词库,特别适用于中文文本处理。
jieba库不仅支持基本的分词功能,还提供了关键词提取、词性标注、命名实体识别等多种功能。
在关键词检测领域,jieba库的TF-IDF和TextRank算法被广泛应用于提取文本中的关键词。
TF-IDF简介:
TF-IDF是一种用于信息检索和文本挖掘的常用加权技术。
它通过计算词汇在文档中的频率(TF)和在整个语料库中的逆文档频率(IDF),来评估词汇的重要性和相关性。
简单来说关键词出现的次数越多且存在于其他文档中的频率越低,那么这个关键词就越重要。
基本过程:
关键词匹配的缺点也很明显,就是关键词匹配的召回率很低,因为关键词匹配只匹配了关键词,而没有匹配到关键词的上下文。
- 所以一般需要结合语义搜索去一起进行。
使用jieba库去进行分词,然后通过TF-IDF算法去计算关键词的权重,然后通过余弦相似度去计算文档和查询的相似度。
最后通过相似度去排序,返回结果。
基本上这是一个比较通用的关键词匹配RAG数据库的方法。
举例:
模拟用户询问问题,模型根据问题从知识库中检索出相关文档,并根据检索到的文档生成回答。
假设用户输出是text1,发到顺丰,text2中是多个文档,以”;”隔开。
使用jieba库实现关键词搜索RAG,搜索text2中最适配text1的文档。
# Example text
text = "发到顺丰"
# Example text2
text2 = "您好,是您拨打的客服电话吗;你好,我的这个货想要通过顺丰去发;订单号发我一下;xxxxxx;好的我这边给您发顺丰"
用jieba库提取关键词:
# 切割 text2 并将其作为文档
documents = text2.split(';')
# 提取关键词的函数
def extract_keywords(text):
return jieba.analyse.extract_tags(text)
# 提取查询关键词
query_keywords = extract_keywords(text)
# 提取文档关键词
documents_keywords = [extract_keywords(doc) for doc in documents]
计算TF-IDF:
# 计算查询关键词的词频 (TF)
query_keyword_counts = Counter(query_keywords)
# 总文档数
total_documents = len(documents)
# 计算所有关键词的逆文档频率 (IDF)
all_keywords = set()
for doc_keywords in documents_keywords:
all_keywords.update(doc_keywords)
keyword_idf = {}
for keyword in all_keywords:
doc_count_containing_keyword = sum(1 for doc_keywords in documents_keywords if keyword in doc_keywords)
keyword_idf[keyword] = math.log((1 + total_documents) / (1 + doc_count_containing_keyword)) + 1
# 计算查询关键词的 TF-IDF
query_tfidf = {}
for keyword, count in query_keyword_counts.items():
tf = count
idf = keyword_idf.get(keyword, 0)
query_tfidf[keyword] = tf * idf
# 计算所有文档的 TF-IDF
documents_tfidf = []
for doc_keywords in documents_keywords:
doc_keyword_counts = Counter(doc_keywords)
doc_tfidf = {}
for keyword, count in doc_keyword_counts.items():
tf = count
idf = keyword_idf.get(keyword, 0)
doc_tfidf[keyword] = tf * idf
documents_tfidf.append(doc_tfidf)
计算文档和查询相似度:
# 计算余弦相似度
def cosine_similarity(vec1, vec2):
intersection = set(vec1.keys()) & set(vec2.keys())
numerator = sum(vec1[x] * vec2[x] for x in intersection)
sum1 = sum(vec1[x] ** 2 for x in vec1)
sum2 = sum(vec2[x] ** 2 for x in vec2)
denominator = math.sqrt(sum1) * math.sqrt(sum2)
if not denominator:
return 0.0
else:
return float(numerator) / denominator
# 计算文档与查询的相似度
similarities = []
for doc_tfidf in documents_tfidf:
similarity = cosine_similarity(query_tfidf, doc_tfidf)
similarities.append(similarity)
# 按相似度排序并返回结果
sorted_documents = sorted(zip(documents, similarities), key=lambda x: x[1], reverse=True)
# 打印结果
for i, (doc, score) in enumerate(zip(documents, similarities)):
print(f"Document {i+1}: {doc}\nScore: {score}\n")
结果:
Score得分越高,则文档越匹配查询词,可以看到,根据关键词搜索,找到了最适配text1的文档,Document 2。
Document 1: 您好,是您拨打的客服电话吗
Score: 0.0
Document 2: 你好,我的这个货想要通过顺丰去发
Score: 0.4472135954999579
Document 3: 订单号发我一下
Score: 0.0
Document 4: xxxxxx
Score: 0.0
Document 5: 好的我这边给您发顺丰
Score: 0.0
完整代码:
import jieba
from jieba.analyse import default_tfidf
from collections import Counter
import math
# Example text
text = "发到顺丰"
# Example text2
text2 = "您好,是您拨打的客服电话吗;你好,我的这个货想要通过顺丰去发;订单号发我一下;xxxxxx;好的我这边给您发顺丰"
# 切割 text2 并将其作为文档
documents = text2.split(';')
# 提取关键词的函数
def extract_keywords(text):
return jieba.analyse.extract_tags(text)
# 提取查询关键词
query_keywords = extract_keywords(text)
# 提取文档关键词
documents_keywords = [extract_keywords(doc) for doc in documents]
# 计算查询关键词的词频 (TF)
query_keyword_counts = Counter(query_keywords)
# 总文档数
total_documents = len(documents)
# 计算所有关键词的逆文档频率 (IDF)
all_keywords = set()
for doc_keywords in documents_keywords:
all_keywords.update(doc_keywords)
keyword_idf = {}
for keyword in all_keywords:
doc_count_containing_keyword = sum(1 for doc_keywords in documents_keywords if keyword in doc_keywords)
keyword_idf[keyword] = math.log((1 + total_documents) / (1 + doc_count_containing_keyword)) + 1
# 计算查询关键词的 TF-IDF
query_tfidf = {}
for keyword, count in query_keyword_counts.items():
tf = count
idf = keyword_idf.get(keyword, 0)
query_tfidf[keyword] = tf * idf
# 计算所有文档的 TF-IDF
documents_tfidf = []
for doc_keywords in documents_keywords:
doc_keyword_counts = Counter(doc_keywords)
doc_tfidf = {}
for keyword, count in doc_keyword_counts.items():
tf = count
idf = keyword_idf.get(keyword, 0)
doc_tfidf[keyword] = tf * idf
documents_tfidf.append(doc_tfidf)
# 计算余弦相似度
def cosine_similarity(vec1, vec2):
intersection = set(vec1.keys()) & set(vec2.keys())
numerator = sum(vec1[x] * vec2[x] for x in intersection)
sum1 = sum(vec1[x] ** 2 for x in vec1)
sum2 = sum(vec2[x] ** 2 for x in vec2)
denominator = math.sqrt(sum1) * math.sqrt(sum2)
if not denominator:
return 0.0
else:
return float(numerator) / denominator
# 计算文档与查询的相似度
similarities = []
for doc_tfidf in documents_tfidf:
similarity = cosine_similarity(query_tfidf, doc_tfidf)
similarities.append(similarity)
# 按相似度排序并返回结果
sorted_documents = sorted(zip(documents, similarities), key=lambda x: x[1], reverse=True)
# 打印结果
for i, (doc, score) in enumerate(zip(documents, similarities)):
print(f"Document {i+1}: {doc}\nScore: {score}\n")
语义搜索
RAG通过Embedding去完成语义分析匹配的。
- Embedding是一种将高维数据映射到低维空间的技术。
在NLP中,Embedding通常用于将单词、句子或文档转换为连续的向量表示。
- 这些向量不仅保留了原始数据的关键信息,还能够在低维空间中捕捉到语义上的相似性。
简单来说,就是机器无法直接识别人类的语言,所以需要通过Embedding去转化成机器能够理解和处理的数值形式。
比如:”猫”和”狗”由于都是动物,所以它们的Embedding向量在空间上比较接近。
- 而”猫”和”书”由于语义上没有直接关系,所以它们的Embedding向量在空间上距离较远。
机器就是通过这样去理解人类语言的。
RAG知识库与Embedding的结合
通过使用Embedding技术,我们可以将知识库中的文档和查询语句转换为向量表示。
- 这样,我们就可以利用向量之间的相似度来实现更精确的匹配。
具体来说,我们可以通过计算查询向量与知识库中每个文档向量的余弦相似度,来确定最相关的文档。
- 也就是类似上面提到的通过计算能用”猫”匹配出”狗”,而不是”书”。
而通过这个我们就可以用用户输入的句子去匹配我们的数据库找到最相关的文档,从而实现RAG。
实践案例
用spacy库进行embedding,并计算相似度。
import spacy
nlp = spacy.load('zh_core_web_sm')
def contains_phrase_nlp(text, phrase):
doc = nlp(text)
phrase_doc = nlp(phrase)
for sent in doc.sents:
similarity = phrase_doc.similarity(sent)
return similarity
if __name__ == '__main__':
text1 = '一起去运动吧'
text2 = '一起去踢足球吧'
text3 = '一起去坐飞机吧'
similarities1 = contains_phrase_nlp(text1, text2)
similarities2 = contains_phrase_nlp(text1, text3)
print(f"Similarity1: {similarities1} \nSimilarity2: {similarities2}")
结果:
Similarity1: 0.8225547086542833
Similarity2: 0.8069693841904839