使用 space、连字符、大小写和标点符号的各种组合进行搜索

Search with various combinations of space, hyphen, casing and punctuations

我的架构:

<fieldType name="text" class="solr.TextField" positionIncrementGap="100">
  <analyzer>
    <tokenizer class="solr.WhitespaceTokenizerFactory"/>
    <filter class="solr.StopFilterFactory"
            ignoreCase="true"
            words="stopwords.txt"
            enablePositionIncrements="true"
            />
    <filter class="solr.WordDelimiterFilterFactory"
            generateWordParts="1" generateNumberParts="1"
            catenateWords="1" catenateNumbers="1" catenateAll="0"
            splitOnCaseChange="1" splitOnNumerics="0"/>
    <filter class="solr.LowerCaseFilterFactory"/>
    <filter class="solr.SnowballPorterFilterFactory" language="English"
            protected="protwords.txt"/>
  </analyzer>
</fieldType>

我想要的组合:

"Walmart", "WalMart", "Wal Mart", "Wal-Mart", "Wal-mart"

给定这些字符串中的任何一个,我想找到另一个。

所以,有 25 个这样的组合,如下所示:

(第一列表示搜索输入文本,第二列表示预期匹配)

(Walmart,Walmart)
(Walmart,WalMart)
(Walmart,Wal Mart)
(Walmart,Wal-Mart)
(Walmart,Wal-mart)
(WalMart,Walmart)
(WalMart,WalMart)
(WalMart,Wal Mart)
(WalMart,Wal-Mart)
(WalMart,Wal-mart)
(Wal Mart,Walmart)
(Wal Mart,WalMart)
(Wal Mart,Wal Mart)
(Wal Mart,Wal-Mart)
(Wal Mart,Wal-mart)
(Wal-Mart,Walmart)
(Wal-Mart,WalMart)
(Wal-Mart,Wal Mart)
(Wal-Mart,Wal-Mart)
(Wal-Mart,Wal-mart)
(Wal-mart,Walmart)
(Wal-mart,WalMart)
(Wal-mart,Wal Mart)
(Wal-mart,Wal-Mart)
(Wal-mart,Wal-mart)

我的架构的当前限制:

1. "Wal-Mart" -> "Walmart",
2. "Wal Mart" -> "Walmart",
3. "Walmart"  -> "Wal Mart",
4. "Wal-mart" -> "Walmart",
5. "WalMart"  -> "Walmart"

分析器截图:

我尝试了各种过滤器组合来解决这些限制,但我被提供的解决方案迷住了:Solr - case-insensitive search do not work

