Come ordinare i risultati della ricerca su più campi utilizzando una funzione di ponderazione?

StackOverflow https://stackoverflow.com/questions/817998

  •  03-07-2019
  •  | 
  •  

Domanda

Ho un indice Lucene in cui ogni documento ha diversi campi che contengono valori numerici. Ora vorrei ordinare i risultati della ricerca su una somma ponderata di questo campo. Ad esempio:

field1=100
field2=002
field3=014

E la funzione di ponderazione è simile a:

f(d) = field1 * 0.5 + field2 * 1.4 + field3 * 1.8

I risultati devono essere ordinati per f (d) dove d rappresenta il documento. La funzione di ordinamento dovrebbe essere non statica e potrebbe differire da una ricerca all'altra perché i fattori costanti sono influenzati dall'utente che esegue la ricerca.

Qualcuno ha idea di come risolvere questo o forse un'idea su come raggiungere questo obiettivo in un altro modo?

È stato utile?

Soluzione

Potresti provare a implementare un personalizzato ScoreDocComparator . Ad esempio:

public class ScaledScoreDocComparator implements ScoreDocComparator {

    private int[][] values;
    private float[] scalars;

    public ScaledScoreDocComparator(IndexReader reader, String[] fields, float[] scalars) throws IOException {
        this.scalars = scalars;
        this.values = new int[fields.length][];
        for (int i = 0; i < values.length; i++) {
            this.values[i] = FieldCache.DEFAULT.getInts(reader, fields[i]);
        }
    }

    protected float score(ScoreDoc scoreDoc) {
        int doc = scoreDoc.doc;

        float score = 0;
        for (int i = 0; i < values.length; i++) {
            int value = values[i][doc];
            float scalar = scalars[i];
            score += (value * scalar);
        }
        return score;
    }

    @Override
    public int compare(ScoreDoc i, ScoreDoc j) {
        float iScore = score(i);
        float jScore = score(j);
        return Float.compare(iScore, jScore);
    }

    @Override
    public int sortType() {
        return SortField.CUSTOM;
    }

    @Override
    public Comparable<?> sortValue(ScoreDoc i) {
        float score = score(i);
        return Float.valueOf(score);
    }

}

Ecco un esempio di ScaledScoreDocComparator in azione. Credo che funzioni nel mio test, ma ti incoraggio a dimostrarlo contro i tuoi dati.

final String[] fields = new String[]{ "field1", "field2", "field3" };
final float[] scalars = new float[]{ 0.5f, 1.4f, 1.8f };

Sort sort = new Sort(
    new SortField(
        "",
        new SortComparatorSource() {
            public ScoreDocComparator newComparator(IndexReader reader, String fieldName) throws IOException {
                return new ScaledScoreDocComparator(reader, fields, scalars);
            }
        }
    )
);

IndexSearcher indexSearcher = ...;
Query query = ...;
Filter filter = ...; // can be null
int nDocs = 100;

TopFieldDocs topFieldDocs = indexSearcher.search(query, filter, nDocs, sort);
ScoreDoc[] scoreDocs = topFieldDocs.scoreDocs;

Bonus!

Sembra che gli sviluppatori Lucene stiano deprecando l'interfaccia ScoreDocComparator (attualmente è deprecato nel repository Subversion). Ecco un esempio del ScaledScoreDocComparator modificato per aderire al successore del ScoreDocComparator , FieldComparator :

public class ScaledComparator extends FieldComparator {

    private String[] fields;
    private float[] scalars;
    private int[][] slotValues;
    private int[][] currentReaderValues;
    private int bottomSlot;

    public ScaledComparator(int numHits, String[] fields, float[] scalars) {
        this.fields = fields;
        this.scalars = scalars;

        this.slotValues = new int[this.fields.length][];
        for (int fieldIndex = 0; fieldIndex < this.fields.length; fieldIndex++) {
            this.slotValues[fieldIndex] = new int[numHits];
        }

        this.currentReaderValues = new int[this.fields.length][];
    }

    protected float score(int[][] values, int secondaryIndex) {
        float score = 0;

        for (int fieldIndex = 0; fieldIndex < fields.length; fieldIndex++) {
            int value = values[fieldIndex][secondaryIndex];
            float scalar = scalars[fieldIndex];
            score += (value * scalar);
        }

        return score;
    }

