【AIGC】AI如何匹配RAG知识库:混合检索

在这里插入图片描述

混合搜索

  • 引言
  • 单检索的局限性
    • 单检索例子
  • 混合检索
    • 拆解实现
    • 完整代码
  • 总结

引言

RAG作为减少模型幻觉和让模型分析、回答私域相关知识最简单高效的方式,我们除了使用之外可以尝试了解其是如何实现的。在实现RAG的过程中,最重要的是保证召回的知识的准确性,不然会极大影响LLM的能力,而混合检索是一个重要的方法去提高召回RAG的准确性。

单检索的局限性

拿最常用的关键词检索举例,它通过匹配用户输入的关键词与文档中的关键词来返回相关结果。然而,关键词检索存在以下几个明显的局限性:

  • 语义缺失:关键词检索无法理解词语之间的语义关系。例如,用户搜索“狗”时,系统可能无法理解“犬”与“狗”是同义词,从而导致相关文档被遗漏。
  • 多义词问题:关键词检索无法处理多义词。例如,“苹果”既可以指水果,也可以指科技公司。如果用户搜索“苹果”,系统无法确定用户指的是哪一种含义,从而返回不相关的结果。
    在这里插入图片描述

单检索例子

比如下面这段代码,我们使用text1去在text2和text3中匹配最符合的文档:

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")

获取到的结果:

匹配句子2得分:0.8164965809277259 

匹配句子3得分:0.8164965809277259 

可以看到我们肉眼可见的text1与text2更匹配,但因为三个句子中都包含商品闲鱼,所以两个句子都匹配到了0.8164965809277259,我们但从关键词匹配根本无法分辨召回哪个文本更好,但是关键词检索并不是一无是处,对很多文档的检索都有关键功能,而在需要保留关键词检索的同时又能分辨这种句子,我们就需要引入语义检索,让他们两种方法工作达到混合检索的功能。
在这里插入图片描述

混合检索

