Elasticsearch:使用 Elasticsearch 进行词汇和语义搜索

作者:PRISCILLA PARODI

在这篇博文中,你将探索使用 Elasticsearch 检索信息的各种方法,特别关注文本:词汇 (lexical)语义搜索 (semantic search)

使用 Elasticsearch 进行词汇和语义搜索

搜索是根据你的搜索查询或组合查询查找最相关信息的过程,相关搜索结果是与这些查询最匹配的文档。 尽管存在与搜索相关的多种挑战和方法,但最终目标仍然相同,即找到问题的最佳答案。

考虑到这一目标,在这篇博文中,我们将探索使用 Elasticsearch 检索信息的不同方法,特别关注文本搜索:词汇语义搜索

先决条件

为了实现这一目标,我们将提供 Python 示例,演示在为模拟电子商务产品信息而生成的数据集上的各种搜索场景。

该数据集包含 2,500 多种产品,每种产品都有描述。 这些产品分为 76 个不同的产品类别,每个类别包含不同数量的产品,如下所示:

树形图可视化 - category.keyword(产品类别)的前 22 个值

对于设置,你将需要:

  • Python 3.6 或更高版本
  • Elastic Python 客户端
  • Elastic 8.8 或更高版本部署,具有 8GB 内存机器学习节点
  • Elastic Learned Sparse EncodeR 模型已预加载到 Elastic 中并在你的部署中安装并启动

我们将使用 Elastic Cloud,可以免费试用

除了本博文中提供的搜索查询之外,Python notebook 还将指导你完成以下过程:

  • 使用 Python 客户端建立与我们的 Elastic 部署的连接
  • 将文本嵌入模型加载到 Elasticsearch 集群中
  • 使用用于索引特征向量和密集向量的映射创建索引。
  • 使用推理处理器创建摄取管道以进行文本嵌入和文本扩展

词汇搜索 - 稀疏检索

Elasticsearch 基于文本查询对文档相关性进行排名的经典方式是使用 BM25 模型的 Lucene 实现,BM25 模型是一种用于词法搜索的稀疏模型 (sparse model for lexical search)。 此方法遵循传统的文本搜索方法,寻找精确的术语匹配。

为了使这种搜索成为可能,Elasticsearch 通过执行文本分析将文本字段数据转换为可搜索的格式。

文本分析由分析器执行,分析器是一组规则,用于管理提取相关标记进行搜索的过程。 分析器必须恰好有一个分词器。 分词器接收字符流并将其分解为单独的标记(通常是单独的单词),如下例所示:

词汇搜索的字符串标记化

ini 复制代码
1.  #Performs text analysis on a string and returns the resulting tokens.

3.  # Define the text to be analyzed
4.  text = "Comfortable furniture for a large balcony"

6.  # Define the analyze request
7.  request_body = {
8.    "analyzer": "standard",
9.    "text": text
10.  }

12.  # Perform the analyze request
13.  response = client.indices.analyze(analyzer=request_body["analyzer"], text=request_body["text"])

15.  # Extract and display the analyzed tokens
16.  tokens = [token["token"] for token in response["tokens"]]
17.  print("Analyzed Tokens:", tokens)

上述代码输出:

less 复制代码
Analyzed Tokens: ['comfortable', 'furniture', 'for', 'a', 'large', 'balcony']

在此示例中,我们使用默认分析器,即标准分析器,它适用于大多数用例,因为它提供基于英语语法的分词化。 标记化可以对各个术语进行匹配,但每个分词仍然按字面意思进行匹配。

如果你想个性化你的搜索体验,你可以选择不同的内置分析器。 例如,通过更新代码以使用停止分析器,它将在任何非字母字符处将文本分解为标记,并支持删除停止词。

bash 复制代码
1.  ...
2.  # Define the analyze request
3.  request_body = {
4.    "analyzer": "stop",
5.    "text": text
6.  }
7.  ...

上面的输出为:

less 复制代码
Analyzed Tokens: ['comfortable', 'furniture', 'large', 'balcony']

当内置分析器不能满足你的需求时,你可以创建自定义分析器,它使用零个或多个字符过滤器、分词器和零个或多个 token 过滤器的适当组合。

bash 复制代码
1.  "analyzer":  {

3.    "my_analyzer": {

5.      "type": "custom", #For custom analyzers, use a type of custom or omit the type parameter.

7.      "tokenizer": "standard", #Built-in or customized tokenizer

9.      "filter": ["lowercase", "synonym"] #Built-in or customized token filters
10.    }
11.  }

