Add `positive_score_impact` support for `rank_features` (#2725)

Adds positive_score_impact support for rank_features field mapper.

Signed-off-by: Yevhen Tienkaiev <hronom@gmail.com>
This commit is contained in:
Yevhen Tienkaiev 2022-04-19 07:38:54 +03:00 committed by GitHub
parent 8bfb082ee1
commit d8c815c6be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 126 additions and 9 deletions

View File

@ -42,7 +42,6 @@ import org.opensearch.index.query.QueryShardContext;
import org.opensearch.search.lookup.SearchLookup; import org.opensearch.search.lookup.SearchLookup;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -55,8 +54,18 @@ public class RankFeaturesFieldMapper extends ParametrizedFieldMapper {
public static final String CONTENT_TYPE = "rank_features"; public static final String CONTENT_TYPE = "rank_features";
private static RankFeaturesFieldType ft(FieldMapper in) {
return ((RankFeaturesFieldMapper) in).fieldType();
}
public static class Builder extends ParametrizedFieldMapper.Builder { public static class Builder extends ParametrizedFieldMapper.Builder {
private final Parameter<Boolean> positiveScoreImpact = Parameter.boolParam(
"positive_score_impact",
false,
m -> ft(m).positiveScoreImpact,
true
);
private final Parameter<Map<String, String>> meta = Parameter.metaParam(); private final Parameter<Map<String, String>> meta = Parameter.metaParam();
public Builder(String name) { public Builder(String name) {
@ -66,16 +75,17 @@ public class RankFeaturesFieldMapper extends ParametrizedFieldMapper {
@Override @Override
protected List<Parameter<?>> getParameters() { protected List<Parameter<?>> getParameters() {
return Collections.singletonList(meta); return List.of(meta, positiveScoreImpact);
} }
@Override @Override
public RankFeaturesFieldMapper build(BuilderContext context) { public RankFeaturesFieldMapper build(BuilderContext context) {
return new RankFeaturesFieldMapper( return new RankFeaturesFieldMapper(
name, name,
new RankFeaturesFieldType(buildFullName(context), meta.getValue()), new RankFeaturesFieldType(buildFullName(context), meta.getValue(), positiveScoreImpact.getValue()),
multiFieldsBuilder.build(this, context), multiFieldsBuilder.build(this, context),
copyTo.build() copyTo.build(),
positiveScoreImpact.getValue()
); );
} }
} }
@ -84,9 +94,12 @@ public class RankFeaturesFieldMapper extends ParametrizedFieldMapper {
public static final class RankFeaturesFieldType extends MappedFieldType { public static final class RankFeaturesFieldType extends MappedFieldType {
public RankFeaturesFieldType(String name, Map<String, String> meta) { private final boolean positiveScoreImpact;
public RankFeaturesFieldType(String name, Map<String, String> meta, boolean positiveScoreImpact) {
super(name, false, false, false, TextSearchInfo.NONE, meta); super(name, false, false, false, TextSearchInfo.NONE, meta);
setIndexAnalyzer(Lucene.KEYWORD_ANALYZER); setIndexAnalyzer(Lucene.KEYWORD_ANALYZER);
this.positiveScoreImpact = positiveScoreImpact;
} }
@Override @Override
@ -94,6 +107,10 @@ public class RankFeaturesFieldMapper extends ParametrizedFieldMapper {
return CONTENT_TYPE; return CONTENT_TYPE;
} }
public boolean positiveScoreImpact() {
return positiveScoreImpact;
}
@Override @Override
public Query existsQuery(QueryShardContext context) { public Query existsQuery(QueryShardContext context) {
throw new IllegalArgumentException("[rank_features] fields do not support [exists] queries"); throw new IllegalArgumentException("[rank_features] fields do not support [exists] queries");
@ -115,9 +132,18 @@ public class RankFeaturesFieldMapper extends ParametrizedFieldMapper {
} }
} }
private RankFeaturesFieldMapper(String simpleName, MappedFieldType mappedFieldType, MultiFields multiFields, CopyTo copyTo) { private final boolean positiveScoreImpact;
private RankFeaturesFieldMapper(
String simpleName,
MappedFieldType mappedFieldType,
MultiFields multiFields,
CopyTo copyTo,
boolean positiveScoreImpact
) {
super(simpleName, mappedFieldType, multiFields, copyTo); super(simpleName, mappedFieldType, multiFields, copyTo);
assert fieldType.indexOptions().compareTo(IndexOptions.DOCS_AND_FREQS) <= 0; assert fieldType.indexOptions().compareTo(IndexOptions.DOCS_AND_FREQS) <= 0;
this.positiveScoreImpact = positiveScoreImpact;
} }
@Override @Override
@ -164,6 +190,9 @@ public class RankFeaturesFieldMapper extends ParametrizedFieldMapper {
+ "] in the same document" + "] in the same document"
); );
} }
if (positiveScoreImpact == false) {
value = 1 / value;
}
context.doc().addWithKey(key, new FeatureField(name(), feature, value)); context.doc().addWithKey(key, new FeatureField(name(), feature, value));
} else { } else {
throw new IllegalArgumentException( throw new IllegalArgumentException(

View File

@ -67,8 +67,8 @@ public class RankFeaturesFieldMapperTests extends MapperTestCase {
} }
@Override @Override
protected void registerParameters(ParameterChecker checker) { protected void registerParameters(ParameterChecker checker) throws IOException {
// no parameters to configure checker.registerConflictCheck("positive_score_impact", b -> b.field("positive_score_impact", false));
} }
@Override @Override
@ -95,6 +95,33 @@ public class RankFeaturesFieldMapperTests extends MapperTestCase {
assertTrue(freq1 < freq2); assertTrue(freq1 < freq2);
} }
public void testNegativeScoreImpact() throws Exception {
DocumentMapper mapper = createDocumentMapper(
fieldMapping(b -> b.field("type", "rank_features").field("positive_score_impact", false))
);
ParsedDocument doc1 = mapper.parse(source(this::writeField));
IndexableField[] fields = doc1.rootDoc().getFields("field");
assertEquals(2, fields.length);
assertThat(fields[0], Matchers.instanceOf(FeatureField.class));
FeatureField featureField1 = null;
FeatureField featureField2 = null;
for (IndexableField field : fields) {
if (field.stringValue().equals("foo")) {
featureField1 = (FeatureField) field;
} else if (field.stringValue().equals("bar")) {
featureField2 = (FeatureField) field;
} else {
throw new UnsupportedOperationException();
}
}
int freq1 = RankFeatureFieldMapperTests.getFrequency(featureField1.tokenStream(null, null));
int freq2 = RankFeatureFieldMapperTests.getFrequency(featureField2.tokenStream(null, null));
assertTrue(freq1 > freq2);
}
public void testRejectMultiValuedFields() throws MapperParsingException, IOException { public void testRejectMultiValuedFields() throws MapperParsingException, IOException {
DocumentMapper mapper = createDocumentMapper(mapping(b -> { DocumentMapper mapper = createDocumentMapper(mapping(b -> {
b.startObject("field").field("type", "rank_features").endObject(); b.startObject("field").field("type", "rank_features").endObject();

View File

@ -37,7 +37,7 @@ import java.util.Collections;
public class RankFeaturesFieldTypeTests extends FieldTypeTestCase { public class RankFeaturesFieldTypeTests extends FieldTypeTestCase {
public void testIsNotAggregatable() { public void testIsNotAggregatable() {
MappedFieldType fieldType = new RankFeaturesFieldMapper.RankFeaturesFieldType("field", Collections.emptyMap()); MappedFieldType fieldType = new RankFeaturesFieldMapper.RankFeaturesFieldType("field", Collections.emptyMap(), true);
assertFalse(fieldType.isAggregatable()); assertFalse(fieldType.isAggregatable());
} }
} }

View File

@ -157,3 +157,24 @@ setup:
- match: - match:
hits.hits.1._id: "1" hits.hits.1._id: "1"
---
"Negative linear":
- do:
search:
index: test
body:
query:
rank_feature:
field: url_length
linear: {}
- match:
hits.total.value: 2
- match:
hits.hits.0._id: "2"
- match:
hits.hits.1._id: "1"

View File

@ -9,6 +9,9 @@ setup:
properties: properties:
tags: tags:
type: rank_features type: rank_features
negative_reviews:
type: rank_features
positive_score_impact: false
- do: - do:
index: index:
@ -18,6 +21,9 @@ setup:
tags: tags:
foo: 3 foo: 3
bar: 5 bar: 5
negative_reviews:
1star: 10
2star: 1
- do: - do:
index: index:
@ -27,6 +33,9 @@ setup:
tags: tags:
bar: 6 bar: 6
quux: 10 quux: 10
negative_reviews:
1star: 1
2star: 10
- do: - do:
indices.refresh: {} indices.refresh: {}
@ -97,3 +106,34 @@ setup:
- match: - match:
hits.hits.1._id: "1" hits.hits.1._id: "1"
---
"Linear negative impact":
- do:
search:
index: test
body:
query:
rank_feature:
field: negative_reviews.1star
linear: {}
- match:
hits.hits.0._id: "2"
- match:
hits.hits.1._id: "1"
- do:
search:
index: test
body:
query:
rank_feature:
field: negative_reviews.2star
linear: {}
- match:
hits.hits.0._id: "1"
- match:
hits.hits.1._id: "2"