    protected float scoreSlot(int slot) {
        return score(slotValues, slot);
    }

    protected float scoreDoc(int doc) {
        return score(currentReaderValues, doc);
    }

    @Override
    public int compare(int slot1, int slot2) {
        float score1 = scoreSlot(slot1);
        float score2 = scoreSlot(slot2);
        return Float.compare(score1, score2);
    }

    @Override
    public int compareBottom(int doc) throws IOException {
        float bottomScore = scoreSlot(bottomSlot);
        float docScore = scoreDoc(doc);
        return Float.compare(bottomScore, docScore);
    }

    @Override
    public void copy(int slot, int doc) throws IOException {
        for (int fieldIndex = 0; fieldIndex < fields.length; fieldIndex++) {
            slotValues[fieldIndex][slot] = currentReaderValues[fieldIndex][doc];
        }
    }

    @Override
    public void setBottom(int slot) {
        bottomSlot = slot;
    }

    @Override
    public void setNextReader(IndexReader reader, int docBase, int numSlotsFull) throws IOException {
        for (int fieldIndex = 0; fieldIndex < fields.length; fieldIndex++) {
            String field = fields[fieldIndex];
            currentReaderValues[fieldIndex] = FieldCache.DEFAULT.getInts(reader, field);
        }
    }

    @Override
    public int sortType() {
        return SortField.CUSTOM;
    }

    @Override
    public Comparable<?> value(int slot) {
        float score = scoreSlot(slot);
        return Float.valueOf(score);
    }

}

L'uso di questa nuova classe è molto simile all'originale, tranne per il fatto che la definizione dell'oggetto sort è leggermente diversa:

final String[] fields = new String[]{ "field1", "field2", "field3" };
final float[] scalars = new float[]{ 0.5f, 1.4f, 1.8f };

Sort sort = new Sort(
    new SortField(
        "",
        new FieldComparatorSource() {
            public FieldComparator newComparator(String fieldname, int numHits, int sortPos, boolean reversed) throws IOException {
                return new ScaledComparator(numHits, fields, scalars);
            }
        }
    )
);

Altri suggerimenti

Sto pensando che un modo per farlo sarebbe quello di accettare questi come parametri per la tua funzione di ordinamento:

numero di campi, matrice di documenti, elenco di fattori di peso (basato sul numero di campi)

Calcola la funzione di pesatura per ciascun documento, memorizzando il risultato in un array separato nello stesso ordine dell'array di documenti. Quindi, esegui qualsiasi ordinamento desideri (l'ordinamento rapido sarebbe probabilmente il migliore), assicurandoti di ordinare non solo l'array f (d), ma anche l'array di documenti. Restituisce l'array di documenti ordinati e il gioco è fatto.

Implementa la tua classe di somiglianza e sovrascrivi idf (Termine, Searcher) . In questo metodo, è possibile restituire il punteggio come segue. if (term.field.equals (" field1 ") {

    if (term.field.equals("field1") {
        score = 0.5 * Integer.parseInt(term.text());
    } else if (term.field.equals("field2") {
        score = 1.4 * Integer.parseInt(term.text());
    } // and so on
    return score;

Quando si esegue la query, assicurarsi che sia su tutti i campi. Questa è la query dovrebbe apparire come

  

field1: term field2: term field3: term

Il punteggio finale aggiungerà anche alcuni pesi basati sulla normalizzazione della query. Ma ciò non influirà sulla classificazione relativa dei documenti secondo l'equazione fornita da te.

Crea un wrapper che detenga la valutazione ed è comparabile. Qualcosa del tipo:

public void sort(Datum[] data) {
   Rating[] ratings = new Rating[data.length];
   for(int i=0;i<data.length;i++)
     rating[i] = new Rating(data[i]);
   Arrays.sort(rating);
   for(int i=0;i<data.length;i++)
     data[i] = rating[i].datum;
}

class Rating implements Comparable<Datum> {
   final double rating;
   final Datum datum;

   public Rating(Datum datum) {
      this.datum = datum;
      rating = datum.field1 * 0.5 + datum.field2 * 1.4 + datum.field3 * 1.8
   }

   public int compareTo(Datum d) {
      return Double.compare(rating, d.rating);
   }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top