在Solr(3.3)中,是否可以通过EdgeNGramFilterFactory
逐字母搜索并且对短语查询敏感?
例如,我正在寻找一个字段,如果包含“contrat informatique”,将在用户输入时找到:
目前,我做了类似的事情:
<fieldtype name="terms" class="solr.TextField">
<analyzer type="index">
<charFilter class="solr.MappingCharFilterFactory" mapping="mapping-ISOLatin1Accent.txt"/>
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0" splitOnCaseChange="1"/>
<tokenizer class="solr.LowerCaseTokenizerFactory"/>
<filter class="solr.EdgeNGramFilterFactory" minGramSize="2" maxGramSize="15" side="front"/>
</analyzer>
<analyzer type="query">
<charFilter class="solr.MappingCharFilterFactory" mapping="mapping-ISOLatin1Accent.txt"/>
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0" splitOnCaseChange="1"/>
<tokenizer class="solr.LowerCaseTokenizerFactory"/>
</analyzer>
</fieldtype>
...但短语查询失败。
当我查看solr admin中的模式分析器时,我发现“contrat informatique”生成了以下标记:
[...] contr contra contrat in inf info infor inform [...]
因此查询使用“contrat in”(连续标记),但不是“contrat inf”(因为这两个标记是分开的)。
我很确定任何类型的词干都可以用于短语查询,但我找不到在EdgeNGramFilterFactory
之前使用的过滤器的正确标记器。
答案 0 :(得分:5)
由于查询slop参数= 0,默认情况下,精确短语搜索不起作用。 在搜索短语“Hello World”时,它会搜索具有连续位置的词语。 我希望EdgeNGramFilter有一个参数来控制输出定位,这看起来像一个旧的question。
通过将qs参数设置为某个非常高的值(超过ngrams之间的最大距离),您可以返回短语。这部分地解决了允许短语但不精确的排列的问题。 因此,搜索“contrat informatique”会匹配“...被放弃的合同。信息......”等文字。
要支持完全词组查询,我最终会使用separate fields for ngrams。
所需步骤:
定义单独的字段类型以索引常规值和克数:
<fieldType name="text" class="solr.TextField" omitNorms="false">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
<fieldType name="ngrams" class="solr.TextField" omitNorms="false">
<analyzer type="index">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.EdgeNGramFilterFactory" minGramSize="2" maxGramSize="15" side="front"/>
</analyzer>
<analyzer type="query">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
索引时告诉solr copy fields:
您可以为每个字段定义单独的ngrams反射:
<field name="contact_ngrams" type="ngrams" indexed="true" stored="false"/>
<field name="product_ngrams" type="ngrams" indexed="true" stored="false"/>
<copyField source="contact_text" dest="contact_ngrams"/>
<copyField source="product_text" dest="product_ngrams"/>
或者您可以将所有ngrams放入一个字段:
<field name="heap_ngrams" type="ngrams" indexed="true" stored="false"/>
<copyField source="*_text" dest="heap_ngrams"/>
请注意,在这种情况下,您无法分离助推器。
最后一件事是在查询中指定ngrams字段和助推器。 一种方法是配置您的应用程序。 另一种方法是在solrconfig.xml
中指定“追加”参数 <lst name="appends">
<str name="qf">heap_ngrams</str>
</lst>
答案 1 :(得分:2)
唉,我无法像Jayendra Patil建议的那样使用PositionFilter
权限(PositionFilter使任何查询成为一个OR布尔查询),我使用了不同的方法。
仍然使用EdgeNGramFilter
,我添加了一个事实,即用户输入的每个关键字都是必需的,并禁用所有短语。
因此,如果用户要求"cont info"
,则会转换为+cont +info
。一个真正的短语会更加宽容,但它设法做了我想做的事情(并且只返回两个中只有一个术语的结果)。
对这种解决方法的唯一反对意见是条款可以在结果中排列(因此也会找到带有“informatique contrat”的文档),但这并不是什么大问题。
答案 2 :(得分:1)
这就是我的想法 -
对于ngram的短语匹配,为每个单词生成的标记的位置应该相同
我检查了边克过滤器,它增加了令牌,并没有找到任何参数来防止它
有一个位置过滤器可用,这使得令牌位置保持与开始时相同的标记
因此,如果使用以下配置,则所有令牌都处于相同位置并且它与短语查询匹配(相同的令牌位置匹配为短语)
我通过anaylsis工具检查了它并匹配了查询。
所以你可能想尝试一下提示: -
<analyzer type="index">
<tokenizer class="solr.WhitespaceTokenizerFactory" />
<charFilter class="solr.MappingCharFilterFactory"
mapping="mapping-ISOLatin1Accent.txt" />
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1"
generateNumberParts="1" catenateWords="1" catenateNumbers="1"
catenateAll="0" splitOnCaseChange="1"/>
<filter class="solr.LowerCaseFilterFactory" />
<filter class="solr.EdgeNGramFilterFactory" minGramSize="2"
maxGramSize="15" side="front"/>
<filter class="solr.PositionFilterFactory" />
</analyzer>
答案 3 :(得分:1)
我已经修复了EdgeNGramFilter,因此令牌中的位置不再增加:
public class CustomEdgeNGramTokenFilterFactory extends TokenFilterFactory {
private int maxGramSize = 0;
private int minGramSize = 0;
@Override
public void init(Map<String, String> args) {
super.init(args);
String maxArg = args.get("maxGramSize");
maxGramSize = (maxArg != null ? Integer.parseInt(maxArg)
: EdgeNGramTokenFilter.DEFAULT_MAX_GRAM_SIZE);
String minArg = args.get("minGramSize");
minGramSize = (minArg != null ? Integer.parseInt(minArg)
: EdgeNGramTokenFilter.DEFAULT_MIN_GRAM_SIZE);
}
@Override
public CustomEdgeNGramTokenFilter create(TokenStream input) {
return new CustomEdgeNGramTokenFilter(input, minGramSize, maxGramSize);
}
}
public class CustomEdgeNGramTokenFilter extends TokenFilter {
private final int minGram;
private final int maxGram;
private char[] curTermBuffer;
private int curTermLength;
private int curGramSize;
private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
private final OffsetAttribute offsetAtt = addAttribute(OffsetAttribute.class);
private final PositionIncrementAttribute positionIncrementAttribute = addAttribute(PositionIncrementAttribute.class);
/**
* Creates EdgeNGramTokenFilter that can generate n-grams in the sizes of the given range
*
* @param input {@link org.apache.lucene.analysis.TokenStream} holding the input to be tokenized
* @param minGram the smallest n-gram to generate
* @param maxGram the largest n-gram to generate
*/
public CustomEdgeNGramTokenFilter(TokenStream input, int minGram, int maxGram) {
super(input);
if (minGram < 1) {
throw new IllegalArgumentException("minGram must be greater than zero");
}
if (minGram > maxGram) {
throw new IllegalArgumentException("minGram must not be greater than maxGram");
}
this.minGram = minGram;
this.maxGram = maxGram;
}
@Override
public final boolean incrementToken() throws IOException {
while (true) {
int positionIncrement = 0;
if (curTermBuffer == null) {
if (!input.incrementToken()) {
return false;
} else {
positionIncrement = positionIncrementAttribute.getPositionIncrement();
curTermBuffer = termAtt.buffer().clone();
curTermLength = termAtt.length();
curGramSize = minGram;
}
}
if (curGramSize <= maxGram) {
if (!(curGramSize > curTermLength // if the remaining input is too short, we can't generate any n-grams
|| curGramSize > maxGram)) { // if we have hit the end of our n-gram size range, quit
// grab gramSize chars from front
int start = 0;
int end = start + curGramSize;
offsetAtt.setOffset(start, end);
positionIncrementAttribute.setPositionIncrement(positionIncrement);
termAtt.copyBuffer(curTermBuffer, start, curGramSize);
curGramSize++;
return true;
}
}
curTermBuffer = null;
}
}
@Override
public void reset() throws IOException {
super.reset();
curTermBuffer = null;
}
}