增强FAQ搜索引擎:发挥Elasticsearch中KNN的威力

英文原文地址:https://medium.com/nerd-for-tech/enhancing-faq-search-engines-harnessing-the-power-of-knn-in-elasticsearch-76076f670580

增强FAQ搜索引擎:发挥Elasticsearch中KNN的威力

2023 年 10 月 21 日

在一个快速准确的信息检索至关重要的时代,开发强大的搜索引擎是至关重要的。随着大型语言模型(LLM)和信息检索体系结构(如RAG)的出现,在现代软件系统中利用文本转向量(Embeddings)和向量数据库已经变得非常流行。我们深入研究了如何利用Elasticsearch的k近邻(KNN)搜索和来自强大语言模型的文本Embedding的细节,这是一个强有力的组合,有望彻底改变我们访问常见问题(FAQ)的方式。通过对Elasticsearch的KNN功能的全面探索,我们将揭示这种集成如何使我们能够创建一个前沿的FAQ搜索引擎,通过理解查询的语义上下文,以闪电般的延迟增强用户体验。

在开始设计解决方案之前,让我们先了解信息检索系统中的一些基本概念。

文本转向量(Embeddings)

The best way to describe an embedding is explained in this article.

Embedding是一条信息的数字表示,例如,文本、文档、图像、音频等。它的特点在于我们不需要完全通过字面值(通俗一点就是关键字)来检索内容,而是可以通过相近的意思来检索。

语义检索

传统的搜索系统使用词法匹配来检索给定查询的文档。语义搜索旨在使用文本表示(Embedding)来理解查询的上下文,以提高搜索准确性。

语义搜索的类型

对称语义搜索:查询和搜索文本长度相似的搜索用例。例如,在数据集中找到类似的问题。

不对称语义搜索:查询和搜索文本长度不同的搜索用例。例如,为给定查询查找相关段落。

译注:如果你用的chunking基于句向量,那么可以认为就是对称的。

向量搜索引擎(向量数据库)

img

向量搜索引擎是专门的数据库,可用于存储非结构化信息,如图像、文本、音频或视频经过Embedding之后的向量。在这里,我们将使用Elasticsearch的向量搜索功能。现在我们先来了解搜索系统的构建流程,让我们深入了elasticsearch的体系结构和实现。

解决方案架构

img
  1. 搜索解决方案的第一步是将问答对索引到Elasticsearch中。我们将创建一个索引,并将问题和答案Embedding存储在同一个索引中。我们将根据检索的特征使用2个独立的模型来Embedding问题和答案。
  2. 我们将使用步骤1中使用的相同模型嵌入查询,并形成搜索查询(3部分,即问题,答案,词汇搜索),将查询Embedding映射到各自的问题和答案Embedding。
  3. 我们还将为查询的每个部分提供一个boost值,以表示它们在组合中的重要性。返回的最终结果基于分数的总和乘以各自的boost值进行排序。

环境设置

设置索引。您可以使用以下映射作为起点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 1
  },
  "mappings": {
    "properties": {
      "Question": { "type": "text" },
      "Answer": { "type": "text" },
      "question_emb": {
        "type": "dense_vector",
        "dims": 768,
        "index": true,
        "similarity": "dot_product"
      },
      "answer_emb": {
        "type": "dense_vector",
        "dims": 1024,
        "index": true,
        "similarity": "dot_product" 
      }
    }
  }
}

模型选择

由于我们使用一种相当常见的语言处理数据,因此为了进行这个实验,我从Retrieval(用于答案)和STS(用于问题)部分的MTEB排行榜中选择了表现最好的模型。

选择模式:

  1. 答案(Retrieval):BAAI/big -large-en-v1.5(您可以使用quantized版本进行更快的推理)
  2. 提问(STS):thenlper/gte-base

如果您有特定于领域的常见问题解答,并且想要检查哪个模型表现最好,您可以使用Beir:https://github.com/beir-cellar/beir。查看https://github.com/beir-cellar/beir/wiki/Load-your-custom-dataset,该部分描述了如何加载您的自定义数据集进行评估。

实现

