语义和混合搜索入门
默认情况下,OpenSearch 使用 Okapi BM25 算法计算文档分数。BM25 是一种基于关键词的算法,在包含关键词的查询上表现良好,但未能捕捉查询词的语义含义。与基于关键词的搜索不同,语义搜索会考虑查询在搜索上下文中的含义。因此,当查询需要自然语言理解时,语义搜索表现良好。
在本教程中,您将学习如何实现以下类型的搜索:
- 语义搜索:考虑语义含义,以确定用户查询在搜索上下文中的意图,从而提高搜索相关性。
- 混合搜索:结合语义搜索和关键词搜索,以提高搜索相关性。
语义搜索的 OpenSearch 组件
在本教程中,您将使用以下 OpenSearch 组件:
在您学习本教程的过程中,您将找到所有这些组件的描述,因此如果您对其中一些组件不熟悉,请不要担心。前面列表中的每个链接都将带您到相应组件的文档部分。
先决条件
对于此简单设置,您将使用 OpenSearch 提供的机器学习 (ML) 模型和没有专用 ML 节点的集群。为确保此基本本地设置正常工作,请发送以下请求来更新 ML 相关集群设置:
PUT _cluster/settings
{
"persistent": {
"plugins.ml_commons.only_run_on_ml_node": "false",
"plugins.ml_commons.native_memory_threshold": "99"
}
}
高级
对于自定义本地模型设置,请注意以下要求:
- 要注册自定义本地模型,您需要指定额外的集群设置
"allow_registering_model_via_url": "true"
。 - 在生产环境中,最佳实践是通过专用 ML 节点来分离工作负载。在具有专用 ML 节点的集群上,请指定
"only_run_on_ml_node": "true"
以提高性能。
有关 ML 相关集群设置的更多信息,请参阅ML Commons 集群设置。
教程
本教程包含以下步骤:
您可以通过命令行或 OpenSearch Dashboards 开发工具控制台来学习本教程。
教程中的某些步骤包含可选的 测试 部分。您可以通过运行这些部分中的请求来确认该步骤已成功完成。
完成后,请按照清理部分中的步骤删除所有创建的组件。
步骤 1:选择模型
首先,您需要选择一个语言模型,以便在摄入时和查询时从文本字段生成向量嵌入。
在本教程中,您将使用来自 Hugging Face 的 DistilBERT 模型。它是 OpenSearch 中可用的预训练句子 Transformer 模型之一,在基准测试中表现出一些最佳结果(更多信息,请参阅这篇博客文章)。您需要模型的名称、版本和维度来注册它。您可以通过选择与模型的 TorchScript 工件对应的 config_url
链接,在预训练模型表中找到此信息:
- 模型名称是
huggingface/sentence-transformers/msmarco-distilbert-base-tas-b
。 - 模型版本是
1.0.3
。 - 此模型的维度数为
768
。
请记下模型的维度,因为在设置向量索引时会用到它。
高级:使用不同的模型
或者,您可以为您的模型选择以下选项之一:
-
使用 OpenSearch 提供的任何其他预训练模型。有关更多信息,请参阅OpenSearch 提供的预训练模型。
-
将您自己的模型上传到 OpenSearch。有关更多信息,请参阅自定义本地模型。
-
连接到外部平台托管的基础模型。有关更多信息,请参阅连接到远程模型。
有关选择模型的信息,请参阅延伸阅读。
步骤 2:注册并部署模型
要注册并部署模型,请在注册请求中提供模型组 ID:
POST /_plugins/_ml/models/_register?deploy=true
{
"name": "huggingface/sentence-transformers/msmarco-distilbert-base-tas-b",
"version": "1.0.3",
"model_format": "TORCH_SCRIPT"
}
注册模型是一个异步任务。OpenSearch 会为此任务返回一个任务 ID:
{
"task_id": "aFeif4oB5Vm0Tdw8yoN7",
"status": "CREATED"
}
OpenSearch 从 URL 下载模型的配置文件和模型内容。由于模型大小超过 10 MB,OpenSearch 会将其分成最大 10 MB 的块,并将这些块保存在模型索引中。您可以使用任务 API 检查任务状态:
GET /_plugins/_ml/tasks/aFeif4oB5Vm0Tdw8yoN7
OpenSearch 将注册的模型保存在模型索引中。部署模型会创建模型实例并将模型缓存到内存中。
任务完成后,任务状态将为 COMPLETED
,并且任务 API 响应将包含部署模型的模型 ID:
{
"model_id": "aVeif4oB5Vm0Tdw8zYO2",
"task_type": "REGISTER_MODEL",
"function_name": "TEXT_EMBEDDING",
"state": "COMPLETED",
"worker_node": [
"4p6FVOmJRtu3wehDD74hzQ"
],
"create_time": 1694358489722,
"last_update_time": 1694358499139,
"is_async": true
}
您需要模型 ID 才能在以下几个步骤中使用此模型。
测试
通过在请求中提供其 ID 来搜索新创建的模型:
GET /_plugins/_ml/models/aVeif4oB5Vm0Tdw8zYO2
响应包含模型:
{
"name": "huggingface/sentence-transformers/msmarco-distilbert-base-tas-b",
"model_group_id": "Z1eQf4oB5Vm0Tdw8EIP2",
"algorithm": "TEXT_EMBEDDING",
"model_version": "1",
"model_format": "TORCH_SCRIPT",
"model_state": "REGISTERED",
"model_content_size_in_bytes": 266352827,
"model_content_hash_value": "acdc81b652b83121f914c5912ae27c0fca8fabf270e6f191ace6979a19830413",
"model_config": {
"model_type": "distilbert",
"embedding_dimension": 768,
"framework_type": "SENTENCE_TRANSFORMERS",
"all_config": """{"_name_or_path":"old_models/msmarco-distilbert-base-tas-b/0_Transformer","activation":"gelu","architectures":["DistilBertModel"],"attention_dropout":0.1,"dim":768,"dropout":0.1,"hidden_dim":3072,"initializer_range":0.02,"max_position_embeddings":512,"model_type":"distilbert","n_heads":12,"n_layers":6,"pad_token_id":0,"qa_dropout":0.1,"seq_classif_dropout":0.2,"sinusoidal_pos_embds":false,"tie_weights_":true,"transformers_version":"4.7.0","vocab_size":30522}"""
},
"created_time": 1694482261832,
"last_updated_time": 1694482324282,
"last_registered_time": 1694482270216,
"last_deployed_time": 1694482324282,
"total_chunks": 27,
"planning_worker_node_count": 1,
"current_worker_node_count": 1,
"planning_worker_nodes": [
"4p6FVOmJRtu3wehDD74hzQ"
],
"deploy_to_all_nodes": true
}
响应包含模型信息。您可以看到 model_state
为 REGISTERED
。此外,模型被分成 27 个块,如 total_chunks
字段所示。
高级:注册自定义模型
要注册自定义模型,您必须在注册请求中提供模型配置。有关更多信息,请参阅在 OpenSearch 中使用 ML 模型。
测试
通过在请求中提供其 ID 来搜索已部署的模型:
GET /_plugins/_ml/models/aVeif4oB5Vm0Tdw8zYO2
响应显示模型状态为 DEPLOYED
:
{
"name": "huggingface/sentence-transformers/msmarco-distilbert-base-tas-b",
"model_group_id": "Z1eQf4oB5Vm0Tdw8EIP2",
"algorithm": "TEXT_EMBEDDING",
"model_version": "1",
"model_format": "TORCH_SCRIPT",
"model_state": "DEPLOYED",
"model_content_size_in_bytes": 266352827,
"model_content_hash_value": "acdc81b652b83121f914c5912ae27c0fca8fabf270e6f191ace6979a19830413",
"model_config": {
"model_type": "distilbert",
"embedding_dimension": 768,
"framework_type": "SENTENCE_TRANSFORMERS",
"all_config": """{"_name_or_path":"old_models/msmarco-distilbert-base-tas-b/0_Transformer","activation":"gelu","architectures":["DistilBertModel"],"attention_dropout":0.1,"dim":768,"dropout":0.1,"hidden_dim":3072,"initializer_range":0.02,"max_position_embeddings":512,"model_type":"distilbert","n_heads":12,"n_layers":6,"pad_token_id":0,"qa_dropout":0.1,"seq_classif_dropout":0.2,"sinusoidal_pos_embds":false,"tie_weights_":true,"transformers_version":"4.7.0","vocab_size":30522}"""
},
"created_time": 1694482261832,
"last_updated_time": 1694482324282,
"last_registered_time": 1694482270216,
"last_deployed_time": 1694482324282,
"total_chunks": 27,
"planning_worker_node_count": 1,
"current_worker_node_count": 1,
"planning_worker_nodes": [
"4p6FVOmJRtu3wehDD74hzQ"
],
"deploy_to_all_nodes": true
}
您还可以通过发送 模型配置文件 API 请求来接收集群中所有已部署模型的统计信息:
GET /_plugins/_ml/profile/models
步骤 3:摄入数据
OpenSearch 使用语言模型将文本转换为向量嵌入。在摄入期间,OpenSearch 为请求中的文本字段创建向量嵌入。在搜索期间,您可以通过应用相同的模型为查询文本生成向量嵌入,从而对文档执行向量相似度搜索。
步骤 3(a):创建摄入管道
现在您已经部署了一个模型,您可以使用此模型配置一个摄入管道,该管道包含一个处理器:一个在文档被摄入索引之前转换文档字段的任务。在此示例中,您将设置一个 text_embedding
处理器,该处理器从文本创建向量嵌入。您需要上一节中设置的模型的 model_id
以及一个 field_map
,后者指定从哪个字段获取文本(text
)以及将嵌入记录到哪个字段(passage_embedding
):
PUT /_ingest/pipeline/nlp-ingest-pipeline
{
"description": "An NLP ingest pipeline",
"processors": [
{
"text_embedding": {
"model_id": "aVeif4oB5Vm0Tdw8zYO2",
"field_map": {
"text": "passage_embedding"
}
}
}
]
}
测试
使用摄入 API 搜索已创建的摄入管道:
GET /_ingest/pipeline
响应包含摄入管道:
{
"nlp-ingest-pipeline": {
"description": "An NLP ingest pipeline",
"processors": [
{
"text_embedding": {
"model_id": "aVeif4oB5Vm0Tdw8zYO2",
"field_map": {
"text": "passage_embedding"
}
}
}
]
}
}
步骤 3(b):创建向量索引
现在您将创建一个向量索引,其中包含一个名为 text
的字段(包含图像描述)和一个名为 passage_embedding
的 knn_vector
字段(包含文本的向量嵌入)。此外,将默认摄入管道设置为您在上一步中创建的 nlp-ingest-pipeline
:
PUT /my-nlp-index
{
"settings": {
"index.knn": true,
"default_pipeline": "nlp-ingest-pipeline"
},
"mappings": {
"properties": {
"id": {
"type": "text"
},
"passage_embedding": {
"type": "knn_vector",
"dimension": 768,
"space_type": "l2"
},
"text": {
"type": "text"
}
}
}
}
设置向量索引允许您稍后对 passage_embedding
字段执行向量搜索。
测试
使用以下请求获取已创建索引的设置和映射:
GET /my-nlp-index/_settings
GET /my-nlp-index/_mappings
步骤 3(c):将文档摄入索引
在此步骤中,您将摄入几个示例文档到索引中。示例数据取自 Flickr 图像数据集。每个文档都包含一个与图像描述对应的 text
字段和一个与图像 ID 对应的 id
字段:
PUT /my-nlp-index/_doc/1
{
"text": "A West Virginia university women 's basketball team , officials , and a small gathering of fans are in a West Virginia arena .",
"id": "4319130149.jpg"
}
PUT /my-nlp-index/_doc/2
{
"text": "A wild animal races across an uncut field with a minimal amount of trees .",
"id": "1775029934.jpg"
}
PUT /my-nlp-index/_doc/3
{
"text": "People line the stands which advertise Freemont 's orthopedics , a cowboy rides a light brown bucking bronco .",
"id": "2664027527.jpg"
}
PUT /my-nlp-index/_doc/4
{
"text": "A man who is riding a wild horse in the rodeo is very near to falling off .",
"id": "4427058951.jpg"
}
PUT /my-nlp-index/_doc/5
{
"text": "A rodeo cowboy , wearing a cowboy hat , is being thrown off of a wild white horse .",
"id": "2691147709.jpg"
}
当文档被摄入索引时,text_embedding
处理器会创建一个包含向量嵌入的额外字段,并将其添加到文档中。要查看已索引的示例文档,请搜索文档 1:
GET /my-nlp-index/_doc/1
响应包括文档 _source
,其中包含原始的 text
和 id
字段以及添加的 passage_embedding
字段:
{
"_index": "my-nlp-index",
"_id": "1",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found": true,
"_source": {
"passage_embedding": [
0.04491629,
-0.34105563,
0.036822468,
-0.14139028,
...
],
"text": "A West Virginia university women 's basketball team , officials , and a small gathering of fans are in a West Virginia arena .",
"id": "4319130149.jpg"
}
}
步骤 4:搜索数据
现在您将使用关键词搜索、语义搜索以及两者的组合来搜索索引。
使用关键词搜索
要使用关键词搜索,请使用 match
查询。您将从结果中排除嵌入:
GET /my-nlp-index/_search
{
"_source": {
"excludes": [
"passage_embedding"
]
},
"query": {
"match": {
"text": {
"query": "wild west"
}
}
}
}
未返回文档 3,因为它不包含指定的关键词。包含词语 rodeo
和 cowboy
的文档得分较低,因为它们的语义含义未被考虑在内。
结果
{
"took": 647,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 4,
"relation": "eq"
},
"max_score": 1.7878418,
"hits": [
{
"_index": "my-nlp-index",
"_id": "1",
"_score": 1.7878418,
"_source": {
"text": "A West Virginia university women 's basketball team , officials , and a small gathering of fans are in a West Virginia arena .",
"id": "4319130149.jpg"
}
},
{
"_index": "my-nlp-index",
"_id": "2",
"_score": 0.58093566,
"_source": {
"text": "A wild animal races across an uncut field with a minimal amount of trees .",
"id": "1775029934.jpg"
}
},
{
"_index": "my-nlp-index",
"_id": "5",
"_score": 0.55228686,
"_source": {
"text": "A rodeo cowboy , wearing a cowboy hat , is being thrown off of a wild white horse .",
"id": "2691147709.jpg"
}
},
{
"_index": "my-nlp-index",
"_id": "4",
"_score": 0.53899646,
"_source": {
"text": "A man who is riding a wild horse in the rodeo is very near to falling off .",
"id": "4427058951.jpg"
}
}
]
}
}
使用语义搜索
要使用语义搜索,请使用 neural
查询并提供您之前设置的模型 ID,以便使用摄入时使用的模型生成查询文本的向量嵌入:
GET /my-nlp-index/_search
{
"_source": {
"excludes": [
"passage_embedding"
]
},
"query": {
"neural": {
"passage_embedding": {
"query_text": "wild west",
"model_id": "aVeif4oB5Vm0Tdw8zYO2",
"k": 5
}
}
}
}
这次,响应不仅包含所有五个文档,而且文档顺序也得到了改进,因为语义搜索考虑了语义含义。
结果
{
"took": 25,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 5,
"relation": "eq"
},
"max_score": 0.01585195,
"hits": [
{
"_index": "my-nlp-index",
"_id": "4",
"_score": 0.01585195,
"_source": {
"text": "A man who is riding a wild horse in the rodeo is very near to falling off .",
"id": "4427058951.jpg"
}
},
{
"_index": "my-nlp-index",
"_id": "2",
"_score": 0.015748845,
"_source": {
"text": "A wild animal races across an uncut field with a minimal amount of trees.",
"id": "1775029934.jpg"
}
},
{
"_index": "my-nlp-index",
"_id": "5",
"_score": 0.015177963,
"_source": {
"text": "A rodeo cowboy , wearing a cowboy hat , is being thrown off of a wild white horse .",
"id": "2691147709.jpg"
}
},
{
"_index": "my-nlp-index",
"_id": "1",
"_score": 0.013272902,
"_source": {
"text": "A West Virginia university women 's basketball team , officials , and a small gathering of fans are in a West Virginia arena .",
"id": "4319130149.jpg"
}
},
{
"_index": "my-nlp-index",
"_id": "3",
"_score": 0.011347735,
"_source": {
"text": "People line the stands which advertise Freemont 's orthopedics , a cowboy rides a light brown bucking bronco .",
"id": "2664027527.jpg"
}
}
]
}
}
使用混合搜索
混合搜索结合了关键词搜索和语义搜索以提高搜索相关性。要实现混合搜索,您需要设置一个在搜索时运行的搜索管道。您将配置的搜索管道会在中间阶段拦截搜索结果,并对其应用 normalization-processor
。该 normalization-processor
根据所选的归一化和组合技术,对来自多个查询子句的文档分数进行归一化和组合,从而重新评分文档。
步骤 1:配置搜索管道
要配置包含 normalization-processor
的搜索管道,请使用以下请求。处理器中的归一化技术设置为 min_max
,组合技术设置为 arithmetic_mean
。weights
数组指定分配给每个查询子句的权重(以小数百分比表示):
PUT /_search/pipeline/nlp-search-pipeline
{
"description": "Post processor for hybrid search",
"phase_results_processors": [
{
"normalization-processor": {
"normalization": {
"technique": "min_max"
},
"combination": {
"technique": "arithmetic_mean",
"parameters": {
"weights": [
0.3,
0.7
]
}
}
}
}
]
}
步骤 2:使用混合查询搜索
您将使用 hybrid
查询来组合 match
和 neural
查询子句。请务必在查询参数中将之前创建的 nlp-search-pipeline
应用到请求中:
GET /my-nlp-index/_search?search_pipeline=nlp-search-pipeline
{
"_source": {
"exclude": [
"passage_embedding"
]
},
"query": {
"hybrid": {
"queries": [
{
"match": {
"text": {
"query": "cowboy rodeo bronco"
}
}
},
{
"neural": {
"passage_embedding": {
"query_text": "wild west",
"model_id": "aVeif4oB5Vm0Tdw8zYO2",
"k": 5
}
}
}
]
}
}
}
OpenSearch 不仅返回与 wild west
语义含义匹配的文档,而且现在包含与狂野西部主题相关词语的文档相对于其他文档也获得了更高的分数。
结果
{
"took": 27,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 5,
"relation": "eq"
},
"max_score": 0.86481035,
"hits": [
{
"_index": "my-nlp-index",
"_id": "5",
"_score": 0.86481035,
"_source": {
"text": "A rodeo cowboy , wearing a cowboy hat , is being thrown off of a wild white horse .",
"id": "2691147709.jpg"
}
},
{
"_index": "my-nlp-index",
"_id": "4",
"_score": 0.7003,
"_source": {
"text": "A man who is riding a wild horse in the rodeo is very near to falling off .",
"id": "4427058951.jpg"
}
},
{
"_index": "my-nlp-index",
"_id": "2",
"_score": 0.6839765,
"_source": {
"text": "A wild animal races across an uncut field with a minimal amount of trees.",
"id": "1775029934.jpg"
}
},
{
"_index": "my-nlp-index",
"_id": "3",
"_score": 0.3007,
"_source": {
"text": "People line the stands which advertise Freemont 's orthopedics , a cowboy rides a light brown bucking bronco .",
"id": "2664027527.jpg"
}
},
{
"_index": "my-nlp-index",
"_id": "1",
"_score": 0.29919013,
"_source": {
"text": "A West Virginia university women 's basketball team , officials , and a small gathering of fans are in a West Virginia arena .",
"id": "4319130149.jpg"
}
}
]
}
}
除了在每个请求中指定搜索管道之外,您还可以将其设置为索引的默认搜索管道,如下所示:
PUT /my-nlp-index/_settings
{
"index.search.default_pipeline" : "nlp-search-pipeline"
}
现在您可以尝试不同的权重、归一化技术和组合技术。有关更多信息,请参阅 normalization-processor
和 hybrid
query 文档。
高级
您可以使用搜索模板对搜索进行参数化。搜索模板隐藏了实现细节,减少了嵌套级别,从而降低了查询复杂性。有关更多信息,请参阅搜索模板。
使用自动化工作流
您可以使用自动化工作流快速设置语义搜索或混合搜索。此方法会自动创建并配置所有必要的资源。有关更多信息,请参阅工作流模板。
自动化语义搜索设置
OpenSearch 提供了一个工作流模板,它会自动注册并部署一个默认本地模型 (huggingface/sentence-transformers/paraphrase-MiniLM-L3-v2
),并创建一个摄入管道和一个向量索引:
POST /_plugins/_flow_framework/workflow?use_case=semantic_search_with_local_model&provision=true
查看语义搜索工作流模板默认值,以确定是否需要更新任何参数。例如,如果您想使用不同的模型,请在请求正文中指定模型名称:
POST /_plugins/_flow_framework/workflow?use_case=semantic_search_with_local_model&provision=true
{
"register_local_pretrained_model.name": "huggingface/sentence-transformers/msmarco-distilbert-base-tas-b"
}
OpenSearch 会为创建的工作流返回一个工作流 ID:
{
"workflow_id" : "U_nMXJUBq_4FYQzMOS4B"
}
要检查工作流状态,请发送以下请求:
GET /_plugins/_flow_framework/workflow/U_nMXJUBq_4FYQzMOS4B/_status
工作流完成后,state
将变为 COMPLETED
。工作流运行以下步骤:
现在您可以继续执行步骤 3(c) 将文档摄入索引,以及执行步骤 4 搜索您的数据。
清理
完成后,从集群中删除您在本教程中创建的组件:
DELETE /my-nlp-index
DELETE /_search/pipeline/nlp-search-pipeline
DELETE /_ingest/pipeline/nlp-ingest-pipeline
POST /_plugins/_ml/models/aVeif4oB5Vm0Tdw8zYO2/_undeploy
DELETE /_plugins/_ml/models/aVeif4oB5Vm0Tdw8zYO2
DELETE /_plugins/_ml/model_groups/Z1eQf4oB5Vm0Tdw8EIP2
延伸阅读
- 阅读关于 OpenSearch 语义搜索的基础知识,请参阅在 OpenSearch 中构建语义搜索引擎。
- 阅读关于结合关键词搜索和语义搜索、归一化和组合技术选项以及基准测试的信息,请参阅OpenSearch 语义搜索的 ABC:架构、基准测试和组合策略。
后续步骤
- 探索 OpenSearch 中的AI 搜索。