在上面结合了分词器和分词过滤器的示例中,文本在被 synonym token filter 处理之前将被 lowercase filter 转为小写。

如果你想了解更多关于 analyzer 方面的知识,请参阅文章 "Elastic:开发者上手指南" 中的 "分词器介绍" 部分。

词汇匹配 - Lexical Matching

BM25 将根据术语的频率及其重要性来衡量文档与给定搜索查询的相关性。

下面的代码执行 match 查询,考虑 "ecommerce-search " 索引中的 "decription" 字段值和搜索查询 "Comfortable furniture for a large balcony"",搜索最多两个文档。

细化被视为与该查询匹配的文档的标准可以提高精度。 然而,更具体的结果是以降低对变化的容忍度为代价的。

ini 复制代码
1.  # BM25

3.  response = client.search(size=2,
4.  index="ecommerce-search",
5.  query= {
6.    "match": {
7.      "description" : {  
8.        "query": "Comfortable furniture for a large balcony",
9.        "analyzer": "stop"
10.      }
11.    }
12.  }
13.  )

15.  hits = response['hits']['hits']

17.  if not hits:
18.    print("No matches found")

20.  else:
21.    for hit in hits:
22.      score = hit['_score']
23.      product = hit['_source']['product']
24.      category = hit['_source']['category']
25.      description = hit['_source']['description']
26.      print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")

输出为:

markdown 复制代码
1.  Score: 15.607948
2.  Product: Barbie Dreamhouse
3.  Category: Toys
4.  Description: is a classic Barbie playset with multiple rooms, furniture, a large balcony, a pool, and accessories. It allows kids to create their dream Barbie world.

6.  Score: 9.137739
7.  Product: Comfortable Rocking Chair
8.  Category: Indoor Furniture
9.  Description: enjoy relaxing moments with this comfortable rocking chair. Its smooth motion and cushioned seat make it an ideal piece of furniture for unwinding.

通过分析输出,最相关的结果是 "Toys " 类别中的 "Barbie Dreamhouse " 产品,其描述高度相关,因为它包括术语 "furniture "、"large " 和 "balcony",这是 唯一在描述中包含 3 个术语与搜索查询相匹配的产品,该产品也是唯一在描述中包含术语"阳台"的产品。

第二个最相关的产品是归类为 "Indoor Furniture " 的 "Comfortable Rocking Chair ",其描述包括术语 "comfortable " 和 "furniture"。 数据集中只有 3 个产品与此搜索查询的至少 2 个术语匹配,该产品就是其中之一。

"Comfortable " 出现在 105 个产品的描述中,"furniture " 出现在 4 个不同类别的 4 个产品的描述中:Toys , Indoor Furniture, Outdoor Furniture 和 "Cat Supplies & Toys"。

正如你所看到的,考虑到该查询,最相关的产品是玩具,第二相关的产品是室内家具。 如果你想要有关分数计算的详细信息,以了解为什么这些文档是匹配的,你可以将 explain __query 参数设置为true。

尽管这两个结果都是最相关的结果,但考虑到该数据集中的文档数量和术语的出现次数,查询 "Comfortable Furniture for a Large Baladal" 背后的意图是搜索实际大阳台的家具,但是不包括其他,玩具和室内家具。

词汇搜索相对简单且快速 ,但它有局限性,因为在不一定知道用户的意图和查询的情况下,并不总是可能知道所有可能的术语和同义词。 自然语言使用中的一个常见现象是词汇不匹配。 研究表明,平均而言,80% 的情况下,不同的人(同一领域的专家)会对同一事物有不同的命名。

这些限制促使我们寻找其他包含语义知识的评分模型。 基于 Transformer 的模型擅长处理自然语言等顺序输入标记,通过考虑文档和查询的数学表示来捕获搜索的潜在含义。 这允许对文本进行密集的、上下文感知的向量表示,为语义搜索提供动力,这是一种查找相关内容的精细方法。

语义搜索-密集检索

在这种情况下,将数据转换为有意义的向量值后,将利用 k 最近邻 (kNN) 搜索算法来查找数据集中与查询向量最相似的向量表示。 Elasticsearch 支持两种 kNN 搜索方法:精确 brute--fource kNN近似 kNN(也称为 ANN)。

Brute-force kNN 可以保证准确的结果,但不能很好地适应大型数据集。 近似 kNN 通过牺牲一些精度来提高性能,从而有效地找到近似最近邻。

