You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by cp...@apache.org on 2018/02/13 19:25:07 UTC

lucene-solr:branch_7x: SOLR-11941: Add abstract contrib/ltr AdapterModel.

Repository: lucene-solr
Updated Branches:
  refs/heads/branch_7x 3c24eb108 -> 11e5aa071


SOLR-11941: Add abstract contrib/ltr AdapterModel.


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/11e5aa07
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/11e5aa07
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/11e5aa07

Branch: refs/heads/branch_7x
Commit: 11e5aa071e4d27fab0ffe2210d4868806103e863
Parents: 3c24eb1
Author: Christine Poerschke <cp...@apache.org>
Authored: Tue Feb 13 18:49:25 2018 +0000
Committer: Christine Poerschke <cp...@apache.org>
Committed: Tue Feb 13 19:08:08 2018 +0000

----------------------------------------------------------------------
 solr/CHANGES.txt                                |   3 +
 .../org/apache/solr/ltr/model/AdapterModel.java |  43 ++++++
 .../org/apache/solr/ltr/model/WrapperModel.java |   8 +-
 .../solr/ltr/store/rest/ManagedModelStore.java  |  21 +--
 .../apache/solr/ltr/model/TestAdapterModel.java | 143 +++++++++++++++++++
 solr/solr-ref-guide/src/learning-to-rank.adoc   |   1 +
 6 files changed, 204 insertions(+), 15 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/11e5aa07/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 466bb25..e7eee93 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -141,6 +141,9 @@ New Features
 * SOLR-11925: Time Routed Aliases can have their oldest collections automatically deleted via the "router.autoDeleteAge"
   setting. (David Smiley)
 
