2011-09-30 9 views
7

In Solr (3.3), è possibile rendere un campo lettera per lettera ricercabile tramite un EdgeNGramFilterFactory e anche per le query a frase?Solr: query a frase esatta con EdgeNGramFilterFactory

Per esempio, io sono alla ricerca di un campo che, se contenente "contrat informatique", verrà trovato se l'utente digita:

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

Attualmente, ho fatto qualcosa di simile:

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

... ma è venuto a mancare su query frase.

Quando mi guardo allo analizzatore schema in solr admin, trovo che "contrat informatique" ha generato i token seguenti:

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

Quindi la query funziona con "contratto in" (token consecutivi), ma non "contrat inf" (perché questi due token sono separati).

Sono abbastanza sicuro che qualsiasi tipo di derivazione possa funzionare con le query a frase, ma non riesco a trovare il giusto tokenizzatore del filtro da usare prima dello EdgeNGramFilterFactory.

risposta

2

Come ahimè non ho potuto gestire usare un PositionFilter destra come Jayendra Patil ha suggerito (positionFilter fa alcuna una query o query booleana), ho usato un approccio diverso.

Sempre con lo EdgeNGramFilter, ho aggiunto il fatto che ogni parola chiave digitata dall'utente è obbligatoria e disabilita tutte le frasi.

Quindi, se l'utente richiede "cont info", si trasforma in +cont +info. È un po 'più permissivo che una frase vera sarebbe, ma è riuscita a fare ciò che voglio (e non restituisce risultati con un solo termine tra i due).

L'unico contro a questa soluzione è che i termini possono essere permutati nei risultati (quindi verrà trovato anche un documento con "informatique contrat"), ma non è un grosso problema.

+0

Ciao, Xavier. Per favore, puoi spiegare come hai trasformato "cont info" in + cont + info? C'è qualche classe di utilizzo fuori dalla scatola per questo? Oppure è sufficiente identificare le doppie quotazioni e trasformarle manualmente? Sto cercando di risolvere questo: http: // StackOverflow.it/questions/37033381/solr-search-field-best-practice – wattale

+0

Si trattava di un'operazione manuale, alla ricerca di doppie quotazioni e aggiunta del segno più. Non ho trovato nulla che potesse automatizzare questo per me: -/ –

+0

Grazie per la risposta xavier, anche per me dopo aver strisciato così tanto contenuto non ho potuto trovare una soluzione pronta per l'uso. Ho pensato di reinventare la ruota facendo questo manualmente. Ma immagino che farlo manualmente sia l'unica opzione disponibile: | – wattale

1

Ecco cosa stavo pensando:
Per gli ngram da associare a frase la posizione dei token generati per ogni parola deve essere la stessa.
Ho controllato il filtro dei bordi e incrementa i token e non ho trovato alcun parametro per impedirlo.
È disponibile un filtro di posizione che mantiene la posizione dei token sullo stesso token dell'inizio.
Quindi, se viene utilizzata la seguente configurazione, tutti i token si trovano nella stessa posizione e corrispondono alla query frase (le stesse posizioni dei token sono abbinate come frasi)
Ho controllato tramite lo strumento anaylsis e le query sono state abbinate.

così si potrebbe desiderare di provare il suggerimento: -

<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

Il l'idea è chiara, ma non sembra funzionare comunque: -/Anche se ho ottenuto le corrispondenze tramite lo strumento di analisi dell'amministratore, una query reale non restituisce nulla (probabilmente perché nello strumento di analisi, il modo in cui evidenzia i token non si preoccupa delle frasi). Inoltre, [PositionFilter] (http://tinyurl.com/solr-positionfilter) rende la query _boolean_ come detto sul wiki, quindi "contrat informatique" o anche "+ contrat + informatique" restituisce i documenti con "contrat" ​​ma anche senza "informatique" come l'operatore predefinito è un OR. Una possibile alternativa sarebbe quella di trasformare la query in + contrat + informatique, credo. –

4

La ricerca frase esatta non funziona a causa del parametro intervallo di query = 0 per impostazione predefinita. Cercando una frase '"Hello World"' cerca termini con posizioni sequenziali. Vorrei che EdgeNGramFilter avesse un parametro per controllare il posizionamento dell'output, sembra un vecchio question.

Impostando il parametro qs su un valore molto elevato (più della distanza massima tra ngrams) è possibile recuperare le frasi. Questo risolve parzialmente il problema consentendo di trovare frasi, ma non esatte, permutazioni. Così quella ricerca di "contrat informatique" sarebbe partita testo come "... contratto abbandonata. Informatique ..."

enter image description here

Per supportare precisa interrogazione frase finisco per usare separate fields for ngrams.

Passi necessari:

definire i tipi di campo distinto per indicizzare i valori normali e grammi:

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

Dillo solr a copy fields quando l'indicizzazione:

È possibile definire ngrams separati riflessione per ogni field:

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

Oppure si può mettere tutti ngrams in un campo:

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

Notare che non sarete in grado di separare i ripetitori in questo caso.

E l'ultima cosa è specificare i campi ngrams e booster nella query. Un modo è quello di configurare la tua applicazione. Un altro modo è quello di specificare "aggiunge" params nella solrconfig.xml

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

Ho fatto una correzione per EdgeNGramFilter così posizioni all'interno di un token non vengono incrementati più:

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; 
    } 
} 
Problemi correlati