借助 Lucene 对 kNN 搜索和密集向量索引的支持,Elasticsearch 充分利用了分层可导航小世界 (HNSW) 算法,该算法在各种 ANN 基准数据集上展示了强大的搜索性能。 可以使用以下示例代码在 Python 中执行近似 kNN 搜索。

使用近似 kNN 进行语义搜索

python 复制代码
1.  # KNN - approximate kNN

3.  response = client.search(index='ecommerce-search', size=2,
4.  knn={
5.    "field": "description_vector.predicted_value",
6.    "k": 50, # Number of nearest neighbors to return as top hits.
7.  #The optimal value of k is dependent on the data. It can vary in different scenarios.

9.    "num_candidates": 500, # Number of nearest neighbor candidates to consider per shard.

11.  #Increasing num_candidates tends to improve the accuracy of the final k results.

13.    "query_vector_builder": { # Object indicating how to build a query_vector. kNN search enables you to perform semantic search by using a previously deployed text embedding model, the steps for this process are demonstrated in the Python notebook.
14.      "text_embedding": { 
15.        "model_id": "sentence-transformers__all-mpnet-base-v2", # Text embedding model id
16.        "model_text": "Comfortable furniture for a large balcony" # Query
17.      }
18.    }
19.  }
20.  )

22.  for hit in response['hits']['hits']:

24.    score = hit['_score']
25.    product = hit['_source']['product']
26.    category = hit['_source']['category']
27.    description = hit['_source']['description']
28.    print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")

考虑到产品数据集中 "description" 字段的嵌入,此代码块使用 Elasticsearch 的 kNN 返回最多两个产品,其描述类似于 "Comfortable furniture for a large balcony" 的向量化查询 (query_vector_build)。

产品嵌入先前是在摄取管道中生成的,其中包含 "all-mpnet-base-v2" 文本嵌入模型的推理处理器,用于推断管道中摄取的数据。

该模型是根据使用 "sentence_transformers.evaluation" 对预训练模型进行评估而选择的,其中在训练期间使用不同的类别来评估模型。 根据 Sentence-Transformers 排名,"all-mpnet-base-v2" 模型展示了最佳的平均性能,并且还在大规模文本嵌入基准 (MTEB) 排行榜上获得了有利的位置。 该模型预先训练了 microsoft/mpnet-base 模型并在 1B 句子对数据集上进行了微调,它将句子映射到 768 维密集向量空间。

或者,还有许多其他模型可供使用,特别是那些针对特定领域数据进行微调的模型。

上面代码的输出为:

python 复制代码
1.  Score: 0.79207325
2.  Product: Patio Sofa Set with Ottoman
3.  Category: Outdoor Furniture
4.  Description: is a versatile and comfortable patio sofa set, including a sofa, ottoman, and coffee table, great for outdoor lounging.

6.  Score: 0.7836937
7.  Product: Patio Sofa Set with Canopy
8.  Category: Outdoor Furniture
9.  Description: is a luxurious and comfortable patio sofa set with a canopy, providing shade and style for outdoor lounging.

输出可能会根据所选模型、滤波器和近似 kNN 调整而有所不同。

kNN 搜索结果都属于 "Outdoor Furniture " 类别,尽管查询中没有明确提及 "outdoor"一词,这凸显了上下文中语义理解的重要性。

密集向量搜索具有以下几个优点:

  • 启用语义搜索
  • 处理非常大的数据集的可扩展性
  • 灵活处理各种数据类型

然而,密集向量搜索也面临着其自身的挑战:

  • 为你的用例选择正确的嵌入模型
  • 选择模型后,可能需要微调模型以优化特定领域数据集的性能,这个过程需要领域专家的参与
  • 此外,索引高维向量的计算成本可能很高

语义搜索 - 学习稀疏检索 (Learned Sparse Retrieval)

让我们探索另一种方法:学习稀疏检索,这是执行语义搜索的另一种方法。

作为稀疏模型,它利用 Elasticsearch 基于 Lucene 的倒排索引,该索引得益于数十年的优化。 然而,这种方法不仅仅是简单地使用 BM25 等词汇评分函数添加同义词。 相反,它使用更深入的语言规模知识来整合学习的关联,以优化相关性。

通过扩展搜索查询以包含原始查询中不存在的相关术语,Elastic Learned Sparse Encoder 改进了稀疏向量嵌入,如下面的示例所示。

使用 Elastic Learned Sparse Encoder 进行稀疏向量搜索

ini 复制代码
1.  # Elastic Learned Sparse Encoder

