使用 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
架构更改后的分析器:
问题:
为什么 "WalMart" 与我的初始架构不匹配 "Walmart"?
Solr 分析器清楚地告诉我它在索引时间内产生了 3 个标记:wal
、mart
、walmart
。在查询期间:它产生了 1 个令牌:walmart
(虽然不清楚为什么它只产生 1 个令牌),我不明白为什么它不匹配,因为 walmart
包含在两个查询中和索引标记。
我在这里提到的问题只是一个用例。还有更复杂的,例如:
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)神奇地解决了这个问题!我不再有任何限制,我的查询分析器也按预期运行。
我的架构:
<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
架构更改后的分析器:
问题:
为什么 "WalMart" 与我的初始架构不匹配 "Walmart"? Solr 分析器清楚地告诉我它在索引时间内产生了 3 个标记:
wal
、mart
、walmart
。在查询期间:它产生了 1 个令牌:walmart
(虽然不清楚为什么它只产生 1 个令牌),我不明白为什么它不匹配,因为walmart
包含在两个查询中和索引标记。我在这里提到的问题只是一个用例。还有更复杂的,例如:
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)神奇地解决了这个问题!我不再有任何限制,我的查询分析器也按预期运行。