Como classificar os resultados da pesquisa em vários campos usando uma função de ponderação?

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

  •  03-07-2019
  •  | 
  •  

Pergunta

Eu tenho um índice Lucene, onde todo documento possui vários campos que contêm valores numéricos. Agora eu gostaria de classificar o resultado da pesquisa em uma soma ponderada desse campo. Por exemplo:

field1=100
field2=002
field3=014

E a função de ponderação se parece:

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

Os resultados devem ser ordenados por f(d) Onde d representa o documento. A função de classificação deve ser não estática e pode diferir da pesquisa para a pesquisa, porque os fatores constantes são influenciados pelo usuário que executa a pesquisa.

Alguém tem uma ideia de como resolver isso ou talvez uma idéia de como alcançar esse objetivo de outra maneira?

Foi útil?

Solução

Você pode tentar implementar um personalizado ScoredocComParator. Por exemplo:

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);
    }

}

Aqui está um exemplo de ScaledScoreDocComparator em ação. Acredito que funciona no meu teste, mas encorajo você a provar isso contra seus dados.

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;

Bônus!

Parece que os desenvolvedores do Lucene estão depreciando o ScoreDocComparator interface (atualmente está depreciada no repositório de subversão). Aqui está um exemplo do ScaledScoreDocComparator modificado para aderir a ScoreDocComparatorsucessor, 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);
    }

}

Usar esta nova classe é muito semelhante ao original, exceto que a definição do sort Objeto é um pouco diferente:

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);
            }
        }
    )
);

Outras dicas

Estou pensando que uma maneira de fazer isso seria aceitá -las como parâmetros para sua função de classificação:

Número de campos, matriz de documentos, lista de fatores de peso (com base no número de campos)

Calcule a função de pesagem para cada documento, armazenando o resultado em uma matriz separada na mesma ordem que a matriz de documentos. Em seguida, execute qualquer tipo que você desejar (classificar rápido provavelmente seria o melhor), certificando -se de que você está classificando não apenas a matriz f (d), mas também a matriz de documentos. Retorne a matriz de documentos classificados e terminar.

Implemente sua própria classe de similaridade e substitua IDF (termo, pesquisador) método. Neste método, você pode retornar a pontuação da seguinte forma. if (termin.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;

Ao executar a consulta, verifique se ela está em todos os campos. Essa consulta deve parecer

Field1: Termo Field2: Term Field3: Termo

A pontuação final também adicionará alguns pesos com base na normalização da consulta. Mas isso não afetará a classificação relativa dos documentos, conforme a equação dada por você.

Crie um invólucro que possua a classificação e é comparável. Algo como:

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);
   }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top