重み付け関数を使用して複数のフィールドで検索結果を並べ替える方法は?
質問
すべてのドキュメントに数値を含む複数のフィールドがあるLuceneインデックスがあります。ここで、このフィールドの加重合計で検索結果をソートしたいと思います。 例:
field1=100
field2=002
field3=014
そして重み関数は次のようになります:
f(d) = field1 * 0.5 + field2 * 1.4 + field3 * 1.8
結果は f(d)
の順に並べる必要があります。 d
はドキュメントを表します。ソート関数は非静的である必要があり、一定の要因は検索を実行するユーザーによって影響を受けるため、検索ごとに異なる可能性があります。
これを解決する方法や、この目標を別の方法で達成する方法を知っている人はいますか?
解決
カスタムを実装してみてください。 ScoreDocComparator 。例:
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);
}
}
実際の ScaledScoreDocComparator
の例を次に示します。私のテストでは機能すると思いますが、データに対して証明することをお勧めします。
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;
ボーナス!
Lucene開発者は ScoreDocComparator
インターフェースを非推奨にしているようです(現在Subversionリポジトリでは非推奨です)。以下は、 ScoreDocComparator
の後継である FieldComparator
に準拠するように変更された ScaledScoreDocComparator
の例です。
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);
}
}
この新しいクラスの使用は、 sort
オブジェクトの定義が少し異なることを除いて、元のクラスと非常に似ています:
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);
}
}
)
);
他のヒント
これを行う1つの方法は、これらをソート関数のパラメーターとして受け入れることだと考えています。
フィールド数、ドキュメントの配列、重み係数のリスト(フィールド数に基づく)
各ドキュメントの計量関数を計算し、ドキュメント配列と同じ順序で別の配列に結果を保存します。次に、必要な並べ替えを実行します(おそらく高速並べ替えが最適です)。f(d)配列だけでなく、ドキュメント配列も並べ替えていることを確認してください。ソートされたドキュメントの配列を返すと完了です。
独自の類似度クラスを実装し、 idf(Term、Searcher)メソッド。 このメソッドでは、次のようにスコアを返すことができます。 if(term.field.equals(&quot; field1&quot;){
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;
クエリを実行するときは、すべてのフィールドにあることを確認してください。つまり、クエリは
のようになりますfield1:term field2:term field3:term
最終スコアは、クエリの正規化に基づいていくつかの重みも追加します。しかし、それはあなたによって与えられた方程式に従って文書の相対的なランキングに影響を与えません。
評価を保持し、同等のラッパーを作成します。次のようなもの:
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);
}
}