虽然它似乎克服了我的局限之一(参见#5 WalMart -> Walmart),但总体上比我之前的情况更糟糕。现在它不适用于以下情况:

(Wal Mart,WalMart), 
(Wal-Mart,WalMart), 
(Wal-mart,WalMart), 
(WalMart,Wal Mart)
besides cases 1 to 4 as mentioned above

架构更改后的分析器:

问题:

  1. 为什么 "WalMart" 与我的初始架构不匹配 "Walmart"? Solr 分析器清楚地告诉我它在索引时间内产生了 3 个标记:walmartwalmart。在查询期间:它产生了 1 个令牌:walmart(虽然不清楚为什么它只产生 1 个令牌),我不明白为什么它不匹配,因为 walmart 包含在两个查询中和索引标记。

  2. 我在这里提到的问题只是一个用例。还有更复杂的,例如:

    Words with apostrophes: "Mc Donalds", "Mc Donald's", "McDonald's", "Mc donalds", "Mc donald's", "Mcdonald's"

    Words with different punctuations: "Mc-Donald Engineering Company, Inc."

一般来说,针对这种需求对模式进行建模的最佳方法是什么? NGrams ?在不同字段(不同格式)中索引相同数据并使用 copyField 指令 (https://wiki.apache.org/solr/SchemaXml#Indexing_same_data_in_multiple_fields) ?这对性能有何影响?

编辑:我的 Solr 模式中的默认运算符是 AND。我无法将其更改为 OR。

我们将带连字符的单词视为一种特殊情况,并编写了一个自定义分析器,该分析器在索引时用于创建此标记的三个版本,因此在您的情况下,沃尔玛将成为沃尔玛、沃尔玛和沃尔玛。这些同义词中的每一个都是使用自定义 SynonymFilter 写出的,该自定义 SynonymFilter 最初改编自 Lucene in Action 一书中的示例。 SynonymFilter 位于 Whitespace 分词器和 Lowercase 分词器之间。

在搜索时,三个版本中的任何一个都会匹配索引中的同义词之一。

冒昧先对分析器做一些调整。我认为 WordDelimiterFilter 在功能上是第二步标记化,所以让我们把它放在 Tokenizer 之后。之后就不用再维护大小写了,接下来就是小写了。这对您的 StopFilter 更好,因为我们不需要再担心忽略大小写了。然后添加词干分析器。

<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0" splitOnCaseChange="1" splitOnNumerics="0"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory"
        words="stopwords.txt"
        enablePositionIncrements="true"
        />
<filter class="solr.SnowballPorterFilterFactory" language="English" protected="protwords.txt"/>

总而言之,这并不算太远。主要问题是 "Wal Mart" 与 "Walmart"。对于其中的每一个, WordDelimiterFilter 都与它无关,它是分词器在这里分裂。 "Wal Mart" 被分词器拆分。 "Walmart" 永远不会分裂,因为没有人能合理地知道应该在哪里分裂。

一个解决方案是改用 KeywordTokenizer,让 WordDelimiterFilter 执行 all 标记化,但这会导致其他问题(特别是在处理更长、更复杂的文本时,如您的 "Mc-Donald Engineering Company, Inc." 示例会出现问题)。

相反,我建议 ShingleFilter。这允许您将相邻的标记组合成一个标记以进行搜索。这意味着,当索引 "Wal Mart" 时,它将使用标记 "wal" 和 "mart" 并且还会索引术语 "walmart"。通常,它还会插入一个分隔符,但对于这种情况,您需要覆盖该行为,并指定分隔符 "".

我们现在将 ShingleFilter 放在末尾(如果将它放在词干分析器之前,它往往会搞砸词干提取):

<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0" splitOnCaseChange="1" splitOnNumerics="0"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory"
        words="stopwords.txt"
        enablePositionIncrements="true"
        />
<filter class="solr.SnowballPorterFilterFactory" language="English" protected="protwords.txt"/>
<filter class="solr.ShingleFilterFactory" maxShingleSize="2" tokenSeparator=""/>

这只会创建 2 个连续标记(以及原始单个标记)的组合,所以我假设你不需要匹配更多(如果你需要 "doremi" 以匹配 "Do Re Mi",例如)。但是对于给出的示例,这在我的测试中有效。

Why does "WalMart" not match "Walmart" with my initial schema?

因为您为 DisMax/eDismax 处理程序定义的 mm 参数值过高。我玩过它。当您将 mm 值定义为 100% 时,您将无法匹配。但是为什么?

因为您对查询和索引时间使用相同的分析器。您的搜索词 "WalMart" 被分成 3 个标记(单词)。即这些是 "wal"、"mart" 和 "walmart"。 Solr 现在将在计算 <str name="mm">100%</str>*.

时单独处理每个单词

顺便说一句,我已经重现了你的问题,但是当索引 Walmart,但使用 WalMart 查询时,问题就出现了。当反过来执行时,它工作正常。

您可以使用 LocalParams 覆盖它,您可以像这样重新表述您的查询 {!mm=1}WalMart

There are more slightly complex ones like [ ... ] "Mc Donald's" [ to match ] Words with different punctuations: "Mc-Donald Engineering Company, Inc."

这里也可以使用 mm 参数帮助。

In general, what's the best way to go around modeling the schema with this kind of requirement?

我同意 Sujit Pal 的观点,你应该去实现一个自己的副本 SynonymFilter。为什么?因为它的工作方式与其他过滤器和分词器不同。它创建标记来代替索引词的偏移量。

什么地方?它不会增加您查询的令牌数。您还可以执行反向连字符(连接两个由空格分隔的单词)。

But we are lacking a good synonyms.txt and cannot keep it up-to-date.

扩展或复制 SynonymFilter 时忽略静态映射。您可以删除映射单词的代码。您只需要偏移量处理。

Update 我认为您也可以尝试 PatternCaptureGroupTokenFilter,但是使用正则表达式处理公司名称可能很快就会遇到它的局限性。我稍后会研究这个。


* 您可以在 solrconfig.xml 中找到它,看看您的 <requestHandler ... />

在 solrconfig.xml 中升级 Lucene 版本(4.4 到 4.10)神奇地解​​决了这个问题!我不再有任何限制,我的查询分析器也按预期运行。