使用 OpenSearch 执行不区分重音的搜索

我们经常需要我们的文本搜索与重音符号无关。 不区分重音的 搜索,也称为 变音符号无关 搜索,对于可能包含也可能不包含拉丁字符(如 a、e、、n 和 q)的查询,搜索结果相同。变音符号是带有重音的英语字母,用于标记发音的差异。近年来,带有变音符号的单词已逐渐渗透到主流英语中,例如咖啡馆或门生。好吧,touché!OpenSearch 有答案!

OpenSearch 是一款可扩展、灵活和可扩展的开源软件套件,适用于您的搜索工作负载。 OpenSearch 可以通过三种不同的模式进行部署:自我管理的开源 OpenSearch、托管的亚马逊 OpenSearch 服务和亚马逊 OpenSearch Ser verle ss。 所有三种部署模式均由 Apache Lucene 提供支持,并使用 Lucene 分析器提供文本分析。

在这篇文章中,我们演示了如何使用 OpenSearch 执行不区分重音的搜索来处理变音符号。

解决方案概述

Lucene 分析器是 Java 库,用于在索引和搜索文档时分析文本。这些分析器由分词器和过滤器组成。分词器将传入的文本拆分为一个或多个标记,过滤器用于通过修改或删除不必要的字符来转换标记。

OpenSearch 支持自定义分析器,这使您可以配置分词器和过滤器的不同组合。它可以由字符过滤器、分词器和令牌过滤器组成。为了启用不区分变音符号的搜索,我们配置了使用 ASCII 折叠标记过滤器的自定义分析器。

ASCIIFolding 是一种用于将不在前 127 个 ASCII 字符(基本拉丁语 Unicode 块)中的字母、数字和符号 Unicode 字符转换为 ASCII 等效字符(如果存在)的方法。例如,过滤器将 “a” 更改为 “a”。这允许搜索引擎返回与口音无关的结果。

在这篇文章中,我们使用 OpenSearch 服务支持的 ASCIIFolding 过滤器配置不区分重音的搜索。我们会采集一组带有变音符号的欧洲电影名称,并验证带和不带变音符号的搜索结果。

使用自定义分析器创建索引

我们首先使用自定义分析器 custom_asciifold_folding 创建索引 asciifold_mov ies:

PUT /asciifold_movies
{
  "settings": {
    "analysis": {
      "analyzer": {
        "custom_asciifolding": {
          "tokenizer": "standard",
          "filter": [
            "my_ascii_folding"
          ]
        }
      },
      "filter": {
        "my_ascii_folding": {
          "type": "asciifolding",
          "preserve_original": true
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "custom_asciifolding",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          }
        }
      }
    }
  }
}

摄取样本数据

接下来,我们将带有拉丁字符的样本数据提取到 asc iifold_movies 索引中:

POST _bulk
{ "index" : { "_index" : "asciifold_movies", "_id":"1"} }
{  "title" : "Jour de fête"}
{ "index" : { "_index" : "asciifold_movies", "_id":"2"} }
{  "title" : "La gloire de mon père" }
{ "index" : { "_index" : "asciifold_movies", "_id":"3"} }
{  "title" : "Le roi et l’oiseau" }
{ "index" : { "_index" : "asciifold_movies", "_id":"4"} }
{  "title" : "Être et avoir" }
{ "index" : { "_index" : "asciifold_movies", "_id":"5"} }
{  "title" : "Kirikou et la sorcière"}
{ "index" : { "_index" : "asciifold_movies", "_id":"6"} }
{  "title" : "Señora Acero"}
{ "index" : { "_index" : "asciifold_movies", "_id":"7"} }
{  "title" : "Señora garçon"}
{ "index" : { "_index" : "asciifold_movies", "_id":"8"} }
{  "title" : "Jour de fete"}

查询索引

现在我们在 asciifold_movies 索引中查询带有和不带拉丁字符的单词。

我们的第一个查询使用重音字符:

GET asciifold_movies/_search
{
  "query": {
    "match": {
      "title": "fête"
    }
  }
}

我们的第二个查询使用不带重音符号的同一个单词的拼写:

GET asciifold_movies/_search
{
  "query": {
    "match": {
      "title": "fete"
    }
  }
}

在前面的查询中,搜索词 “fète” 和 “fete” 返回相同的结果:

{
  "took": 10,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 2,
      "relation": "eq"
    },
    "max_score": 0.7361701,
    "hits": [
      {
        "_index": "asciifold_movies",
        "_id": "8",
        "_score": 0.7361701,
        "_source": {
          "title": "Jour de fete"
        }
      },
      {
        "_index": "asciifold_movies",
        "_id": "1",
        "_score": 0.42547938,
        "_source": {
          "title": "Jour de fête"
        }
      }
    ]
  }
}

