2011-09-30 3 views
7

Dans Solr (3.3), est-il possible de faire un champ lettre par lettre consultable via un EdgeNGramFilterFactory et également sensible aux requêtes de phrase?Solr: requête phrase exacte avec un EdgeNGramFilterFactory

Par exemple, je suis à la recherche d'un champ qui, si contenant "contrat informatique", on trouvera si les types d'utilisateurs:

  • Contrat
  • informatique
  • Contr
  • Informa
  • "contrat informatique"
  • "info contrat" ​​

Actuellement, je fait quelque chose comme ceci:

<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> 

... mais il a échoué sur les requêtes de phrase.

Quand je regarde dans l'analyseur de schéma dans solr admin, je trouve que « contrat informatique » a généré les jetons Suivis:

[...] contr contra contrat in inf info infor inform [...] 

Ainsi, la requête fonctionne avec « contrat dans » (jetons consécutifs), mais pas "contrat inf" (parce que ces deux jetons sont séparés).

Je suis assez sûr que tout type de stemming peut fonctionner avec des requêtes de phrase, mais je ne peux pas trouver le bon tokenizer de filtre à utiliser avant le EdgeNGramFilterFactory.

Répondre

2

Comme hélas je ne pouvais pas réussir à utiliser un PositionFilter droit comme Jayendra Patil a suggéré (PositionFilter fait toute une requête OU requête booléenne), j'ai utilisé un une approche différente.

Toujours avec le EdgeNGramFilter, j'ai ajouté le fait que chaque mot-clé que l'utilisateur a tapé est obligatoire, et a désactivé toutes les phrases. Si l'utilisateur demande "cont info", il se transforme en +cont +info. C'est un peu plus permissif qu'une vraie phrase serait, mais il a réussi à faire ce que je veux (et ne renvoie pas les résultats avec un seul terme des deux).Le seul inconvénient de cette solution est que les termes peuvent être permutés dans les résultats (un document avec "contrat informatique" sera également trouvé), mais ce n'est pas grave.

+0

Salut, Xavier. Pouvez-vous s'il vous plaît expliquer comment avez-vous transformé "cont info" en + cont + info y at-il une classe out-of-the-box pour cela? Ou est-ce simplement identifier les doubles citations et les transformer manuellement? J'essaie de résoudre ceci: http: // stackoverflow.com/questions/37033381/solr-search-field-best-practices – wattale

+0

Ce fut une opération manuelle, à la recherche de guillemets et en ajoutant le signe plus. Je n'ai pas trouvé quelque chose qui pourrait automatiser cela pour moi: -/ –

+0

Merci pour la réponse xavier, Pour moi aussi, après avoir rampé tellement de contenu ne pouvait pas trouver une solution hors de la boîte. Je pensais que je réinvente la roue en faisant cela manuellement. Mais je suppose que le faire manuellement est la seule option disponible: | – wattale

1

Voici ce que je pensais -
Pour que les ngrammes correspondent, la position des jetons générés pour chaque mot devrait être la même.
J'ai vérifié pour le filtre de bord grammes et il incrémente les jetons, et n'a trouvé aucun paramètre pour l'empêcher.
Il y a un filtre de position disponible et cela maintient la position des jetons au même jeton qu'au début.
Donc, si la configuration suivante est utilisée, tous les jetons sont à la même position et correspondent à la requête de phrase (les mêmes positions de jetons correspondent à des phrases)
Je l'ai vérifié via l'outil d'analyse et les requêtes correspondantes.

Vous pourriez vouloir essayer l'indice: -

<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> 
+0

Le Idée soignée, mais qui ne semble pas fonctionner: -/Même si j'ai des correspondances via l'outil d'administration, une vraie requête ne renvoie rien (probablement parce que dans l'outil d'analyse, la façon dont les tokens sont mis en évidence ne dérange pas). Aussi, [PositionFilter] (http://tinyurl.com/solr-positionfilter) rend la requête _boolean_ comme dit sur le wiki, donc "contrat informatique" ou encore "+ contrat + informatique" renvoie les documents avec "contrat" ​​mais aussi sans "informatique" comme opérateur par défaut est un OU. Une alternative possible serait de transformer la requête en + contrat + informatique, je pense. –

4

La recherche par phrase exacte ne fonctionne pas car le paramètre slop de requête est égal à 0 par défaut. Recherche d'une phrase '' Hello World '' recherche les termes avec des positions séquentielles. Je souhaite EdgeNGramFilter avait un paramètre pour contrôler le positionnement de sortie, cela ressemble à un vieux question.

En réglant le paramètre qs sur une valeur très élevée (supérieure à la distance maximale entre ngrammes), vous pouvez récupérer des phrases. Ceci résout partiellement le problème permettant des phrases, mais pas exactes, des permutations seront également trouvées. Alors que la recherche de "contrat informatique" correspondrait texte comme "... contrat abandonné. Informatique ..."

enter image description here

Pour soutenir requête exacte phrase je finis par utiliser separate fields for ngrams.

Etapes nécessaires:

Définir les types de champs séparés pour indexer les valeurs et grammes réguliers:

<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> 

Dites Solr copy fields lors de l'indexation:

Vous pouvez définir la réflexion de ngrams distincts pour chaque champ:

<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"/> 

Ou vous pouvez mettre tous ngrams dans un champ:

<field name="heap_ngrams" type="ngrams" indexed="true" stored="false"/> 
<copyField source="*_text" dest="heap_ngrams"/> 

Notez que vous ne serez pas en mesure de séparer boosters dans ce cas.

Et la dernière chose est de spécifier les champs ngrams et boosters dans la requête. Une façon consiste à configurer votre application. Une autre façon est de spécifier « ajoute » params dans le solrconfig.xml

<lst name="appends"> 
    <str name="qf">heap_ngrams</str> 
    </lst> 
1

J'ai fait un correctif pour EdgeNGramFilter si les positions dans un jeton ne sont pas incrémentés plus:

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; 
    } 
} 
Questions connexes