【问题标题】:How to handle synonyms and stop words when building a fuzzy query with Hibernate Search Query DSL使用 Hibernate Search Query DSL 构建模糊查询时如何处理同义词和停用词
【发布时间】:2019-01-04 03:38:25
【问题描述】:

使用 Hibernate Search (5.8.2.Final) 查询 DSL 到 Elasticsearch 服务器。

给定一个执行小写、标准停用词的字段分析器,然后是自定义同义词:

company => co

最后,自定义停用词:

co

我们已经索引了一个供应商名称:Great Spaulding Company,它在 Elasticsearch 中归结为同义词和停用词后的 2 个术语:greatspaulding

我正在尝试构建我的查询,以便每个术语“必须”匹配,模糊或精确,具体取决于术语长度。

我得到了我想要的结果,除非其中一个术语恰好是同义词或停用词并且足够长以至于我的代码为其添加了模糊性,例如company~1,在这种情况下,它不再被视为同义词或停用词,我的查询不返回匹配项,因为“公司”从未存储在第一位 b/c 它变成“co”,然后作为停用词被删除。

是时候写一些代码了。这可能看起来有点老套,但我尝试了很多方法,并使用simpleQueryStringwithAndAsDefaultOperator 并构建我自己的短语似乎让我最接近我需要的结果(但我愿意接受建议)。我正在做类似的事情:

// assume passed in search String of "Great Spaulding Company"
String vendorName = "Great Spaulding Company";  
List<String> vendorNameTerms = Arrays.asList(vendorName.split(" "));
List<String> qualifiedTerms = Lists.newArrayList();

vendorNameTerms.forEach(term -> {
    int editDistance = getEditDistance(term); // 1..5 = 0, 6..10 = 1, > 10 = 2 
    int prefixLength = getPrefixLength(term); //appears of no use with simpleQueryString

    String fuzzyMarker = editDistance > 0 ? "~" + editDistance : "";
    qualifiedTerms.add(String.format("%s%s", term, fuzzyMarker));
});

// join my terms back together with their optional fuzziness marker
String phrase = qualifiedTerms.stream().collect(Collectors.joining(" "));

bool.should(
        qb.simpleQueryString()
                .onField("vendorNames.vendorName")
                .withAndAsDefaultOperator()
                .matching(phrase)
                .createQuery()
);

如上所述,我发现只要不对可能的同义词或停用词添加任何模糊性,查询就会找到匹配项。所以这些短语返回匹配: "Great Spaulding~1""Great Spaulding~1 Co""Spaulding Co"

但是由于我的代码不知道哪些术语是同义词或停用词,它会盲目地查看术语长度并说,哦,'Company' 大于 5 个字符,我会使其模糊,它构建了这些不返回匹配项的各种短语: "Great Spaulding~1 Company~1""Great Company~1"

  1. 为什么 Elasticsearch 不将 Company~1 作为同义词处理?
  2. 关于如何使用 simpleQueryString 或 另一个 DSL 查询?
  3. 每个人都如何处理可能包含停用词的文本的模糊搜索?

[编辑] 我的分析器通常会删除的标点符号也会出现同样的问题。我不能在查询 b/c 中的模糊搜索字符串中包含任何标点符号,ES 分析器似乎没有将其视为非模糊的,并且我没有得到匹配结果。

基于上述搜索字符串的示例:Great Spaulding Company., 在我的代码中内置到短语 Great Spaulding~1 Company.,~1 并且 ES 不会删除标点符号或识别同义词 Company

我将尝试调用 ES _analyze REST api 的技巧,以便它告诉我应该在查询中包含哪些标记,尽管这会增加我构建的每个查询的开销。类似于http://localhost:9200/myEntity/_analyze?analyzer=vendorNameAnalyzer&amp;text=Great Spaulding Company., 产生3 个令牌:greatspauldingcompany

