Home | History | Annotate | Download | only in ranking
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.settings.intelligence.suggestions.ranking;
     18 
     19 import android.content.Context;
     20 import android.service.settings.suggestions.Suggestion;
     21 import android.support.annotation.VisibleForTesting;
     22 
     23 import com.android.settings.intelligence.overlay.FeatureFactory;
     24 
     25 import java.util.ArrayList;
     26 import java.util.Collections;
     27 import java.util.Comparator;
     28 import java.util.HashMap;
     29 import java.util.List;
     30 import java.util.Map;
     31 
     32 /**
     33  * Copied from packages/apps/Settings/src/.../dashboard/suggestions/SuggestionRanker
     34  */
     35 public class SuggestionRanker {
     36 
     37     private static final String TAG = "SuggestionRanker";
     38 
     39     // The following coefficients form a linear model, which mixes the features to obtain a
     40     // relevance metric for ranking the suggestion items. This model is learned with off-line data
     41     // by training a binary classifier to detect the clicked items. The higher the obtained
     42     // relevance metric, the higher chance of getting clicked.
     43     private static final Map<String, Double> WEIGHTS = new HashMap<String, Double>() {{
     44         put(SuggestionFeaturizer.FEATURE_IS_SHOWN, 5.05140842519);
     45         put(SuggestionFeaturizer.FEATURE_IS_DISMISSED, 2.29641455171);
     46         put(SuggestionFeaturizer.FEATURE_IS_CLICKED, -2.98812233623);
     47         put(SuggestionFeaturizer.FEATURE_TIME_FROM_LAST_SHOWN, 5.02807250202);
     48         put(SuggestionFeaturizer.FEATURE_TIME_FROM_LAST_DISMISSED, 2.49589700842);
     49         put(SuggestionFeaturizer.FEATURE_TIME_FROM_LAST_CLICKED, -4.3377039948);
     50         put(SuggestionFeaturizer.FEATURE_SHOWN_COUNT, -2.35993512546);
     51     }};
     52 
     53     private final long mMaxSuggestionsDisplayCount;
     54     private final SuggestionFeaturizer mSuggestionFeaturizer;
     55     private final Map<Suggestion, Double> mRelevanceMetrics;
     56 
     57     Comparator<Suggestion> suggestionComparator = new Comparator<Suggestion>() {
     58         @Override
     59         public int compare(Suggestion suggestion1, Suggestion suggestion2) {
     60             return mRelevanceMetrics.get(suggestion1) < mRelevanceMetrics.get(suggestion2) ? 1 : -1;
     61         }
     62     };
     63 
     64     public SuggestionRanker(Context context, SuggestionFeaturizer suggestionFeaturizer) {
     65         mSuggestionFeaturizer = suggestionFeaturizer;
     66         mRelevanceMetrics = new HashMap<>();
     67         mMaxSuggestionsDisplayCount = FeatureFactory.get(context).experimentFeatureProvider()
     68                 .getMaxSuggestionDisplayCount(context);
     69     }
     70 
     71     /**
     72      * Filter out suggestions that are not relevant at the moment, and rank the rest.
     73      *
     74      * @return a list of suggestion ranked by relevance.
     75      */
     76     public List<Suggestion> rankRelevantSuggestions(List<Suggestion> suggestions) {
     77         mRelevanceMetrics.clear();
     78         Map<String, Map<String, Double>> features = mSuggestionFeaturizer.featurize(suggestions);
     79         for (Suggestion suggestion : suggestions) {
     80             mRelevanceMetrics.put(suggestion, getRelevanceMetric(features.get(suggestion.getId())));
     81         }
     82         final List<Suggestion> rankedSuggestions = new ArrayList<>();
     83         rankedSuggestions.addAll(suggestions);
     84         Collections.sort(rankedSuggestions, suggestionComparator);
     85 
     86         if (rankedSuggestions.size() < mMaxSuggestionsDisplayCount) {
     87             return rankedSuggestions;
     88         } else {
     89             final List<Suggestion> relevantSuggestions = new ArrayList<>();
     90             for (int i = 0; i < mMaxSuggestionsDisplayCount; i++) {
     91                 relevantSuggestions.add(rankedSuggestions.get(i));
     92             }
     93             return relevantSuggestions;
     94         }
     95     }
     96 
     97     @VisibleForTesting
     98     double getRelevanceMetric(Map<String, Double> features) {
     99         double sum = 0;
    100         if (features == null) {
    101             return sum;
    102         }
    103         for (String feature : WEIGHTS.keySet()) {
    104             sum += WEIGHTS.get(feature) * features.get(feature);
    105         }
    106         return sum;
    107     }
    108 }