+* SOLR-11941: Add abstract contrib/ltr AdapterModel to facilitate the development of scoring models that delegate
+  scoring to an opaque pre-trained model. (Christine Poerschke)
+
 Bug Fixes
 ----------------------
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/11e5aa07/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/AdapterModel.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/AdapterModel.java b/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/AdapterModel.java
new file mode 100644
index 0000000..e9ca3ea
--- /dev/null
+++ b/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/AdapterModel.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.ltr.model;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.solr.core.SolrResourceLoader;
+import org.apache.solr.ltr.feature.Feature;
+import org.apache.solr.ltr.norm.Normalizer;
+
+/**
+ * A scoring model whose initialization is completed via its
+ * {@link #init(SolrResourceLoader)} method.
+ */
+public abstract class AdapterModel extends LTRScoringModel {
+
+  protected SolrResourceLoader solrResourceLoader;
+
+  public AdapterModel(String name, List<Feature> features, List<Normalizer> norms, String featureStoreName,
+      List<Feature> allFeatures, Map<String,Object> params) {
+    super(name, features, norms, featureStoreName, allFeatures, params);
+  }
+
+  public void init(SolrResourceLoader solrResourceLoader) throws ModelException {
+    this.solrResourceLoader = solrResourceLoader;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/11e5aa07/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/WrapperModel.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/WrapperModel.java b/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/WrapperModel.java
index cf66135..aeaf578 100644
--- a/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/WrapperModel.java
+++ b/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/WrapperModel.java
@@ -23,7 +23,6 @@ import java.util.Map;
 
 import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.search.Explanation;
-import org.apache.solr.core.SolrResourceLoader;
 import org.apache.solr.ltr.feature.Feature;
 import org.apache.solr.ltr.norm.Normalizer;
 
@@ -51,9 +50,8 @@ import org.apache.solr.ltr.norm.Normalizer;
  * Also note that if a "store" is configured for the wrapper
  * model then it must match the "store" of the wrapped model.
  */
-public abstract class WrapperModel extends LTRScoringModel {
+public abstract class WrapperModel extends AdapterModel {
 
-  protected SolrResourceLoader solrResourceLoader;
   protected LTRScoringModel model;
 
   @Override
@@ -107,10 +105,6 @@ public abstract class WrapperModel extends LTRScoringModel {
     }
   }
 
-  public void setSolrResourceLoader(SolrResourceLoader solrResourceLoader) {
-    this.solrResourceLoader = solrResourceLoader;
-  }
-
   public void updateModel(LTRScoringModel model) {
     this.model = model;
     validate();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/11e5aa07/solr/contrib/ltr/src/java/org/apache/solr/ltr/store/rest/ManagedModelStore.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/java/org/apache/solr/ltr/store/rest/ManagedModelStore.java b/solr/contrib/ltr/src/java/org/apache/solr/ltr/store/rest/ManagedModelStore.java
index bdc4cfd..60cabcc 100644
--- a/solr/contrib/ltr/src/java/org/apache/solr/ltr/store/rest/ManagedModelStore.java
+++ b/solr/contrib/ltr/src/java/org/apache/solr/ltr/store/rest/ManagedModelStore.java
@@ -27,6 +27,7 @@ import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrResourceLoader;
 import org.apache.solr.ltr.feature.Feature;
+import org.apache.solr.ltr.model.AdapterModel;
 import org.apache.solr.ltr.model.WrapperModel;
 import org.apache.solr.ltr.model.LTRScoringModel;
 import org.apache.solr.ltr.model.ModelException;
@@ -241,25 +242,29 @@ public class ManagedModelStore extends ManagedResource implements ManagedResourc
         featureStore.getFeatures(),
         (Map<String,Object>) modelMap.get(PARAMS_KEY));
 
-    if (ltrScoringModel instanceof WrapperModel) {
-      initWrapperModel(solrResourceLoader, (WrapperModel)ltrScoringModel, managedFeatureStore);
+    if (ltrScoringModel instanceof AdapterModel) {
+      initAdapterModel(solrResourceLoader, (AdapterModel)ltrScoringModel, managedFeatureStore);
     }
 
     return ltrScoringModel;
   }
 
+  private static void initAdapterModel(SolrResourceLoader solrResourceLoader,
+      AdapterModel adapterModel, ManagedFeatureStore managedFeatureStore) {
+    adapterModel.init(solrResourceLoader);
+    if (adapterModel instanceof WrapperModel) {
+      initWrapperModel(solrResourceLoader, (WrapperModel)adapterModel, managedFeatureStore);
+    }
+  }
+
   private static void initWrapperModel(SolrResourceLoader solrResourceLoader,
                                        WrapperModel wrapperModel, ManagedFeatureStore managedFeatureStore) {
-    wrapperModel.setSolrResourceLoader(solrResourceLoader);
     final LTRScoringModel model = fromLTRScoringModelMap(
         solrResourceLoader,
         wrapperModel.fetchModelMap(),
         managedFeatureStore);
-    if (model instanceof WrapperModel) {
-      log.warn("It is unusual for one WrapperModel ({}) to wrap another WrapperModel ({})",
-          wrapperModel.getName(),
-          model.getName());
-      initWrapperModel(solrResourceLoader, (WrapperModel)model, managedFeatureStore);
+    if (model instanceof AdapterModel) {
+      initAdapterModel(solrResourceLoader, (AdapterModel)model, managedFeatureStore);
     }
     wrapperModel.updateModel(model);
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/11e5aa07/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestAdapterModel.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestAdapterModel.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestAdapterModel.java
new file mode 100644
index 0000000..d60d46e
--- /dev/null
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestAdapterModel.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.ltr.model;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.search.Explanation;
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.core.SolrResourceLoader;
+import org.apache.solr.ltr.TestRerankBase;
+import org.apache.solr.ltr.feature.Feature;
+import org.apache.solr.ltr.feature.FieldValueFeature;
+import org.apache.solr.ltr.norm.Normalizer;
+import org.apache.solr.ltr.store.rest.ManagedModelStore;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestAdapterModel extends TestRerankBase {
+
+  private static int numDocs = 0;
+  private static float scoreValue;
+
+  @BeforeClass
+  public static void setupBeforeClass() throws Exception {
+
+    setuptest(false);
+
+    for (int ii=1; ii<=random().nextInt(10); ++ii) {
+      String id = Integer.toString(ii);
+      assertU(adoc("id", id, "popularity", ii+"00"));
+      ++numDocs;
+    }
+    assertU(commit());
+
+    loadFeature("popularity", FieldValueFeature.class.getName(), "test", "{\"field\":\"popularity\"}");
+
+    scoreValue = random().nextFloat();
+    final File scoreValueFile = new File(tmpConfDir, "scoreValue.txt");
+    try (BufferedWriter writer = new BufferedWriter(
+        new OutputStreamWriter(new FileOutputStream(scoreValueFile), StandardCharsets.UTF_8))) {
+      writer.write(Float.toString(scoreValue));
+    }
+    scoreValueFile.deleteOnExit();
+
+    final String modelJson = getModelInJson(
+        "answerModel",
+        CustomModel.class.getName(),
+        new String[] { "popularity" },
+        "test",
+        "{\"answerFileName\":\"" + scoreValueFile.getName() + "\"}");
+    assertJPut(ManagedModelStore.REST_END_POINT, modelJson, "/responseHeader/status==0");
+  }
+
+  @Test
+  public void test() throws Exception {
+    final int rows = random().nextInt(numDocs+1); // 0..numDocs
+    final SolrQuery query = new SolrQuery("*:*");
+    query.setRows(rows);
+    query.setFields("*,score");
+    query.add("rq", "{!ltr model=answerModel}");
+    final String[] tests = new String[rows];
+    for (int ii=0; ii<rows; ++ii) {
+      tests[ii] = "/response/docs/["+ii+"]/score=="+scoreValue;
+    }
+    assertJQ("/query" + query.toQueryString(), tests);
+  }
+
+  public static class CustomModel extends AdapterModel {
+
+    private String answerFileName;
+    private float answerValue;
+
+    public CustomModel(String name, List<Feature> features, List<Normalizer> norms, String featureStoreName,
+        List<Feature> allFeatures, Map<String,Object> params) {
+      super(name, features, norms, featureStoreName, allFeatures, params);
+    }
+
+    public void setAnswerFileName(String answerFileName) {
+      this.answerFileName = answerFileName;
+    }
+
+    @Override
+    protected void validate() throws ModelException {
+      super.validate();
+      if (answerFileName == null) {
+        throw new ModelException("no answerFileName configured for model "+name);
+      }
+    }
+
+    public void init(SolrResourceLoader solrResourceLoader) throws ModelException {
+      super.init(solrResourceLoader);
+      try (
+          InputStream is = solrResourceLoader.openResource(answerFileName);
+          InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
+          BufferedReader br = new BufferedReader(isr)
+          ) {
+        answerValue = Float.parseFloat(br.readLine());
+      } catch (IOException e) {
+        throw new ModelException("Failed to get the answerValue from the given answerFileName (" + answerFileName + ")", e);
+      }
+    }
+
+    @Override
+    public float score(float[] modelFeatureValuesNormalized) {
+      return answerValue;
+    }
+
+    @Override
+    public Explanation explain(LeafReaderContext context, int doc, float finalScore,
+        List<Explanation> featureExplanations) {
+      return Explanation.match(finalScore, toString()
+          + " model, always returns "+Float.toString(answerValue)+".");
+    }
+
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/11e5aa07/solr/solr-ref-guide/src/learning-to-rank.adoc
----------------------------------------------------------------------
diff --git a/solr/solr-ref-guide/src/learning-to-rank.adoc b/solr/solr-ref-guide/src/learning-to-rank.adoc
index c165a36..b44e85a 100644
--- a/solr/solr-ref-guide/src/learning-to-rank.adoc
+++ b/solr/solr-ref-guide/src/learning-to-rank.adoc
@@ -88,6 +88,7 @@ Feature selection and model training take place offline and outside Solr. The lt
 |Linear |{solr-javadocs}/solr-ltr/org/apache/solr/ltr/model/LinearModel.html[LinearModel] |RankSVM, Pranking
 |Multiple Additive Trees |{solr-javadocs}/solr-ltr/org/apache/solr/ltr/model/MultipleAdditiveTreesModel.html[MultipleAdditiveTreesModel] |LambdaMART, Gradient Boosted Regression Trees (GBRT)
 |(wrapper) |{solr-javadocs}/solr-ltr/org/apache/solr/ltr/model/DefaultWrapperModel.html[DefaultWrapperModel] |(not applicable)
+|(custom) |(custom class extending {solr-javadocs}/solr-ltr/org/apache/solr/ltr/model/AdapterModel.html[AdapterModel]) |(not applicable)
 |(custom) |(custom class extending {solr-javadocs}/solr-ltr/org/apache/solr/ltr/model/LTRScoringModel.html[LTRScoringModel]) |(not applicable)
 |===