同样,尝试比较 “señora” 和 “senora” 或 “sorciere” 和 “sorciere” 的结果。不区分重音的结果是自定义分析器使用的 ASCIIFolding 过滤器造成的。

为带重音的字段启用聚合

现在我们已经启用了不区分重音的搜索,让我们来看看如何使用重音进行聚合。

尝试在索引上执行以下查询:

GET asciifold_movies/_search
{
  "size": 0,
  "aggs": {
    "test": {
      "terms": {
        "field": "title.keyword"
      }
    }
  }
}

我们得到以下回应:

"aggregations" : {
    "test" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "Jour de fete",
          "doc_count" : 1
        },
        {
          "key" : "Jour de fête",
          "doc_count" : 1
        },
        {
          "key" : "Kirikou et la sorcière",
          "doc_count" : 1
        },
        {
          "key" : "La gloire de mon père",
          "doc_count" : 1
        },
        {
          "key" : "Le roi et l’oiseau",
          "doc_count" : 1
        },
        {
          "key" : "Señora Acero",
          "doc_count" : 1
        },
        {
          "key" : "Señora garçon",
          "doc_count" : 1
        },
        {
          "key" : "Être et avoir",
          "doc_count" : 1
        }
      ]
    }
  }

使用标准化器创建不区分重音的聚合

在前面的示例中,聚合返回两个不同的存储桶,一个用于 “Jour de fète”,另一个用于 “Jour de fete”。无论变音符号如何,我们都可以启用聚合来为该字段创建一个存储桶。这是使用归一化器滤波器实现的。

标准化器支持字符和标记过滤器的子集。标准化器过滤器仅使用默认值,是一种以与语言无关的方式对Unicode文本进行标准化的简单方法,从而标准化Unicode中相同字符的不同形式,并允许与变音符无关的聚合。

让我们修改索引映射以包括归一化器。删除先前的索引,然后使用以下映射创建新索引并提取相同的数据集:

PUT /asciifold_movies
{
  "settings": {
    "analysis": {
      "analyzer": {
        "custom_asciifolding": {
          "tokenizer": "standard",
          "filter": [
            "my_ascii_folding"
          ]
        }
      },
      "filter": {
        "my_ascii_folding": {
          "type": "asciifolding",
          "preserve_original": true
        }
      },
      "normalizer": {
        "custom_normalizer": {
          "type": "custom",
          "filter": "asciifolding"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "custom_asciifolding",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256,
            "normalizer": "custom_normalizer"
          }
        }
      }
    }
  }
}

摄取相同的数据集后,请尝试以下查询:

GET asciifold_movies/_search
{
  "size": 0,
  "aggs": {
    "test": {
      "terms": {
        "field": "title.keyword"
      }
    }
  }
}

我们得到以下结果:

"aggregations" : {
    "test" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "Jour de fete",
          "doc_count" : 2
        },
        {
          "key" : "Etre et avoir",
          "doc_count" : 1
        },
        {
          "key" : "Kirikou et la sorciere",
          "doc_count" : 1
        },
        {
          "key" : "La gloire de mon pere",
          "doc_count" : 1
        },
        {
          "key" : "Le roi et l'oiseau",
          "doc_count" : 1
        },
        {
          "key" : "Senora Acero",
          "doc_count" : 1
        },
        {
          "key" : "Senora garcon",
          "doc_count" : 1
        }
      ]
    }
  }

现在我们比较结果,我们可以看到以 doc_count=2 为术语的 “Jour de fesete” 和 “Jour de fete” 汇总到一个存储桶中。

摘要

在这篇文章中,我们展示了如何通过设计索引映射来实现不区分重音的搜索和聚合,从而对搜索令牌进行 ASCII 折叠,并对关键字字段进行标准化以进行聚合。您可以使用 OpenSearch 查询 DSL 来实现 一系列搜索功能 ,为结构化和非结构化搜索应用程序提供灵活的基础。开源 OpenSearch 社区还扩展了该产品,以支持自然语言处理、机器学习算法、自定义词典和各种其他插件。

如果您对此帖子有反馈,请在评论部分提交。如果您对这篇文章有疑问,请在 亚马逊 OpenSearch Servic e 论坛上开始一个新话题 或联系 亚马逊云科技 S upport。


作者简介

Aruna Govindaraju 是亚马逊 OpenSearch 专业解决方案架构师,曾与许多商业和开源搜索引擎合作过。她热衷于搜索、相关性和用户体验。她在将最终用户信号与搜索引擎行为关联方面的专业知识帮助许多客户改善了搜索体验。她最喜欢的消遣方式是在新英格兰步道和山脉上徒步旅行。