Fix NDCG for empty search results (#29267)
Fixes and edge case where DiscountedCumulativeGain can return NaN as result of the quality metric calculation. This can happen when the search result set is empty and normalization is used. We should return 0 in this case. Also adding related unit tests to the other two metrics.
This commit is contained in:
parent
0028563aac
commit
2b07f63bd5
|
@ -140,9 +140,12 @@ public class DiscountedCumulativeGain implements EvaluationMetric {
|
||||||
|
|
||||||
if (normalize) {
|
if (normalize) {
|
||||||
Collections.sort(allRatings, Comparator.nullsLast(Collections.reverseOrder()));
|
Collections.sort(allRatings, Comparator.nullsLast(Collections.reverseOrder()));
|
||||||
double idcg = computeDCG(
|
double idcg = computeDCG(allRatings.subList(0, Math.min(ratingsInSearchHits.size(), allRatings.size())));
|
||||||
allRatings.subList(0, Math.min(ratingsInSearchHits.size(), allRatings.size())));
|
if (idcg > 0) {
|
||||||
dcg = dcg / idcg;
|
dcg = dcg / idcg;
|
||||||
|
} else {
|
||||||
|
dcg = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
EvalQueryQuality evalQueryQuality = new EvalQueryQuality(taskId, dcg);
|
EvalQueryQuality evalQueryQuality = new EvalQueryQuality(taskId, dcg);
|
||||||
evalQueryQuality.addHitsAndRatings(ratedHits);
|
evalQueryQuality.addHitsAndRatings(ratedHits);
|
||||||
|
|
|
@ -228,6 +228,10 @@ public class MeanReciprocalRank implements EvaluationMetric {
|
||||||
return NAME;
|
return NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the ranking of the first relevant document, or -1 if no relevant document was
|
||||||
|
* found
|
||||||
|
*/
|
||||||
int getFirstRelevantRank() {
|
int getFirstRelevantRank() {
|
||||||
return firstRelevantRank;
|
return firstRelevantRank;
|
||||||
}
|
}
|
||||||
|
|
|
@ -205,6 +205,32 @@ public class DiscountedCumulativeGainTests extends ESTestCase {
|
||||||
assertEquals(12.392789260714371 / 13.347184833073591, dcg.evaluate("id", hits, ratedDocs).getQualityLevel(), DELTA);
|
assertEquals(12.392789260714371 / 13.347184833073591, dcg.evaluate("id", hits, ratedDocs).getQualityLevel(), DELTA);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* test that metric returns 0.0 when there are no search results
|
||||||
|
*/
|
||||||
|
public void testNoResults() throws Exception {
|
||||||
|
Integer[] relevanceRatings = new Integer[] { 3, 2, 3, null, 1, null };
|
||||||
|
List<RatedDocument> ratedDocs = new ArrayList<>();
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
if (i < relevanceRatings.length) {
|
||||||
|
if (relevanceRatings[i] != null) {
|
||||||
|
ratedDocs.add(new RatedDocument("index", Integer.toString(i), relevanceRatings[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SearchHit[] hits = new SearchHit[0];
|
||||||
|
DiscountedCumulativeGain dcg = new DiscountedCumulativeGain();
|
||||||
|
EvalQueryQuality result = dcg.evaluate("id", hits, ratedDocs);
|
||||||
|
assertEquals(0.0d, result.getQualityLevel(), DELTA);
|
||||||
|
assertEquals(0, filterUnknownDocuments(result.getHitsAndRatings()).size());
|
||||||
|
|
||||||
|
// also check normalized
|
||||||
|
dcg = new DiscountedCumulativeGain(true, null, 10);
|
||||||
|
result = dcg.evaluate("id", hits, ratedDocs);
|
||||||
|
assertEquals(0.0d, result.getQualityLevel(), DELTA);
|
||||||
|
assertEquals(0, filterUnknownDocuments(result.getHitsAndRatings()).size());
|
||||||
|
}
|
||||||
|
|
||||||
public void testParseFromXContent() throws IOException {
|
public void testParseFromXContent() throws IOException {
|
||||||
assertParsedCorrect("{ \"unknown_doc_rating\": 2, \"normalize\": true, \"k\" : 15 }", 2, true, 15);
|
assertParsedCorrect("{ \"unknown_doc_rating\": 2, \"normalize\": true, \"k\" : 15 }", 2, true, 15);
|
||||||
assertParsedCorrect("{ \"normalize\": false, \"k\" : 15 }", null, false, 15);
|
assertParsedCorrect("{ \"normalize\": false, \"k\" : 15 }", null, false, 15);
|
||||||
|
|
|
@ -158,6 +158,13 @@ public class MeanReciprocalRankTests extends ESTestCase {
|
||||||
assertEquals(0.0, evaluation.getQualityLevel(), Double.MIN_VALUE);
|
assertEquals(0.0, evaluation.getQualityLevel(), Double.MIN_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testNoResults() throws Exception {
|
||||||
|
SearchHit[] hits = new SearchHit[0];
|
||||||
|
EvalQueryQuality evaluated = (new MeanReciprocalRank()).evaluate("id", hits, Collections.emptyList());
|
||||||
|
assertEquals(0.0d, evaluated.getQualityLevel(), 0.00001);
|
||||||
|
assertEquals(-1, ((MeanReciprocalRank.Breakdown) evaluated.getMetricDetails()).getFirstRelevantRank());
|
||||||
|
}
|
||||||
|
|
||||||
public void testXContentRoundtrip() throws IOException {
|
public void testXContentRoundtrip() throws IOException {
|
||||||
MeanReciprocalRank testItem = createTestItem();
|
MeanReciprocalRank testItem = createTestItem();
|
||||||
XContentBuilder builder = XContentFactory.contentBuilder(randomFrom(XContentType.values()));
|
XContentBuilder builder = XContentFactory.contentBuilder(randomFrom(XContentType.values()));
|
||||||
|
|
|
@ -142,6 +142,14 @@ public class PrecisionAtKTests extends ESTestCase {
|
||||||
assertEquals(0, ((PrecisionAtK.Breakdown) evaluated.getMetricDetails()).getRetrieved());
|
assertEquals(0, ((PrecisionAtK.Breakdown) evaluated.getMetricDetails()).getRetrieved());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testNoResults() throws Exception {
|
||||||
|
SearchHit[] hits = new SearchHit[0];
|
||||||
|
EvalQueryQuality evaluated = (new PrecisionAtK()).evaluate("id", hits, Collections.emptyList());
|
||||||
|
assertEquals(0.0d, evaluated.getQualityLevel(), 0.00001);
|
||||||
|
assertEquals(0, ((PrecisionAtK.Breakdown) evaluated.getMetricDetails()).getRelevantRetrieved());
|
||||||
|
assertEquals(0, ((PrecisionAtK.Breakdown) evaluated.getMetricDetails()).getRetrieved());
|
||||||
|
}
|
||||||
|
|
||||||
public void testParseFromXContent() throws IOException {
|
public void testParseFromXContent() throws IOException {
|
||||||
String xContent = " {\n" + " \"relevant_rating_threshold\" : 2" + "}";
|
String xContent = " {\n" + " \"relevant_rating_threshold\" : 2" + "}";
|
||||||
try (XContentParser parser = createParser(JsonXContent.jsonXContent, xContent)) {
|
try (XContentParser parser = createParser(JsonXContent.jsonXContent, xContent)) {
|
||||||
|
|
Loading…
Reference in New Issue