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:
parent
8bfb082ee1
commit
d8c815c6be
|
@ -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(
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue