RAG作为一种减少模型幻觉(Model Hallucination)并提升模型在私域知识分析与回答能力的高效方法,受到了广泛关注。然而,RAG的成功实施依赖于知识召回的准确性,这直接影响到大语言模型(LLM)的表现。那么,RAG究竟是如何工作的?在实现过程中,哪些技术手段能够确保知识召回的精准性?本文将带你深入了解这些问题,尤其是混合检索技术在其中扮演的关键角色。
单一检索的局限性:为何仅靠关键词检索不够?
在众多检索方法中,关键词检索无疑是最常用的一种。它通过匹配用户输入的关键词与文档中的关键词来返回相关结果。然而,关键词检索存在明显的局限性,这些问题在实际应用中往往难以忽视。
语义缺失:词语之间的深层联系被忽略
关键词检索的最大缺陷之一就是无法理解词语之间的语义关系。举个例子,当用户搜索“狗”时,系统可能无法识别“犬”与“狗”是同义词,导致相关文档被遗漏。这种语义上的缺失,使得关键词检索在处理同义词或近义词时表现不佳。
多义词问题:一词多义带来的困扰
另一个常见问题是多义词的处理。例如,“苹果”既可以指水果,也可以指科技公司。如果用户搜索“苹果”,系统无法确定用户的具体意图,从而可能返回不相关的结果。这种情况下,关键词检索的效果大打折扣。
实例分析:单一检索的不足
让我们通过一个具体的例子来更好地理解单一检索的局限性。假设我们有以下代码,通过关键词匹配在两个文本中寻找最相关的内容:
def main():
"""
主函数,用于测试和演示
"""
text1 = '把商品发到商铺'
text2 = '我想将商品挂到商铺'
text3 = '我想找商铺问下商品'
# calculate_tfidf_similarity: 通过两个文本TF-IDF相似度计算相似度
tfidf_similarities2 = calculate_tfidf_similarity(text1, text2)
tfidf_similarities3 = calculate_tfidf_similarity(text1, text3)
print(f"\n匹配句子1得分:{tfidf_similarities2[0]} \n\n匹配句子2得分: {tfidf_similarities3[0]} \n\n")
运行结果如下:
匹配句子1得分:0.8164965809277259
匹配句子2得分:0.8164965809277259
可以看到,尽管text1与text2在语义上更为匹配,但由于关键词“商品”和“闲鱼”在三个句子中均有出现,导致两个句子的得分相同。这说明,单纯依赖关键词匹配,无法有效区分哪些文档更符合用户需求。
混合检索:打破单一检索的束缚
为了克服单一检索的局限性,混合检索应运而生。混合检索(Hybrid Retrieval)结合了多种检索方法的优势,旨在提升检索结果的质量和多样性。通过将关键词检索与语义检索相结合,混合检索能够更精准地满足用户需求,提供更全面的检索结果。
什么是混合检索?
混合检索是一种综合运用多种检索技术的策略。它不仅依赖于传统的关键词匹配,还结合了语义理解和上下文分析等先进技术。通过这种方式,混合检索能够在保持关键词检索高效性的同时,弥补其在语义理解上的不足,从而实现更高的召回准确性。
实现混合检索的步骤
接下来,我们将通过代码示例,详细拆解混合检索的实现过程。
def main():
"""
主函数,用于测试和演示
"""
text1 = '把商品发到商铺'
text2 = '我想将商品挂到商铺'
text3 = '我想找商铺问下商品'
# 通过两个文本TF-IDF相似度计算相似度
tfidf_similarities2 = calculate_tfidf_similarity(text1, text2)
tfidf_similarities3 = calculate_tfidf_similarity(text1, text3)
# 通过两个文本的嵌入相似度计算相似度
embedding_similarities2 = calculate_similarity(text1, text2)
embedding_similarities3 = calculate_similarity(text1, text3)
print(f"\n\n语义搜索句子1 {embedding_similarities2[0]} \n\n语义搜索句子2: {embedding_similarities3[0]}")
运行结果:
语义搜索句子1 ('我想将商品挂到闲鱼', 0.8553742925917707)
语义搜索句子2: ('我想找闲鱼问下商品', 0.6846143988983046)
从结果中可以看出,语义检索在区分句子相关性方面表现出色,能够更准确地识别出与用户需求更为契合的句子。
加权计算
为了进一步提升检索的准确性,我们可以将关键词检索和语义检索的得分进行加权计算。这样,用户可以根据自身需求,调整关键词检索与语义检索的权重,从而获得最优的检索结果。
def main():
"""
主函数,用于测试和演示
"""
text1 = '把商品发到商铺'
text2 = '我想将商品挂到商铺'
text3 = '我想找商铺问下商品'
# 通过两个文本TF-IDF相似度计算相似度
tfidf_similarities2 = calculate_tfidf_similarity(text1, text2)
tfidf_similarities3 = calculate_tfidf_similarity(text1, text3)
embedding_similarities2 = calculate_similarity(text1, text2)
embedding_similarities3 = calculate_similarity(text1, text3)
Semantic_Proportio = 0.8
Word_Proportion = 0.2
# 根据传进来的权重计算最终得分
final_score2 = calculate_final_score(embedding_similarities2[0][1], tfidf_similarities2[0], Semantic_Proportio, Word_Proportion)
final_score3 = calculate_final_score(embedding_similarities3[0][1], tfidf_similarities3[0], Semantic_Proportio, Word_Proportion)
print(f"最终语句1得分: {final_score2} \n\n最终语句2得分: {final_score3}")
运行结果:
最终语句1得分: 0.8475987502589617
最终语句2得分: 0.7109908353041888
通过混合检索,系统能够更精准地识别出最符合用户需求的句子,大幅提升了检索结果的质量和准确性。
完整代码展示
为了帮助大家更好地理解混合检索的实现,以下是完整的代码示例。请注意,运行代码前需将dashscope
的API key替换为你自己的有效key。
import dashscope
from http import HTTPStatus
import numpy as np
import jieba
from jieba.analyse import extract_tags
import math
# 初始化dashscope,替换qwen的api key
dashscope.api_key = 'sk-xxxx'
def embed_text(text):
"""
使用dashscope API获取文本的嵌入向量
:param text: 输入的文本
:return: 文本的嵌入向量,如果失败则返回None
"""
resp = dashscope.TextEmbedding.call(
model=dashscope.TextEmbedding.Models.text_embedding_v2,
input=text)
if resp.status_code == HTTPStatus.OK:
return resp.output['embeddings'][0]['embedding']
else:
print(f"Failed to get embedding: {resp.status_code}")
return None
def cosine_similarity(vec1, vec2):
"""
计算两个向量之间的余弦相似度
:param vec1: 第一个向量
:param vec2: 第二个向量
:return: 余弦相似度
"""
dot_product = np.dot(vec1, vec2)
norm_vec1 = np.linalg.norm(vec1)
norm_vec2 = np.linalg.norm(vec2)
return dot_product / (norm_vec1 * norm_vec2)
def calculate_similarity(text1, text2):
"""
计算两个文本之间的相似度
:param text1: 第一个文本
:param text2: 第二个文本,可以包含多个句子,用逗号分隔
:return: 每个句子的相似度列表,格式为 (句子, 相似度)
"""
embedding1 = embed_text(text1)
if embedding1 is None:
return []
similarities = []
sentences = [sentence.strip() for sentence in text2.split(',') if sentence.strip()]
for sentence in sentences:
embedding2 = embed_text(sentence)
if embedding2 is None:
continue
similarity = cosine_similarity(embedding1, embedding2)
similarities.append((sentence, similarity))
return similarities
def extract_keywords(text):
"""
提取文本中的关键词
:param text: 输入的文本
:return: 关键词列表
"""
return extract_tags(text)
def cosine_similarity_tfidf(vec1, vec2):
"""
计算两个TF-IDF向量之间的余弦相似度
:param vec1: 第一个TF-IDF向量
:param vec2: 第二个TF-IDF向量
:return: 余弦相似度
"""
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)
return numerator / denominator if denominator else 0.0
def calculate_tfidf_similarity(text, text2):
"""
计算两个文本之间的TF-IDF相似度
:param text: 第一个文本
:param text2: 第二个文本,可以包含多个文档,用竖线分隔
:return: 每个文档的TF-IDF相似度列表
"""
documents = [doc for doc in text2.split('|') if doc.strip()]
query_keywords = extract_keywords(text)
documents_keywords = [extract_keywords(doc) for doc in documents]
query_keyword_counts = {kw: query_keywords.count(kw) for kw in set(query_keywords)}
total_documents = len(documents)
all_keywords = set(kw for doc in documents_keywords for kw in doc)
keyword_idf = {kw: math.log((1 + total_documents) / (1 + sum(1 for doc in documents_keywords if kw in doc))) + 1 for kw in all_keywords}
query_tfidf = {kw: count * keyword_idf.get(kw, 0) for kw, count in query_keyword_counts.items()}
documents_tfidf = [{kw: doc.count(kw) * keyword_idf.get(kw, 0) for kw in set(doc)} for doc in documents_keywords]
return [cosine_similarity_tfidf(query_tfidf, doc_tfidf) for doc_tfidf in documents_tfidf]
def calculate_final_score(embedding_similarity, tfidf_similarity, w1=0.5, w2=0.5):
"""
计算最终得分,结合语义相似度和TF-IDF相似度
:param embedding_similarity: 语义相似度
:param tfidf_similarity: TF-IDF相似度
:param w1: 语义相似度的权重
:param w2: TF-IDF相似度的权重
:return: 最终得分
"""
return w1 * embedding_similarity + w2 * tfidf_similarity
def main():
"""
主函数,用于测试和演示
"""
text1 = '把商品发到商铺'
text2 = '我想将商品挂到商铺'
text3 = '我想找商铺问下商品'
tfidf_similarities2 = calculate_tfidf_similarity(text1, text2)
tfidf_similarities3 = calculate_tfidf_similarity(text1, text3)
embedding_similarities2 = calculate_similarity(text1, text2)
embedding_similarities3 = calculate_similarity(text1, text3)
Semantic_Proportio = 0.8
Word_Proportion = 0.2
final_score2 = calculate_final_score(embedding_similarities2[0][1], tfidf_similarities2[0], Semantic_Proportio, Word_Proportion)
final_score3 = calculate_final_score(embedding_similarities3[0][1], tfidf_similarities3[0], Semantic_Proportio, Word_Proportion)
print(f"最终语句1得分: {final_score2} \n\n最终语句2得分: {final_score3}")
if __name__ == '__main__':
main()
通过上述代码,开发者可以轻松实现混合检索功能,结合关键词与语义检索的优势,提升知识库匹配的精准度。
总结
混合检索技术通过整合关键词检索与语义检索的优势,实现了多维度的知识召回,显著提高了检索结果的准确性和全面性。
希望本文的分享能够为你在RAG知识库的构建与优化过程中提供有价值的参考与启示。
💥 更多精彩文章:期待与您一起共同成长。✨加入我们的旅程,共同发现更多精彩!🌟🌟