【问题讨论】:

    标签: java elasticsearch lucene hibernate-search


    【解决方案1】:

    为什么 Elasticsearch 不将 Company~1 作为同义词处理?

    我猜这是因为模糊查询是"term-level" queries,这意味着它们是根据准确的术语而不是分析的文本进行操作的。如果您的术语在分析后解析为多个标记,我认为为模糊查询定义可接受的行为并不容易。

    有更详细的解释there(我相信它仍然适用于Elasticsearch 5.6中使用的Lucene版本)。

    知道如何使用 simpleQueryString 或其他 DSL 查询来完成这项工作吗? 每个人都如何处理可能包含停用词的文本的模糊搜索?

    您可以尝试反转您的同义词:使用co =&gt; company 而不是company =&gt; co,这样即使“compayn”未被分析,compayn~1 这样的查询也会匹配。但这当然不是一个令人满意的解决方案,因为其他需要分析的示例仍然无法正常工作,例如 Company~1

    以下是替代解决方案。

    解决方案 1:模糊的“匹配”查询

    This article 描述了一种执行模糊搜索的方法,并特别解释了几种模糊查询之间的区别。

    不幸的是,“简单查询字符串”查询中的模糊查询似乎被翻译为不执行分析的查询类型。

    但是,根据您的要求,"match" query 可能就足够了。为了访问 Elasticsearch 提供的所有设置,您将不得不回退到原生查询构建:

        QueryDescriptor query = ElasticsearchQueries.fromJson(
                "{ 'query': {"
                    + "'match' : {"
                        + "'vendorNames.vendorName': {"
                            // Not that using a proper JSON framework would be better here, to avoid problems with quotes in the terms
                            + "'query': '" + userProvidedTerms + "',"
                            + "'operator': 'and',"
                            + "'fuzziness': 'AUTO'"
                        + "}"
                    + "}"
                + " } }"
        );
        List<?> result = session.createFullTextQuery( query ).list();
    

    请参阅this page,详细了解上述示例中“AUTO”的含义。

    请注意,在 Hibernate Search 6 发布之前,您不能将如上所示的本机查询与 Hibernate Search DSL 混合使用。您可以使用 DSL,也可以使用本机查询,但不能在同一个查询中同时使用。

    解决方案 2:ngram

    在我看来,当查询来自您的用户并且这些用户不是 Lucene 专家时,您最好的选择是完全避免解析查询。查询解析涉及(至少部分)文本分析,文本分析最好留给 Lucene/Elasticsearch。

    那么你所能做的就是配置分析器。

    使用这些工具添加“模糊性”的一种方法是使用NGram filter。以min_gram = 3max_gram = 3 为例:

    • 诸如“company”之类的索引字符串将被索引为["com", "omp", "mpa", "pan", "any"]
    • 诸如“compayn”之类的查询,一旦分析,将被转换为(本质上是com OR omp OR mpa OR pay OR ayn
    • 这样的查询可能会匹配很多文档,但在按分数排序时,“Great Spaulding Company”的文档会排在最前面,因为它几乎匹配所有 ngram。

    我在示例中使用了参数值 min_gram = 3max_gram = 3,但在现实世界的应用程序中,min_gram = 3max_gram = 5 这样的值会更好,因为添加的更长的 ngram 会提供更好的搜索分数与索引词的较长部分匹配的词。

    当然,如果你不能按分数排序,或者如果你不能在结果中接受太多的尾随部分匹配,那么这个解决方案将不适合你。

    【讨论】:

    • 感谢 Yoann,您在这里提供了很多很好的信息。在我们的情况下,我们正在使用非常特殊的精确和可能的匹配规则迁移现有代码。我们有 4500 个单元测试和大量这些测试的“精确”和“可能”匹配规则,以各种组合形式对姓名、地址、电话号码等进行匹配。我解决它的方法(不改变我们现有的规则)是升级到 Hibernate ORM 5.3.3 和 Hibernate-Search 5.10,这样我就可以使用 Elasticsearch RestClient 直接向 ES 发出 _analyze 调用,然后使用返回的令牌来构建我的查询。
    • 我知道这在大局中可能不是理想的 b/c,ES 最终会为我构建的每个查询分析 1 个额外的时间,但它让我知道,在我构建查询之前,输入字符串是否可以分析为空。例如,“公司”的供应商名称在分析后被完全删除,所以我什至不想将它包含在我的查询中。它还让 ES 确定标记,而不是我尝试解析字符串并手动为每个标记添加模糊性。我仍然添加了模糊性,但现在可以使用正确的 HibernateSearch .keyword().fuzzy() 语法。
    猜你喜欢
    • 2012-05-29
    • 1970-01-01
    • 1970-01-01
    • 2012-06-15
    • 1970-01-01
    • 2013-11-15
    • 2023-03-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多