3.  response = client.search(index='ecommerce-search', size=2,
4.  query={
5.    "text_expansion": {
6.      "ml.tokens": {
7.        "model_id":"elser_model",
8.        "model_text":"Comfortable furniture for a large balcony"                
9.      }
10.    }
11.  }
12.  )

14.  for hit in response['hits']['hits']:

16.    score = hit['_score']
17.    product = hit['_source']['product']
18.    category = hit['_source']['category']
19.    description = hit['_source']['description']
20.    print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")

输出:

less 复制代码
1.  Score: 14.405318
2.  Product: Garden Lounge Set with Side Table
3.  Category: Garden Furniture
4.  Description: is a comfortable and stylish garden lounge set, including a sofa, chairs, and a side table for outdoor relaxation.

6.  Score: 14.281318
7.  Product: Rattan Patio Conversation Set
8.  Category: Outdoor Furniture
9.  Description: is a stylish and comfortable outdoor furniture set, including a sofa, two chairs, and a coffee table, all made of durable rattan material.

本例中的结果包括 "Garden Furniture " 类别,该类别提供与 "Outdoor Furniture" 非常相似的产品。

通过分析 "ml.tokens"(包含学习稀疏检索生成的标记的 "rank_features" 字段),很明显,在生成的各种标记中,有些术语虽然不是搜索查询的一部分,但在含义上仍然相关,例如 "relax "(comfortable)、"sofa "(furniture)和 "outdoor"(balcony)。

下图突出显示了查询旁边的一些术语,包括带或不带术语扩展的情况。

正如所观察到的,该模型提供了上下文感知搜索,有助于缓解词汇不匹配问题,同时提供更具可解释性的结果。 当不应用特定领域的再训练时,它甚至可以超越密集向量模型。

混合搜索:结合词汇和语义搜索获得相关结果

就搜索而言,没有通用的解决方案。 这些检索方法都有其优点,但也有其挑战。 根据用例,最佳选项可能会发生变化。 通常,不同检索方法的最佳结果可以是互补的。 因此,为了提高相关性,我们将考虑结合每种方法的优点。

有多种方法可以实现混合搜索 (hybrid search),包括线性组合、为每个分数赋予权重以及倒数排名融合(RRF),其中不需要指定权重。

Elasticsearch:词汇和语义搜索的两全其美

python 复制代码
1.  # BM25 + Elastic Learned Sparse Encoder (Linear Combination)

3.  response = client.search(index='ecommerce-search', size=2,

5.  query= {
6.    "bool": {
7.      "should": [
8.      {
9.        "match": {
10.          "description" : {  
11.            "query": "A dining table and comfortable chairs for a large balcony",
12.            "boost": 1
13.          }
14.        }
15.      },                   
16.      {
17.        "text_expansion": {
18.          "ml.tokens": {
19.            "model_id": "elser_model",
20.            "model_text": "A dining table and comfortable chairs for a large balcony",
21.            "boost": 1
22.          }
23.        }
24.       }
25.      ]
26.    }
27.  }
28.  )

30.  # The boost value is 1 for the text expansion and match query. This means that the relevance score of the results of these queries are not boosted. You can specify a boost value to give a weight to each score in the sum. The scores will be calculated as: score = boost value * match_score + boost value * text_expansion_score

32.  for hit in response['hits']['hits']:

34.    score = hit['_score']
35.    product = hit['_source']['product']
36.    category = hit['_source']['category']
37.    description = hit['_source']['description']
38.    print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")

在此代码中,我们使用两个值为 "A dining table and comfortable chairs for a large balcony " 的查询执行混合搜索。 我们没有使用 "furniture" 作为搜索词,而是指定我们要查找的内容,并且两个搜索都考虑相同的字段值 "description"。 排名由 BM25 和 ELSER 分数等权重的线性组合确定。

输出:

python 复制代码
1.  Score: 31.628141
2.  Product: Garden Dining Set with Swivel Rockers
3.  Category: Garden Furniture
4.  Description: is a functional and comfortable garden dining set, including a table and chairs with swivel rockers for easy movement.

6.  Score: 31.334227
7.  Product: Garden Dining Set with Swivel Chairs
8.  Category: Garden Furniture
9.  Description: is a functional and comfortable garden dining set, including a table and chairs with swivel seats for convenience.

在下面的代码中,我们将为查询使用相同的值,但使用倒数排名融合方法结合 BM25(查询参数)和 kNN(knn 参数)的分数来对文档进行组合和排名。

python 复制代码
1.  # BM25 + KNN (RRF)

