1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/resource_coordinator/tab_ranker/tab_score_predictor.h"
6 
7 #include <memory>
8 #include <string>
9 
10 #include "base/logging.h"
11 #include "base/memory/ref_counted_memory.h"
12 #include "base/memory/scoped_refptr.h"
13 #include "base/strings/strcat.h"
14 #include "chrome/browser/resource_coordinator/tab_manager_features.h"
15 #include "chrome/browser/resource_coordinator/tab_ranker/native_inference.h"
16 #include "chrome/browser/resource_coordinator/tab_ranker/pairwise_inference.h"
17 #include "chrome/browser/resource_coordinator/tab_ranker/tab_features.h"
18 #include "chrome/grit/browser_resources.h"
19 #include "components/assist_ranker/example_preprocessing.h"
20 #include "components/assist_ranker/proto/example_preprocessor.pb.h"
21 #include "components/assist_ranker/proto/ranker_example.pb.h"
22 #include "ui/base/resource/resource_bundle.h"
23 
24 namespace tab_ranker {
25 namespace {
26 
27 using resource_coordinator::GetDiscardCountPenaltyTabRanker;
28 using resource_coordinator::GetMRUScorerPenaltyTabRanker;
29 using resource_coordinator::GetScorerTypeForTabRanker;
30 
31 // Maps the |mru_index| to it's reverse rank in (0.0, 1.0).
32 // High score means more likely to be reactivated.
33 // We use inverse rank because we think that the first several |mru_index| is
34 // more significant than the larger ones.
MruToScore(const float mru_index)35 inline float MruToScore(const float mru_index) {
36   DCHECK_GE(mru_index, 0.0f);
37   return 1.0f / (1.0f + mru_index);
38 }
39 
40 // Maps the |discard_count| to a score in (0.0, 1.0), for which
41 // High score means more likely to be reactivated.
42 // We use std::exp because we think that the first several |discard_count| is
43 // not as significant as the larger ones.
DiscardCountToScore(const float discard_count)44 inline float DiscardCountToScore(const float discard_count) {
45   return std::exp(discard_count);
46 }
47 
48 // Loads the preprocessor config protobuf, which lists each feature, their
49 // types, bucket configurations, etc.
50 // Returns nullptr if the protobuf was not successfully populated.
51 std::unique_ptr<assist_ranker::ExamplePreprocessorConfig>
LoadExamplePreprocessorConfig(const int resource_id)52 LoadExamplePreprocessorConfig(const int resource_id) {
53   auto config = std::make_unique<assist_ranker::ExamplePreprocessorConfig>();
54 
55   scoped_refptr<base::RefCountedMemory> raw_config =
56       ui::ResourceBundle::GetSharedInstance().LoadDataResourceBytes(
57           resource_id);
58   if (!raw_config || !raw_config->front()) {
59     LOG(ERROR) << "Failed to load TabRanker example preprocessor config.";
60     return nullptr;
61   }
62 
63   if (!config->ParseFromArray(raw_config->front(), raw_config->size())) {
64     LOG(ERROR) << "Failed to parse TabRanker example preprocessor config.";
65     return nullptr;
66   }
67 
68   return config;
69 }
70 
71 }  // namespace
72 
TabScorePredictor()73 TabScorePredictor::TabScorePredictor()
74     : discard_count_penalty_(GetDiscardCountPenaltyTabRanker()),
75       mru_scorer_penalty_(GetMRUScorerPenaltyTabRanker()),
76       type_(static_cast<ScorerType>(GetScorerTypeForTabRanker())) {}
77 
78 TabScorePredictor::~TabScorePredictor() = default;
79 
ScoreTab(const TabFeatures & tab,float * score)80 TabRankerResult TabScorePredictor::ScoreTab(const TabFeatures& tab,
81                                             float* score) {
82   DCHECK(score);
83 
84   // No error is expected, but something could conceivably be misconfigured.
85   TabRankerResult result = TabRankerResult::kSuccess;
86 
87   if (type_ == kMRUScorer) {
88     result = ScoreTabWithMRUScorer(tab, score);
89   } else if (type_ == kMLScorer) {
90     result = ScoreTabWithMLScorer(tab, score);
91   } else if (type_ == kPairwiseScorer) {
92     result = ScoreTabsPairs(tab, TabFeatures(), score);
93   } else if (type_ == kFrecencyScorer) {
94     result = ScoreTabWithFrecencyScorer(tab, score);
95   } else {
96     return TabRankerResult::kUnrecognizableScorer;
97   }
98 
99   // Applies DiscardCount adjustment.
100   // The default value of discard_count_penalty_ is 0.0f, which will not change
101   // the score.
102   // The larger the |discard_count_penalty_| is (set from Finch), the quicker
103   // the score increases based on the discard_count.
104   *score *= DiscardCountToScore(tab.discard_count * discard_count_penalty_);
105 
106   return result;
107 }
108 
ScoreTabs(const std::map<int32_t,base::Optional<TabFeatures>> & tabs)109 std::map<int32_t, float> TabScorePredictor::ScoreTabs(
110     const std::map<int32_t, base::Optional<TabFeatures>>& tabs) {
111   if (type_ != kPairwiseScorer) {
112     std::map<int32_t, float> reactivation_scores;
113     for (const auto& pair : tabs) {
114       float score = 0.0f;
115       if (pair.second && (ScoreTab(pair.second.value(), &score) ==
116                           TabRankerResult::kSuccess)) {
117         reactivation_scores[pair.first] = score;
118       } else {
119         reactivation_scores[pair.first] = std::numeric_limits<float>::max();
120       }
121     }
122     return reactivation_scores;
123   } else {
124     return ScoreTabsWithPairwiseScorer(tabs);
125   }
126 }
127 
ScoreTabWithMLScorer(const TabFeatures & tab,float * score)128 TabRankerResult TabScorePredictor::ScoreTabWithMLScorer(const TabFeatures& tab,
129                                                         float* score) {
130   // Lazy-load the preprocessor config.
131   LazyInitialize();
132   if (!preprocessor_config_ || !tfnative_alloc_) {
133     return TabRankerResult::kPreprocessorInitializationFailed;
134   }
135   // Build the RankerExample using the tab's features.
136   assist_ranker::RankerExample example;
137   PopulateTabFeaturesToRankerExample(tab, &example);
138   return PredictWithPreprocess(&example, score);
139 }
140 
PredictWithPreprocess(assist_ranker::RankerExample * example,float * score)141 TabRankerResult TabScorePredictor::PredictWithPreprocess(
142     assist_ranker::RankerExample* example,
143     float* score) {
144   // Process the RankerExample with the tab ranker config to vectorize the
145   // feature list for inference.
146   int preprocessor_error = assist_ranker::ExamplePreprocessor::Process(
147       *preprocessor_config_, example, true);
148   if (preprocessor_error) {
149     // kNoFeatureIndexFound can occur normally (e.g., when the domain name
150     // isn't known to the model or a rarely seen enum value is used).
151     DCHECK_EQ(assist_ranker::ExamplePreprocessor::kNoFeatureIndexFound,
152               preprocessor_error);
153   }
154 
155   // This vector will be provided to the inference function.
156   const auto& vectorized_features =
157       example->features()
158           .at(assist_ranker::ExamplePreprocessor::kVectorizedFeatureDefaultName)
159           .float_list()
160           .float_value();
161 
162   // Call correct inference function based on the type_.
163   if (type_ == kMLScorer)
164     tfnative_model::Inference(vectorized_features.data(), score,
165                               tfnative_alloc_.get());
166   if (type_ == kPairwiseScorer)
167     pairwise_model::Inference(vectorized_features.data(), score,
168                               pairwise_alloc_.get());
169 
170   if (preprocessor_error != assist_ranker::ExamplePreprocessor::kSuccess &&
171       preprocessor_error !=
172           assist_ranker::ExamplePreprocessor::kNoFeatureIndexFound) {
173     // May indicate something is wrong with how we create the RankerExample.
174     return TabRankerResult::kPreprocessorOtherError;
175   } else {
176     return TabRankerResult::kSuccess;
177   }
178 }
179 
ScoreTabWithMRUScorer(const TabFeatures & tab,float * score)180 TabRankerResult TabScorePredictor::ScoreTabWithMRUScorer(const TabFeatures& tab,
181                                                          float* score) {
182   *score = MruToScore(tab.mru_index * mru_scorer_penalty_);
183   return TabRankerResult::kSuccess;
184 }
185 
ScoreTabsPairs(const TabFeatures & tab1,const TabFeatures & tab2,float * score)186 TabRankerResult TabScorePredictor::ScoreTabsPairs(const TabFeatures& tab1,
187                                                   const TabFeatures& tab2,
188                                                   float* score) {
189   if (type_ == kPairwiseScorer) {
190     // Lazy-load the preprocessor config.
191     LazyInitialize();
192     if (!preprocessor_config_ || !pairwise_alloc_) {
193       return TabRankerResult::kPreprocessorInitializationFailed;
194     }
195 
196     // Build the RankerExamples using the tab's features.
197     assist_ranker::RankerExample example1, example2;
198     PopulateTabFeaturesToRankerExample(tab1, &example1);
199     PopulateTabFeaturesToRankerExample(tab2, &example2);
200 
201     // Merge features from example2 to example1.
202     auto& features = *example1.mutable_features();
203     for (const auto& feature : example2.features()) {
204       const std::string new_name = base::StrCat({feature.first, "_1"});
205       features[new_name] = feature.second;
206     }
207 
208     // Inference on example1.
209     return PredictWithPreprocess(&example1, score);
210   } else {
211     // For non-pairwise scorer, we simply calculate the score of each tab and
212     // return the difference.
213     float score1, score2;
214     const TabRankerResult result1 = ScoreTab(tab1, &score1);
215     const TabRankerResult result2 = ScoreTab(tab2, &score2);
216     *score = score1 - score2;
217     return std::max(result1, result2);
218   }
219 }
220 
ScoreTabsWithPairwiseScorer(const std::map<int32_t,base::Optional<TabFeatures>> & tabs)221 std::map<int32_t, float> TabScorePredictor::ScoreTabsWithPairwiseScorer(
222     const std::map<int32_t, base::Optional<TabFeatures>>& tabs) {
223   const int N = tabs.size();
224 
225   std::vector<int32_t> ids;
226   for (const auto& pair : tabs) {
227     ids.push_back(pair.first);
228   }
229 
230   // Sort ids by MRU first.
231   // Put the tabs without TabFeatures in front so that they won't be discarded
232   // mistakenly (including current Foregrounded tab).
233   std::sort(ids.begin(), ids.end(),
234             [&tabs](const int32_t id1, const int32_t id2) {
235               const auto& tab1 = tabs.at(id1);
236               const auto& tab2 = tabs.at(id2);
237               if (!tab2)
238                 return false;
239               if (!tab1)
240                 return true;
241               return tab1->mru_index < tab2->mru_index;
242             });
243 
244   std::map<int32_t, float> reactivation_scores;
245 
246   // start_index is the first one that has tab_features.
247   int start_index = 0;
248   for (int i = 0; i < N; ++i) {
249     if (!tabs.at(ids[i])) {
250       reactivation_scores[ids[i]] = N - i;
251       start_index = i + 1;
252     } else {
253       break;
254     }
255   }
256 
257   // winning_indices records what's the best tab to be put at pos i.
258   std::vector<int> winning_indices;
259   for (int i = 0; i < N; ++i)
260     winning_indices.push_back(i);
261 
262   int winning_index = N - 1;
263   int swapped_index = N - 1;
264   for (int j = start_index; j < N; ++j) {
265     // Find the best candidate at j.
266 
267     // swapped_index < N - 1 means that one element has
268     // just been swapped to swapped_index, we should re-calculate
269     // winning_indices from swapped_index to j;
270     if (swapped_index < N - 1) {
271       // Set winning_index as the winning_indices at swapped_index + 1, since
272       // ids from ids.back() to ids[swapped_index + 1] are not
273       // changed.
274       winning_index = winning_indices[swapped_index + 1];
275     }
276 
277     for (int i = swapped_index; i >= j; --i) {
278       // Compare ids[i] with ids[winning_index]; inference score > 0 means
279       // that ids[i] is more likely to be reactivated, so we should prefer
280       // ids[i] as new winning_index.
281       float score = 0.0f;
282       const TabRankerResult result = ScoreTabsPairs(
283           tabs.at(ids[i]).value(), tabs.at(ids[winning_index]).value(), &score);
284       if (result == TabRankerResult::kSuccess && score > 0.0f) {
285         winning_index = i;
286       }
287 
288       // Always update winning_indices.
289       winning_indices[i] = winning_index;
290     }
291 
292     // swap winning_index with j;
293     std::swap(ids[winning_index], ids[j]);
294     swapped_index = winning_index;
295 
296     // Find the best candidate for position j, set the score for ids[j].
297     reactivation_scores[ids[j]] = N - j;
298   }
299   return reactivation_scores;
300 }
LazyInitialize()301 void TabScorePredictor::LazyInitialize() {
302   // Load correct config and alloc based on type_.
303   if (type_ == kMLScorer) {
304     if (!preprocessor_config_)
305       preprocessor_config_ = LoadExamplePreprocessorConfig(
306           IDR_TAB_RANKER_EXAMPLE_PREPROCESSOR_CONFIG_PB);
307     if (!tfnative_alloc_)
308       tfnative_alloc_ = std::make_unique<tfnative_model::FixedAllocations>();
309     DCHECK(preprocessor_config_);
310     DCHECK_EQ(preprocessor_config_->feature_indices().size(),
311               static_cast<std::size_t>(tfnative_model::FEATURES_SIZE));
312   }
313 
314   if (type_ == kPairwiseScorer) {
315     if (!preprocessor_config_)
316       preprocessor_config_ = LoadExamplePreprocessorConfig(
317           IDR_TAB_RANKER_PAIRWISE_EXAMPLE_PREPROCESSOR_CONFIG_PB);
318     if (!pairwise_alloc_)
319       pairwise_alloc_ = std::make_unique<pairwise_model::FixedAllocations>();
320     DCHECK(preprocessor_config_);
321     DCHECK_EQ(preprocessor_config_->feature_indices().size(),
322               static_cast<std::size_t>(pairwise_model::FEATURES_SIZE));
323   }
324 }
325 
326 // Simply returns the frecency_score in the TabFeatures.
ScoreTabWithFrecencyScorer(const TabFeatures & tab,float * score)327 TabRankerResult TabScorePredictor::ScoreTabWithFrecencyScorer(
328     const TabFeatures& tab,
329     float* score) {
330   *score = tab.frecency_score;
331   return TabRankerResult::kSuccess;
332 }
333 
334 }  // namespace tab_ranker
335