混合检索(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

ok,可以看到我们通过混合检索的方式,可以更准确地找到与用户输入最相关的文档,从而提高检索结果的质量和准确性。
在这里插入图片描述

完整代码

关键词检索和语义检索的详细实现之前我的文章已经提过了,这里不在赘述,直接贴完整代码。注意把key换成qwen中你自己的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检索有着极大帮助,希望本文能对你有启示。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/899490.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

红日靶场(三)1、环境介绍及环境搭建

1、靶场介绍 红日靶场03是一个用于安全测试和渗透测试的虚拟化环境,可以帮助用户通过模拟攻击和防御场景来提升网络安全技能。该靶场包含了多个虚拟机和网络配置,用户可以在其中进行各种安全测试活动,如信息收集、漏洞利用、权限提升等。 2…

npm install node-sass安装失败

需求:搭建前端开发环境时,npm install报错,错误提示安装node_modules时,node-sass依赖包安装失败,网上找了好久解决方法,大家提示采用淘宝源等方式安装,都失败了了,尝试了很久终于找…

BUUCTF 之Basic 1(BUU BRUTE 11)

今天我们继续BUUCTF之Basic 1的第二关卡。 1、老规矩,进入地址BUUCTF在线评测 (buuoj.cn)打开对应靶场进行启动,会看一个页面,就代表启动成功。 首先分析一下,看到这个页面我们就可以得出是爆破的题目,常用于登陆&am…

1208. 尽可能使字符串相等

Problem: 1208. 尽可能使字符串相等 题目描述 给定两个相同长度的字符串 s 和 t,将字符串 s 转换为字符串 t 需要消耗开销,开销是两个字符的 ASCII 码差值的绝对值。还有一个最大预算 maxCost,我们需要在这个预算范围内,找到 s 中…

基于知识图谱的诗词推荐系统

你是否曾经想在浩如烟海的古代诗词中找到属于自己的那几首“知己”?现在,借助人工智能与知识图谱,古典诗词不再是玄之又玄的文本,而是变成了让你“个性化定制”的文化体验!我们带来的这款基于知识图谱的诗词推荐系统&a…

我准备写一份Stable Diffusion入门指南-part1

我准备写个SD自学指南,当然也是第一次写,可能有点凌乱,后续我会持续更新不断优化,我是生产队的驴,欢迎监督。 Stable Diffusion WebUI 入门指南 Stable Diffusion WebUI 是一款基于 Stable Diffusion 模型的用户界面…

SIP 业务举例之 Transfer - Unattended(无人值守呼叫转移)

目录 1. Transfer - Unattended 简介 2. IP Telephony 特性 3. RFC5359 的 Transfer - Unattended 信令流程 无人值守呼叫转移 隐式订阅 Bob 通知 Alice 呼叫转移完成 - NOTIFY 隐含的订阅和显示的订阅 4. Transfer - Unattended 过程总结 博主wx:yuanlai45_csdn 博主…

重写 CSS Flexible Box

一、是什么? Flex 是 Flexible Box 的缩写, 意为 弹性布局, 用来为盒状模型提供更为灵活的布局能力, 它给 Flexbox 的 子元素 之间提供了强大的 空间分布(伸缩) 和 对齐 能力 二、基础概念 2.1 容器 采用 Flex 布局的元素 (设置了 display: flex | inline-flex 的元素) 称…

轻松上手 Disruptor:两个实例解析并发编程利器

Disruptor 是英国外汇交易公司 LMAX 开发的一个高性能队列。很多知名开源项目里,比如 canal 、log4j2、 storm 都是用了 Disruptor 以提升系统性能 。 这篇文章,我们通过两个例子一步一个脚印帮助同学们入门 Disruptor 。 1 环形缓冲区 下图展示了 Di…

详解Oracle审计(一)

题记: 有段时间没写过oracle了,今天回归。 本文将详细介绍oracle的审计功能,基于11g版本,但对12c,19c也同样适用。 审计(Audit)用于监视用户所执行的数据库操作,并且 Oracle 会将审…

hadoop的yarn

1.分布式的资源调度-yarn(hadoop的一个组件) 资源服务器硬件资源,如:CPU,内存,硬盘,网络等 资源调度:管控服务器硬件资源,提供更好的利用率 分布式资源调度:管控整个分布式服务器集群的全部资源,整合进行统一调度 总结就是使用yar…

chatGpt4.0Plus,Claude3最新保姆级教程开通升级

如何使用 WildCard 服务注册 Claude3 随着 Claude3 的震撼发布,最强 AI 模型的桂冠已不再由 GPT-4 独揽。Claude3 推出了三个备受瞩目的模型:Claude 3 Haiku、Claude 3 Sonnet 以及 Claude 3 Opus,每个模型都展现了卓越的性能与特色。其中&a…

Blazor WebAssembly 项目部署时遇到 500.19错误

这个错误其实很普遍,在部署 asp.net core 的时候都能解决 无非是安装 这些, 尤其是下面那个 Hosting Bundle 但是遇到 Blazor WebAssembly 项目部署时还得多装一个 “重写模块” 下载地址,安装后重启网址 https://www.iis.net/downloads/microsoft/u…

【从零开始的LeetCode-算法】3185. 构成整天的下标对数目 II

给你一个整数数组 hours&#xff0c;表示以 小时 为单位的时间&#xff0c;返回一个整数&#xff0c;表示满足 i < j 且 hours[i] hours[j] 构成 整天 的下标对 i, j 的数目。 整天 定义为时间持续时间是 24 小时的 整数倍 。 例如&#xff0c;1 天是 24 小时&#xff0c…

使用Vue指令实现面板拉伸功能

目的&#xff1a;现在有一个PDF预览页面&#xff0c;左侧为PDF预览区域&#xff0c;右侧为可以进行AI功能的面板。现在想让AI面板通过拖动左边框实现面板拉伸。 实现效果如下面的视频&#xff1a; 关键点&#xff1a; 预览区是使用iframe渲染的&#xff0c;在拖动的过程中&…

软件测试学习笔记丨Selenium键盘鼠标事件ActionChains

本文转自测试人社区&#xff0c;原文链接&#xff1a;https://ceshiren.com/t/topic/22515 本文为霍格沃兹测试开发学社的学习经历分享&#xff0c;写出来分享给大家&#xff0c;希望有志同道合的小伙伴可以一起交流技术&#xff0c;一起进步~ 说明&#xff1a;本篇博客基于sel…

Detecting Holes in Point Set Surfaces 论文阅读

下载链接 Detecting Holes in Point Set Surfaces 摘要 3D 数据采集过程&#xff08;例如激光范围扫描&#xff09;产生的重要物体模型通常包含由于遮挡、反射或透明度而产生的孔洞。本文的目标就是在点集表面上检测存在的孔洞。对于每个点&#xff0c;将多个标准组合成一个综…

C# shader 生成程序纹理

1、程序纹理是什么 程序纹理&#xff08;Procedural Textures&#xff09;就是通过程序代码生成的纹理 2、程序纹理如何生成 一般生成程序纹理由两种方式&#xff1a; 通过C#脚本生成纹理后传递给Shader直接在Shader代码中自定义逻辑生成纹理 3、程序纹理的好处 程序纹理…

2.1 > Shell 是什么、如何更熟练的使用 Bash Shell

Shell 基础知识 Shell是计算机操作系统中的一个命令行解释器&#xff0c;由C语言编写&#xff0c;用于用户与操作系统之间进行交互。用户可以通过Shell输入命令&#xff0c;操作系统接收到这些命令后执行相应的操作。Shell一般还提供了编程语言的基本功能&#xff0c;允许用户…

梯度累积的隐藏陷阱:Transformer库中梯度累积机制的缺陷与修正

在本地环境下对大规模语言模型&#xff08;LLMs&#xff09;进行微调时&#xff0c;由于GPU显存限制&#xff0c;采用大批量训练通常难以实现。为解决此问题&#xff0c;一般普遍会采用梯度累积技术来模拟较大的批量规模。该方法不同于传统的每批次更新模型权重的方式&#xff…