3.  response = client.search(index='ecommerce-search', size=2,
4.  query={
5.    "bool": {
6.      "should": [
7.      {
8.        "match": {
9.          "description": {
10.          "query": "A dining table and comfortable chairs for a large balcony"
11.          }
12.        }
13.      }
14.      ]
15.    }
16.  },
17.  knn={
18.    "field": "description_vector.predicted_value",
19.    "k": 50,
20.    "num_candidates": 500,
21.    "query_vector_builder": {
22.      "text_embedding": {
23.        "model_id": "sentence-transformers__all-mpnet-base-v2",
24.        "model_text": "A dining table and comfortable chairs for a large balcony"
25.      }
26.    }
27.  },
28.  rank={
29.    "rrf": { # Reciprocal rank fusion
30.      "window_size": 50, # This value determines the size of the individual result sets per query.
31.      "rank_constant": 20 # This value determines how much influence documents in individual result sets per query have over the final ranked result set.
32.    }
33.  }
34.  )

36.  for hit in response['hits']['hits']:

38.    rank = hit['_rank']
39.    category = hit['_source']['category']
40.    product = hit['_source']['product']
41.    description = hit['_source']['description']
42.    print(f"\nRank: {rank}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")

RRF 功能处于技术预览阶段。 语法可能会在正式发布之前发生变化。

输出:

python 复制代码
1.  Rank: 1
2.  Product: Patio Dining Set with Bench
3.  Category: Outdoor Furniture
4.  Description: is a spacious and functional patio dining set, including a dining table, chairs, and a bench for additional seating.

6.  Rank: 2
7.  Product: Garden Dining Set with Swivel Chairs
8.  Category: Garden Furniture
9.  Description: is a functional and comfortable garden dining set, including a table and chairs with swivel seats for convenience.

这里我们还可以使用不同的字段和值; Python notebook 中提供了其中一些示例。

正如你所看到的,使用 Elasticsearch,你可以两全其美:传统的词法搜索和向量搜索,无论是稀疏还是密集,都可以实现你的目标并找到问题的最佳答案。

如果你想继续了解此处提到的方法,这些博客可能会很有用:

Elasticsearch 提供向量数据库以及构建向量搜索所需的所有工具:

结论:

在这篇博文中,我们探索了使用 Elasticsearch 检索信息的各种方法,特别关注文本、词汇和语义搜索。 为了演示这一点,我们提供了 Python 示例,展示了使用包含电子商务产品信息的数据集的不同搜索场景。

我们回顾了 BM25 的经典词汇搜索,并讨论了它的优点和挑战,例如词汇不匹配。 我们强调了结合语义知识来克服这个问题的重要性。 此外,我们讨论了密集向量搜索,它支持语义搜索,并讨论了与这种检索方法相关的挑战,包括索引高维向量时的计算成本。

另一方面,我们提到稀疏向量的压缩效果非常好。 因此,我们讨论了 Elastic 的学习稀疏编码器,它将搜索查询扩展为包含原始查询中不存在的相关术语。

在搜索方面,没有一种万能的解决方案。 每种检索方法都有其优点和挑战。 因此,我们还讨论了混合搜索的概念。

正如你所看到的,使用 Elasticsearch,你可以两全其美:传统的词法搜索和向量搜索!

准备好开始了吗? 检查可用的 Python notebook 并开始免费试用 Elastic Cloud

相关推荐
m0_748237051 小时前
Monorepo pnpm 模式管理多个 web 项目
大数据·前端·elasticsearch
java1234_小锋2 小时前
ElasticSearch如何做性能优化?
大数据·elasticsearch·性能优化
LI JS@你猜啊13 小时前
Elasticsearch 集群
大数据·服务器·elasticsearch
神奇侠202417 小时前
解决集群Elasticsearch 未授权访问漏洞
elasticsearch
Elastic 中国社区官方博客17 小时前
如何通过 Kafka 将数据导入 Elasticsearch
大数据·数据库·分布式·elasticsearch·搜索引擎·kafka·全文检索
神奇侠202417 小时前
解决单台Elasticsearch 未授权访问漏洞
elasticsearch
nece00117 小时前
elasticsearch 杂记
大数据·elasticsearch·搜索引擎
开心最重要(*^▽^*)17 小时前
Es搭建——单节点——Linux
大数据·elasticsearch
喝醉酒的小白20 小时前
ElasticSearch 的核心功能
大数据·elasticsearch·jenkins
forestsea1 天前
【Elasticsearch】分片与副本机制:优化数据存储与查询性能
大数据·elasticsearch·搜索引擎