为了这个实验的目的,我将使用Kaggle的一个心理健康常见问题解答(https://www.kaggle.com/datasets/narendrageek/mental-health-faq-for-chatbot/)数据集。

  • 加载数据集
1
2
import pandas as pd
data = pd.read_csv('Mental_Health_FAQ.csv')
  • 生成Embedding
1
2
3
4
from sentence_transformers import SentenceTransformer
question_emb_model = SentenceTransformer('thenlper/gte-base')

data['question_emb'] = data['Questions'].apply(lambda x: question_emb_model.encode(x, normalize_embeddings=True))

注:我们将Embedding归一化,使用点积代替余弦相似度作为相似度度量。计算速度更快,推荐参考Elasticsearch的Dense Vector Field(https://www.elastic.co/guide/en/elasticsearch/reference/8.8/dense-vector.html)文档。

1
2
3
answer_emb_model = SentenceTransformer('BAAI/bge-large-en-v1.5')

data['answer_emb'] = data['Answers'].apply(lambda x: answer_emb_model.encode(x, normalize_embeddings=True))
  • 索引文件

我们将使用Elasticsearch辅助函数。具体来说,我们将使用streaming_bulk API来索引文档。

首先,让我们实例化elasticsearch python客户端。

1
2
3
4
5
6
7
8
9
from elasticsearch import Elasticsearch

from ssl import create_default_context

context = create_default_context(cafile=r"path\to\certs\http_ca.crt")
es = Elasticsearch('https://localhost:9200',
    http_auth=('elastic', 'elastic_generated_password'),
    ssl_context=context,
)

接下来,我们需要创建一个文档生成器,它可以提供给流批量API。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
index_name="faq-index"
def generate_docs():
    for index, row in data.iterrows():
        doc = {
                "_index": index_name,
                "_source": {
                    "faq_id":row['Question_ID'],
                    "question":row['Questions'],
                    "answer":row['Answers'],
                    "question_emb": row['question_emb'],
                    "answer_emb": row['answer_emb']
                },
            }

        yield doc

最后,我们可以索引文档。

1
2
3
4
5
6
7
8
9
10
import tqdm
from elasticsearch.helpers import streaming_bulk
number_of_docs=len(data)
progress = tqdm.tqdm(unit="docs", total=number_of_docs)
successes = 0
for ok, action in streaming_bulk(client=es, index=index_name, actions=generate_docs()):
    progress.update(1)
    successes += ok

print("Indexed %d/%d documents" % (successes, number_of_docs))
  • 查询文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def faq_search(query="", k=10, num_candidates=10):
    
    if query is not None and len(query) == 0:
        print('Query cannot be empty')
        return None
    else:
        query_question_emb = question_emb_model.encode(query, normalize_embeddings=True)

        instruction="Represent this sentence for searching relevant passages: "

        query_answer_emb = answer_emb_model.encode(instruction + query, normalize_embeddings=True)

        payload = {
          "query": {
            "match": {
              "title": {
                "query": query,
                "boost": 0.2
              }
            }
          },
          "knn": [ {
            "field": "question_emb",
            "query_vector": query_question_emb,
            "k": k,
            "num_candidates": num_candidates,
            "boost": 0.3
          },
          {
            "field": "answer_emb",
            "query_vector": query_answer_emb,
            "k": k,
            "num_candidates": num_candidates,
            "boost": 0.5
          }],
          "size": 10,
          "_source":["faq_id","question", "answer"]
        }

        response = es.search(index=index_name, body=payload)['hits']['hits']

        return response

注:正如模型页面上所指示的,在将查询转换为Embedding之前,我们需要将指令附加到查询中。此外,我们使用模型的v1.5版本,因为它具有更好的相似度分布。查看模型页面上的常见问题解答以获取更多详细信息。

评价

为了了解所提出的方法是否有效,将其与传统的KNN搜索系统进行评估是很重要的。让我们试着定义这两个系统并评估提议的系统。

系统1:非对称KNN搜索(查询和回答向量)。

系统2:查询(BM25)、非对称KNN搜索(查询和答案向量)和对称KNN搜索(查询和问题向量)的组合。

为了评估系统,我们必须模拟用户如何使用搜索。简而言之,我们需要从源问题中生成与问题复杂性相似的意译问题。我们将使用t5-small-fine-tuned-quora-for-paraphrasing微调模型来解释问题。

让我们定义一个可以生成转述问题的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
from transformers import AutoModelWithLMHead, AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("mrm8488/t5-small-finetuned-quora-for-paraphrasing")
model = AutoModelWithLMHead.from_pretrained("mrm8488/t5-small-finetuned-quora-for-paraphrasing")

def paraphrase(question, number_of_questions=3, max_length=128):
    input_ids = tokenizer.encode(question, return_tensors="pt", add_special_tokens=True)

    generated_ids = model.generate(input_ids=input_ids, num_return_sequences=number_of_questions, num_beams=5, max_length=max_length, no_repeat_ngram_size=2, repetition_penalty=3.5, length_penalty=1.0, early_stopping=True)

    preds = [tokenizer.decode(g, skip_special_tokens=True, clean_up_tokenization_spaces=True) for g in generated_ids]

    return preds

现在我们已经准备好了我们的释义函数,让我们创建一个评估数据集,我们将使用它来度量系统的准确性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
temp_data = data[['Question_ID','Questions']]

eval_data = []

for index, row in temp_data.iterrows():
    preds = paraphrase("paraphrase: {}".format(row['Questions']))
    
    for pred in preds:
        temp={}
        temp['Question'] = pred
        temp['FAQ_ID'] = row['Question_ID']
        eval_data.append(temp)
    
eval_data = pd.DataFrame(eval_data)

#shuffle the evaluation dataset
eval_data=eval_data.sample(frac=1).reset_index(drop=True)

最后,我们将修改faq_search函数以返回相应系统的faq_id。

对于系统1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def get_faq_id_s1(query="", k=5, num_candidates=10):
    
    if query is not None and len(query) == 0:
        print('Query cannot be empty')
        return None
    else:
        instruction="Represent this sentence for searching relevant passages: "

        query_answer_emb = answer_emb_model.encode(instruction + query, normalize_embeddings=True)

        payload = {
          "knn": [
          {
            "field": "answer_emb",
            "query_vector": query_answer_emb,
            "k": k,
            "num_candidates": num_candidates,
          }],
          "size": 1,
          "_source":["faq_id"]
        }

        response = es.search(index=index_name, body=payload)['hits']['hits']

        return response[0]['_source']['faq_id']

对于系统2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def get_faq_id_s2(query="", k=5, num_candidates=10):
    
    if query is not None and len(query) == 0:
        print('Query cannot be empty')
        return None
    else:
        query_question_emb = question_emb_model.encode(query, normalize_embeddings=True)

        instruction="Represent this sentence for searching relevant passages: "

        query_answer_emb = answer_emb_model.encode(instruction + query, normalize_embeddings=True)

        payload = {
          "query": {
            "match": {
              "title": {
                "query": query,
                "boost": 0.2
              }
            }
          },
          "knn": [ {
            "field": "question_emb",
            "query_vector": query_question_emb,
            "k": k,
            "num_candidates": num_candidates,
            "boost": 0.3
          },
          {
            "field": "answer_emb",
            "query_vector": query_answer_emb,
            "k": k,
            "num_candidates": num_candidates,
            "boost": 0.5
          }],
          "size": 1,
          "_source":["faq_id"]
        }

        response = es.search(index=index_name, body=payload)['hits']['hits']

        return response[0]['_source']['faq_id']

注:Boost值是实验性的。为了这个实验,我根据组合中每个领域的重要性进行了划分。搜索中每个字段的重要性完全是主观的,可能由业务本身定义,但如果没有,系统的一般经验法则是Answer向量 > Question向量 > Query

我们已经准备好开始评估了。我们将为两个系统生成一个预测列,并将其与原始的faq_id进行比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
eval_data['PRED_FAQ_ID_S1'] = eval_data['Question'].apply(get_faq_id_s1)

from sklearn.metrics import accuracy_score

ground_truth = eval_data["FAQ_ID"].values
predictions_s1 = eval_data["PRED_FAQ_ID_S1"].values

s1_accuracy = accuracy_score(ground_truth, predictions_s1)

print('System 1 Accuracy: {}'.format(s1_accuracy))
System 1 Accuracy: 0.7312925170068028
eval_data['PRED_FAQ_ID_S2'] = eval_data['Question'].apply(get_faq_id_s2)

predictions_s2 = eval_data["PRED_FAQ_ID_S2"].values

s2_accuracy = accuracy_score(ground_truth, predictions_s2)

print('System 2 Accuracy: {}'.format(s2_accuracy))
System 2 Accuracy: 0.8401360544217688

与非对称KNN搜索相比,我们可以看到准确度提高了7-11% 。

我也尝试过ramsrigouthamg/t5_paraphraser,但这个模型产生的问题有点复杂和冗长(虽然在上下文中)。您还可以使用LLM来生成评估数据集并检查系统的执行情况。

准确性的提高是主观的,取决于查询的质量,即查询的上下文丰富程度、Embedding质量,甚至用户的不同使用场景。为了更好地理解这一点,让我们考虑两种终端用户:

  1. 一般用户,想要了解你的产品和服务的一些事实:在这种情况下,上述系统会做得很好,因为问题简单,直观,在上下文中足够。
  2. 专业用户,例如想要了解产品的一些复杂细节以建立系统或解决某些问题的工程师:在这种情况下,查询就其词法组成而言更加特定于领域,因此,开箱即用的模型Embedding将无法捕获所有上下文。那么,我们如何解决这个问题呢?系统的架构将保持不变,但搜索系统的整体准确性可以通过使用特定领域的数据(或预训练的特定领域的模型)微调这些模型来提高。

结论

在本文中,我们提出并实现了使用多种搜索类型组合的FAQ搜索。我们研究了Elasticsearch如何将对称和非对称语义搜索结合起来,从而将搜索系统的性能提高了11%。我们也了解所建议的搜索架构的系统和资源需求,这将是考虑采用此方法的主要决定因素。

本文Github代码:https://github.com/satishsilveri/Semantic-Search/blob/main/FAQ-Search-using-Elastic.ipynb

引用

  1. k-nearest neighbor (kNN) search | Elasticsearch Guide [8.11] | Elastic
  2. Semantic Search — Sentence-Transformers documentation
  3. https://huggingface.co/blog/getting-started-with-embeddings
  4. Install Elasticsearch with Docker | Elasticsearch Guide [8.11] | Elastic
  5. Dense vector field type | Elasticsearch Guide [8.8] | Elastic
  6. Mental Health FAQ for Chatbot | Kaggle
  7. https://huggingface.co/spaces/mteb/leaderboard
  8. https://huggingface.co/BAAI/bge-large-en-v1.5
  9. https://huggingface.co/thenlper/gte-base
  10. https://huggingface.co/mrm8488/t5-small-finetuned-quora-for-paraphrasing

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

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

相关文章

Servlet JSP-实现简单的登录功能

本篇文章讲解如何使用Servlet-JSP-实现简单的登录功能。我们在进行Servlet和JSP实现简单登录功能的过程中,首先通过Eclipse创建了一个Maven项目,这为我们的Web应用提供了良好的项目管理和依赖管理。接下来,我们解决了新建项目时可能出现的报错…

CDN内容分发网络

1、CDN的含义 1.1 什么是CDN? CDN是内容分发网络(Content Delivery Network)的缩写。它是一种通过将内容部署到全球各地的服务器节点,使用户能够快速访问和下载内容的网络架构。 简单来说,CDN通过将内容分发到离用户更…

鸿蒙应用开发学习:改进小鱼动画实现按键一直按下时控制小鱼移动和限制小鱼移出屏幕

一、前言 近期我在学习鸿蒙应用开发,跟着B站UP主黑马程序员的视频教程做了一个小鱼动画应用,UP主提供的小鱼动画源代码仅仅实现了移动组件的功能,还存在一些问题,如默认进入页面是竖屏而页面适合横屏显示;真机测试发现…

【纯CSS特效源码】(二)精美的立体字

1.漂浮感立体 关键处: text-shadow:2px -2px white, -6px 6px gray;给字体添加了两层shadow,右上角白色提亮,左下角灰色阴影。 参数解释:例子中2px -2px white,代表右上角白色 第一个参数2px:正数表示从左…

C++ 完成Client分页显示log

分页显示t_log 1、获取用户的输入 1.1、写一个Input成员函数&#xff0c;处理输入进来的语句 std::string XClient::Input() {//清空缓冲//cin.ignore(4096, \n);string input "";for (;;){char a getchar();if (a < 0 || a \n || a \r)break;cout <<…

基于鸿蒙HarmonyOS 元服务开发一款公司运营应用(ArkTS API 9)

前言 最近基于Harmony OS最新版本开发了一个作品&#xff0c;本文来详细讲解一下&#xff0c;如何我是如何开发这个作品的。以及如何使用OpenHarmony&#xff0c;基于ArkTS&#xff0c;API 9来开发一个属于自己的元服务。 废话不多说&#xff0c;我的作品名称叫做Company Oper…

Spring Boot 中实现文件上传、下载、删除功能

&#x1f3c6;作者简介&#xff0c;普修罗双战士&#xff0c;一直追求不断学习和成长&#xff0c;在技术的道路上持续探索和实践。 &#x1f3c6;多年互联网行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f389;欢迎 &#x1f44d;点赞✍评论…

【RPC】序列化:对象怎么在网络中传输?

今天来聊下RPC框架中的序列化。在不同的场景下合理地选择序列化方式&#xff0c;对提升RPC框架整体的稳定性和性能是至关重要的。 一、为什么需要序列化&#xff1f; 首先&#xff0c;我们得知道什么是序列化与反序列化。 网络传输的数据必须是二进制数据&#xff0c;但调用…

Jenkins-Maven Git

整合Maven 安装GIT #更新yum sudo yum update #安装git yum install git 安装Maven插件,在插件管理中心&#xff1a; 配置仓库 配置密码认证 我们可以在这个目录下看到Jenkins 帮我们拉取了代码 /env/liyong/data/docker/jenkins_mount/workspace/maven-job 配置maven打包…

江科大STM32 下

目录 ADC数模转换器DMA直接存储器存取USART串口9-2 串口发送接受9-3 串口收发HEX数据包 I2CSPI协议10.1 SPI简介W25Q64简介10.3 SPI软件读写W25Q6410.4 SPI硬件读写W25Q64 BKP、RTC11.0 Unix时间戳11.1 读写备份寄存器BKP11.2 RTC实时时钟 十二、PWR12.1 PWR简介12.2 修改主频1…

Scratch优秀作品飞翔小鸟

程序说明&#xff1a;在无尽的划痕堆中飞驰而过随着你越来越多地飞进迷宫般的街区&#xff0c;平台变得越来越难。 演示视频 scratch飞翔小鸟 其实这就是一个类似像素小鸟的程序&#xff0c;只不过水管角色就地取材&#xff0c;使用scratch里面的积木图片拼成了水管&#xff0…

爬虫案例—抓取豆瓣电影的电影名称、评分、简介、评价人数

爬虫案例—抓取豆瓣电影的电影名称、评分、简介、评价人数 豆瓣电影网址&#xff1a;https://movie.douban.com/top250 主页截图和要抓取的内容如下图&#xff1a; 分析&#xff1a; 第一页的网址&#xff1a;https://movie.douban.com/top250?start0&filter 第二页的…

文献阅读:Large Language Models as Optimizers

文献阅读&#xff1a;Large Language Models as Optimizers 1. 文章简介2. 方法介绍 1. OPRO框架说明2. Demo验证 1. 线性回归问题2. 旅行推销员问题&#xff08;TSP问题&#xff09; 3. Prompt Optimizer 3. 实验考察 & 结论 1. 实验设置2. 基础实验结果 1. GSM8K2. BBH3.…

linux建立基本网站

网站需求&#xff1a; 1.基于域名[www.openlab.com]可以访问网站内容为 welcome to openlab!!! 2.给该公司创建三个子界面分别显示学生信息&#xff0c;教学资料和缴费网站&#xff0c;基于[www.openlab.com/student] 网站访问学生信息 [www.openlab.com/data]网站访问教学资…

微机原理常考填空以及注意事项

以下&#xff1a; 1&#xff0c;两条高位地址线未参加地址译码&#xff0c;则对应的地址范围它的容量是多少倍&#xff1f; 答&#xff1a;公式CPU的地址线&#xff08;假设16位&#xff09;&#xff08;它的低位地址线一般进入片内A0~A10&#xff0c;高位A11就是A、A12就是B、…

微信小程序(一)简单的结构及样式演示

注释很详细&#xff0c;直接上代码 涉及内容&#xff1a; view和text标签的使用类的使用flex布局水平方向上均匀分布子元素垂直居中对齐子元素字体大小文字颜色底部边框的宽和颜色 源码&#xff1a; index.wxml <view class"navs"><text class"active…

任务7:安装MySQL数据库

任务描述 知识点&#xff1a; MySQL数据库安装与使用 重 点&#xff1a; 基于CentOS系统&#xff0c;安装MySQL数据库 内 容&#xff1a; 安装MySQL数据库修改root用户密码 任务指导 MySQL是一个关系型数据库管理系统&#xff0c;由瑞典MySQL AB 公司开发&#xff0c…

【汽车销售数据】2015~2023年各厂商各车型的探索 数据分析可视化

数据处理的思路&#xff1a; 1 各表使用情况&#xff1a; 汽车分厂商每月销售表&#xff0c;该表主要分析展示top10销量的厂商销量、占比变化情况&#xff08;柱形图、饼图&#xff09;&#xff1b;中国汽车分车型每月销售量表&#xff0c;该表主要分析展示top20销量的车型销…

UML-顺序图

提示&#xff1a;用例图从参与者的角度出发&#xff0c;描述了系统的需求&#xff08;用例图&#xff09;&#xff1b;静态图定义系统中的类和对象间的静态关系&#xff08;类图、对象图和包图&#xff09;&#xff1b;状态机模型描述系统元素的行为和状态变化流程&#xff08;…

计算机体系结构基础复习

1. 计算机系统可划分为哪几个层次,各层次之间的界面是什么? 你认为这样划分层次的意义何在? 答&#xff1a; 计算机系统可划分为四个层次&#xff0c;分别是&#xff1a;应用程序、 操作系统、 硬件系统、 晶体管四个大的层次。 注意把这四个层次联系起来的三个界面。各层次…