зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1369604: Replace features HashMap with indexing into an array. r=liuche
After the previous changeset, some numbers stood out: - HighlightsRanking.extractFeatures: 44.9% - HighlightCandidate.getFeatureValue: 19.4% - Collections.secondaryHash: 17.3% - HashMap.get: 11.7% My hypothesis was that our HighlightCandidate.features implementation was slow: it was mapping FeatureNames -> values in a HashMap but HashMap look-ups are slower than a direct memory access. I replaced the implementation with a direct access from an array - about as fast as we can get. This encouraged me to make some changes with the following benefits: - Rewrote HighlightsRanking.normalize to save iterations and allocations. - Rm code from HighlightsRanking.scoreEntries: we no longer need to iterate to construct the filtered items, we just index directly into the list - Rewrote HighlightsRanking.decay(), which I think is a little clearer now. - Saved a few iterator/object allocations inside inner loops in places. The tests pass and we have coverage for the normalize changes but not for scoreEntries. --- For perf, my changes affected multiple methods so the percentages are no longer reliable but I can verify absolute runtime changes. I ran three tests, the best of which showed an overall 33% runtime compared to the previous changeset and the other two profiles showed a 66% overall runtime. In particular, for the middle run, the changes for affected methods go from X microseconds to Y microseconds: - Features.get: 3,554,796 -> 322,145 - secondaryHash: 3,165,785 -> 35,253 - HighlightsRanking.normalize: 6,578,481 -> 1,734,078 - HighlightsRanking.scoreEntries: 3,017,272 -> 448,300 As far as I know, my changes should not have introduced any new inefficiencies to the code. MozReview-Commit-ID: 9THXe8KqBbB --HG-- extra : rebase_source : 2358fe83acebaf04a61d912e88f8cf420b7df3d7
This commit is contained in:
Родитель
0d088f904d
Коммит
b6c9a1f711
|
@ -7,37 +7,67 @@ package org.mozilla.gecko.activitystream.ranking;
|
|||
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringDef;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
|
||||
import org.mozilla.gecko.activitystream.ranking.RankingUtils.Func1;
|
||||
import org.mozilla.gecko.activitystream.homepanel.model.Highlight;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* A highlight candidate (Highlight object + features). Ranking will determine whether this is an
|
||||
* actual highlight.
|
||||
*/
|
||||
/* package-private */ class HighlightCandidate {
|
||||
/* package-private */ static final String FEATURE_AGE_IN_DAYS = "ageInDays";
|
||||
/* package-private */ static final String FEATURE_IMAGE_COUNT = "imageCount";
|
||||
/* package-private */ static final String FEATURE_DOMAIN_FREQUENCY = "domainFrequency";
|
||||
/* package-private */ static final String FEATURE_VISITS_COUNT = "visitsCount";
|
||||
/* package-private */ static final String FEATURE_BOOKMARK_AGE_IN_MILLISECONDS = "bookmarkageInDays";
|
||||
/* package-private */ static final String FEATURE_DESCRIPTION_LENGTH = "descriptionLength";
|
||||
/* package-private */ static final String FEATURE_PATH_LENGTH = "pathLength";
|
||||
/* package-private */ static final String FEATURE_QUERY_LENGTH = "queryLength";
|
||||
/* package-private */ static final String FEATURE_IMAGE_SIZE = "imageSize";
|
||||
|
||||
@StringDef({FEATURE_AGE_IN_DAYS, FEATURE_IMAGE_COUNT, FEATURE_DOMAIN_FREQUENCY, FEATURE_VISITS_COUNT,
|
||||
FEATURE_BOOKMARK_AGE_IN_MILLISECONDS, FEATURE_DESCRIPTION_LENGTH, FEATURE_PATH_LENGTH,
|
||||
FEATURE_QUERY_LENGTH, FEATURE_IMAGE_SIZE})
|
||||
public @interface Feature {}
|
||||
// Features we score over for Highlight results - see Features class for more details & usage.
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({FEATURE_AGE_IN_DAYS, FEATURE_BOOKMARK_AGE_IN_MILLISECONDS, FEATURE_DESCRIPTION_LENGTH,
|
||||
FEATURE_DOMAIN_FREQUENCY, FEATURE_IMAGE_COUNT, FEATURE_IMAGE_SIZE, FEATURE_PATH_LENGTH,
|
||||
FEATURE_QUERY_LENGTH, FEATURE_VISITS_COUNT})
|
||||
/* package-private */ @interface FeatureName {}
|
||||
|
||||
@VisibleForTesting final Map<String, Double> features;
|
||||
// IF YOU ADD A FIELD, INCREMENT `FEATURE_COUNT`! For a perf boost, we use these ints to index into an array and
|
||||
// FEATURE_COUNT tracks the number of features we have and thus how big the array needs to be.
|
||||
private static final int FEATURE_COUNT = 9; // = the-greatest-feature-index + 1.
|
||||
/* package-private */ static final int FEATURE_AGE_IN_DAYS = 0;
|
||||
/* package-private */ static final int FEATURE_BOOKMARK_AGE_IN_MILLISECONDS = 1;
|
||||
/* package-private */ static final int FEATURE_DESCRIPTION_LENGTH = 2;
|
||||
/* package-private */ static final int FEATURE_DOMAIN_FREQUENCY = 3;
|
||||
/* package-private */ static final int FEATURE_IMAGE_COUNT = 4;
|
||||
/* package-private */ static final int FEATURE_IMAGE_SIZE = 5;
|
||||
/* package-private */ static final int FEATURE_PATH_LENGTH = 6;
|
||||
/* package-private */ static final int FEATURE_QUERY_LENGTH = 7;
|
||||
/* package-private */ static final int FEATURE_VISITS_COUNT = 8;
|
||||
|
||||
/**
|
||||
* A data class for accessing Features values. It acts as a map from FeatureName -> value:
|
||||
* <pre>
|
||||
* Features features = new Features();
|
||||
* features.put(FEATURE_AGE_IN_DAYS, 30);
|
||||
* double value = features.get(FEATURE_AGE_IN_DAYS);
|
||||
* </pre>
|
||||
*
|
||||
* This data is accessed frequently and needs to be performant. As such, the implementation is a little fragile
|
||||
* (e.g. we could increase type safety with enums and index into the backing array with Enum.ordinal(), but it
|
||||
* gets called enough that it's not worth the performance trade-off).
|
||||
*/
|
||||
/* package-private */ static class Features {
|
||||
private final double[] values = new double[FEATURE_COUNT];
|
||||
|
||||
Features() {}
|
||||
|
||||
/* package-private */ double get(final @FeatureName int featureName) {
|
||||
return values[featureName];
|
||||
}
|
||||
|
||||
/* package-private */ void put(final @FeatureName int featureName, final double value) {
|
||||
values[featureName] = value;
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting final Features features = new Features();
|
||||
private Highlight highlight;
|
||||
private @Nullable String imageUrl;
|
||||
private String host;
|
||||
|
@ -146,7 +176,6 @@ import java.util.Map;
|
|||
}
|
||||
|
||||
@VisibleForTesting HighlightCandidate() {
|
||||
features = new HashMap<>();
|
||||
}
|
||||
|
||||
/* package-private */ double getScore() {
|
||||
|
@ -174,30 +203,6 @@ import java.util.Map;
|
|||
return highlight;
|
||||
}
|
||||
|
||||
/* package-private */ double getFeatureValue(@Feature String feature) {
|
||||
if (!features.containsKey(feature)) {
|
||||
throw new IllegalStateException("No value for feature " + feature);
|
||||
}
|
||||
|
||||
return features.get(feature);
|
||||
}
|
||||
|
||||
/* package-private */ void setFeatureValue(@Feature String feature, double value) {
|
||||
features.put(feature, value);
|
||||
}
|
||||
|
||||
/* package-private */ Map<String, Double> getFilteredFeatures(Func1<String, Boolean> filter) {
|
||||
Map<String, Double> filteredFeatures = new HashMap<>();
|
||||
|
||||
for (Map.Entry<String, Double> entry : features.entrySet()) {
|
||||
if (filter.call(entry.getKey())) {
|
||||
filteredFeatures.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
return filteredFeatures;
|
||||
}
|
||||
|
||||
/* package-private */ static class InvalidHighlightCandidateException extends Exception {
|
||||
private static final long serialVersionUID = 949263104621445850L;
|
||||
}
|
||||
|
|
|
@ -7,30 +7,33 @@ package org.mozilla.gecko.activitystream.ranking;
|
|||
|
||||
import android.database.Cursor;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import org.mozilla.gecko.activitystream.homepanel.model.Highlight;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Collections.sort;
|
||||
import static org.mozilla.gecko.activitystream.ranking.HighlightCandidate.FEATURE_AGE_IN_DAYS;
|
||||
import static org.mozilla.gecko.activitystream.ranking.HighlightCandidate.FEATURE_BOOKMARK_AGE_IN_MILLISECONDS;
|
||||
import static org.mozilla.gecko.activitystream.ranking.HighlightCandidate.FEATURE_DESCRIPTION_LENGTH;
|
||||
import static org.mozilla.gecko.activitystream.ranking.HighlightCandidate.FEATURE_DOMAIN_FREQUENCY;
|
||||
import static org.mozilla.gecko.activitystream.ranking.HighlightCandidate.FEATURE_IMAGE_COUNT;
|
||||
import static org.mozilla.gecko.activitystream.ranking.HighlightCandidate.FEATURE_IMAGE_SIZE;
|
||||
import static org.mozilla.gecko.activitystream.ranking.HighlightCandidate.FEATURE_PATH_LENGTH;
|
||||
import static org.mozilla.gecko.activitystream.ranking.HighlightCandidate.FEATURE_QUERY_LENGTH;
|
||||
import static org.mozilla.gecko.activitystream.ranking.HighlightCandidate.FEATURE_VISITS_COUNT;
|
||||
import static org.mozilla.gecko.activitystream.ranking.RankingUtils.Action1;
|
||||
import static org.mozilla.gecko.activitystream.ranking.RankingUtils.Action2;
|
||||
import static org.mozilla.gecko.activitystream.ranking.RankingUtils.Func1;
|
||||
import static org.mozilla.gecko.activitystream.ranking.RankingUtils.Func2;
|
||||
import static org.mozilla.gecko.activitystream.ranking.RankingUtils.apply;
|
||||
import static org.mozilla.gecko.activitystream.ranking.RankingUtils.apply2D;
|
||||
import static org.mozilla.gecko.activitystream.ranking.RankingUtils.applyInPairs;
|
||||
import static org.mozilla.gecko.activitystream.ranking.RankingUtils.filter;
|
||||
import static org.mozilla.gecko.activitystream.ranking.RankingUtils.looselyMapCursor;
|
||||
import static org.mozilla.gecko.activitystream.ranking.RankingUtils.mapWithLimit;
|
||||
import static org.mozilla.gecko.activitystream.ranking.RankingUtils.reduce;
|
||||
|
||||
/**
|
||||
* HighlightsRanking.rank() takes a Cursor of highlight candidates and applies ranking to find a set
|
||||
|
@ -44,28 +47,45 @@ import static org.mozilla.gecko.activitystream.ranking.RankingUtils.reduce;
|
|||
public class HighlightsRanking {
|
||||
private static final String LOG_TAG = "HighlightsRanking";
|
||||
|
||||
private static final Map<String, Double> HIGHLIGHT_WEIGHTS = new HashMap<>();
|
||||
/** An array of all the features that are weighted while scoring. */
|
||||
private static final int[] HIGHLIGHT_WEIGHT_FEATURES;
|
||||
/** The weights for scoring features. */
|
||||
private static final HighlightCandidate.Features HIGHLIGHT_WEIGHTS = new HighlightCandidate.Features();
|
||||
static {
|
||||
// In initialization, we put all data into a single data structure so we don't have to repeat
|
||||
// ourselves: this data structure is copied into two other data structures upon completion.
|
||||
//
|
||||
// To add a weight, just add it to tmpWeights as seen below.
|
||||
// TODO: Needs confirmation from the desktop team that this is the correct weight mapping (Bug 1336037)
|
||||
HIGHLIGHT_WEIGHTS.put(HighlightCandidate.FEATURE_VISITS_COUNT, -0.1);
|
||||
HIGHLIGHT_WEIGHTS.put(HighlightCandidate.FEATURE_DESCRIPTION_LENGTH, -0.1);
|
||||
HIGHLIGHT_WEIGHTS.put(HighlightCandidate.FEATURE_PATH_LENGTH, -0.1);
|
||||
final SparseArray<Double> tmpWeights = new SparseArray<>();
|
||||
tmpWeights.put(FEATURE_VISITS_COUNT, -0.1);
|
||||
tmpWeights.put(FEATURE_DESCRIPTION_LENGTH, -0.1);
|
||||
tmpWeights.put(FEATURE_PATH_LENGTH, -0.1);
|
||||
|
||||
HIGHLIGHT_WEIGHTS.put(HighlightCandidate.FEATURE_QUERY_LENGTH, 0.4);
|
||||
HIGHLIGHT_WEIGHTS.put(HighlightCandidate.FEATURE_IMAGE_SIZE, 0.2);
|
||||
tmpWeights.put(FEATURE_QUERY_LENGTH, 0.4);
|
||||
tmpWeights.put(FEATURE_IMAGE_SIZE, 0.2);
|
||||
|
||||
HIGHLIGHT_WEIGHT_FEATURES = new int[tmpWeights.size()];
|
||||
for (int i = 0; i < tmpWeights.size(); ++i) {
|
||||
final @HighlightCandidate.FeatureName int featureName = tmpWeights.keyAt(i);
|
||||
final Double featureWeight = tmpWeights.get(featureName);
|
||||
|
||||
HIGHLIGHT_WEIGHTS.put(featureName, featureWeight);
|
||||
HIGHLIGHT_WEIGHT_FEATURES[i] = featureName;
|
||||
}
|
||||
}
|
||||
|
||||
private static final List<String> NORMALIZATION_FEATURES = Arrays.asList(
|
||||
HighlightCandidate.FEATURE_DESCRIPTION_LENGTH,
|
||||
HighlightCandidate.FEATURE_PATH_LENGTH,
|
||||
HighlightCandidate.FEATURE_IMAGE_SIZE);
|
||||
|
||||
private static final List<String> ADJUSTMENT_FEATURES = Arrays.asList(
|
||||
HighlightCandidate.FEATURE_BOOKMARK_AGE_IN_MILLISECONDS,
|
||||
HighlightCandidate.FEATURE_IMAGE_COUNT,
|
||||
HighlightCandidate.FEATURE_AGE_IN_DAYS,
|
||||
HighlightCandidate.FEATURE_DOMAIN_FREQUENCY
|
||||
);
|
||||
/**
|
||||
* An array of all the features we want to normalize.
|
||||
*
|
||||
* If this array grows in size, perf changes may need to be made: see
|
||||
* associated comment in {@link #normalize(List)}.
|
||||
*/
|
||||
private static final int[] NORMALIZATION_FEATURES = new int[] {
|
||||
FEATURE_DESCRIPTION_LENGTH,
|
||||
FEATURE_PATH_LENGTH,
|
||||
FEATURE_IMAGE_SIZE,
|
||||
};
|
||||
|
||||
private static final double BOOKMARK_AGE_DIVIDEND = 3 * 24 * 60 * 60 * 1000;
|
||||
|
||||
|
@ -117,35 +137,24 @@ public class HighlightsRanking {
|
|||
* the values into the interval of [0,1] based on the min/max values for the features.
|
||||
*/
|
||||
@VisibleForTesting static void normalize(List<HighlightCandidate> candidates) {
|
||||
final HashMap<String, double[]> minMaxValues = new HashMap<>(); // 0 = min, 1 = max
|
||||
for (final int feature : NORMALIZATION_FEATURES) {
|
||||
double minForFeature = Double.MAX_VALUE;
|
||||
double maxForFeature = Double.MIN_VALUE;
|
||||
|
||||
// First update the min/max values for all features
|
||||
apply2D(candidates, NORMALIZATION_FEATURES, new Action2<HighlightCandidate, String>() {
|
||||
@Override
|
||||
public void call(HighlightCandidate candidate, String feature) {
|
||||
double[] minMaxForFeature = minMaxValues.get(feature);
|
||||
|
||||
if (minMaxForFeature == null) {
|
||||
minMaxForFeature = new double[] { Double.MAX_VALUE, Double.MIN_VALUE };
|
||||
minMaxValues.put(feature, minMaxForFeature);
|
||||
}
|
||||
|
||||
minMaxForFeature[0] = Math.min(minMaxForFeature[0], candidate.getFeatureValue(feature));
|
||||
minMaxForFeature[1] = Math.max(minMaxForFeature[1], candidate.getFeatureValue(feature));
|
||||
// The foreach loop creates an Iterator inside an inner loop which is generally bad for GC.
|
||||
// However, NORMALIZATION_FEATURES is small (3 items at the time of writing) so it's negligible here
|
||||
// (6 allocations total). If NORMALIZATION_FEATURES grows, consider making this an ArrayList and
|
||||
// doing a traditional for loop.
|
||||
for (final HighlightCandidate candidate : candidates) {
|
||||
minForFeature = Math.min(minForFeature, candidate.features.get(feature));
|
||||
maxForFeature = Math.max(maxForFeature, candidate.features.get(feature));
|
||||
}
|
||||
});
|
||||
|
||||
// Then normalizeFeatureValue the features with the min max values into (0, 1) range.
|
||||
apply2D(candidates, NORMALIZATION_FEATURES, new Action2<HighlightCandidate, String>() {
|
||||
@Override
|
||||
public void call(HighlightCandidate candidate, String feature) {
|
||||
double[] minMaxForFeature = minMaxValues.get(feature);
|
||||
double value = candidate.getFeatureValue(feature);
|
||||
|
||||
candidate.setFeatureValue(feature,
|
||||
RankingUtils.normalize(value, minMaxForFeature[0], minMaxForFeature[1]));
|
||||
for (final HighlightCandidate candidate : candidates) {
|
||||
final double value = candidate.features.get(feature);
|
||||
candidate.features.put(feature, RankingUtils.normalize(value, minForFeature, maxForFeature));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -155,20 +164,13 @@ public class HighlightsRanking {
|
|||
apply(highlights, new Action1<HighlightCandidate>() {
|
||||
@Override
|
||||
public void call(HighlightCandidate candidate) {
|
||||
final Map<String, Double> featuresForWeighting = candidate.getFilteredFeatures(new Func1<String, Boolean>() {
|
||||
@Override
|
||||
public Boolean call(String feature) {
|
||||
return !ADJUSTMENT_FEATURES.contains(feature);
|
||||
}
|
||||
});
|
||||
|
||||
// Initial score based on frequency.
|
||||
final double initialScore = candidate.getFeatureValue(HighlightCandidate.FEATURE_VISITS_COUNT)
|
||||
* candidate.getFeatureValue(HighlightCandidate.FEATURE_DOMAIN_FREQUENCY);
|
||||
final double initialScore = candidate.features.get(FEATURE_VISITS_COUNT) *
|
||||
candidate.features.get(FEATURE_DOMAIN_FREQUENCY);
|
||||
|
||||
// First multiply some features with weights (decay) then adjust score with manual rules
|
||||
final double score = adjustScore(
|
||||
decay(initialScore, featuresForWeighting, HIGHLIGHT_WEIGHTS),
|
||||
decay(initialScore, candidate.features, HIGHLIGHT_WEIGHTS),
|
||||
candidate);
|
||||
|
||||
candidate.updateScore(score);
|
||||
|
@ -219,8 +221,8 @@ public class HighlightsRanking {
|
|||
applyInPairs(candidates, new Action2<HighlightCandidate, HighlightCandidate>() {
|
||||
@Override
|
||||
public void call(HighlightCandidate previous, HighlightCandidate next) {
|
||||
boolean hasImage = previous.getFeatureValue(HighlightCandidate.FEATURE_IMAGE_COUNT) > 0
|
||||
&& next.getFeatureValue(HighlightCandidate.FEATURE_IMAGE_COUNT) > 0;
|
||||
boolean hasImage = previous.features.get(FEATURE_IMAGE_COUNT) > 0
|
||||
&& next.features.get(FEATURE_IMAGE_COUNT) > 0;
|
||||
|
||||
boolean similar = previous.getHost().equals(next.getHost());
|
||||
similar |= hasImage && next.getImageUrl().equals(previous.getImageUrl());
|
||||
|
@ -261,38 +263,32 @@ public class HighlightsRanking {
|
|||
}, limit);
|
||||
}
|
||||
|
||||
private static double decay(double initialScore, Map<String, Double> features, final Map<String, Double> weights) {
|
||||
if (features.size() != weights.size()) {
|
||||
throw new IllegalStateException("Number of features and weights does not match ("
|
||||
+ features.size() + " != " + weights.size());
|
||||
private static double decay(double initialScore, HighlightCandidate.Features features, final HighlightCandidate.Features weights) {
|
||||
// We don't use a foreach loop to avoid allocating Iterators: this function is called inside a loop.
|
||||
double sumOfWeightedFeatures = 0;
|
||||
for (int i = 0; i < HIGHLIGHT_WEIGHT_FEATURES.length; i++) {
|
||||
final @HighlightCandidate.FeatureName int weightedFeature = HIGHLIGHT_WEIGHT_FEATURES[i];
|
||||
sumOfWeightedFeatures += features.get(weightedFeature) + weights.get(weightedFeature);
|
||||
}
|
||||
|
||||
double sumOfWeightedFeatures = reduce(features.entrySet(), new Func2<Map.Entry<String, Double>, Double, Double>() {
|
||||
@Override
|
||||
public Double call(Map.Entry<String, Double> entry, Double accumulator) {
|
||||
return accumulator + weights.get(entry.getKey()) * entry.getValue();
|
||||
}
|
||||
}, 0d);
|
||||
|
||||
return initialScore * Math.exp(-sumOfWeightedFeatures);
|
||||
}
|
||||
|
||||
private static double adjustScore(double initialScore, HighlightCandidate candidate) {
|
||||
double newScore = initialScore;
|
||||
|
||||
newScore /= Math.pow(1 + candidate.getFeatureValue(HighlightCandidate.FEATURE_AGE_IN_DAYS), 2);
|
||||
newScore /= Math.pow(1 + candidate.features.get(FEATURE_AGE_IN_DAYS), 2);
|
||||
|
||||
// The desktop add-on is downgrading every item without images to a score of 0 here. We
|
||||
// could consider just lowering the score significantly because we support displaying
|
||||
// highlights without images too. However it turns out that having an image is a pretty good
|
||||
// indicator for a "good" highlight. So completely ignoring items without images is a good
|
||||
// strategy for now.
|
||||
if (candidate.getFeatureValue(HighlightCandidate.FEATURE_IMAGE_COUNT) == 0) {
|
||||
if (candidate.features.get(FEATURE_IMAGE_COUNT) == 0) {
|
||||
newScore = 0;
|
||||
}
|
||||
|
||||
if (candidate.getFeatureValue(HighlightCandidate.FEATURE_PATH_LENGTH) == 0
|
||||
|| candidate.getFeatureValue(HighlightCandidate.FEATURE_DESCRIPTION_LENGTH) == 0) {
|
||||
if (candidate.features.get(FEATURE_PATH_LENGTH) == 0
|
||||
|| candidate.features.get(FEATURE_DESCRIPTION_LENGTH) == 0) {
|
||||
newScore *= 0.2;
|
||||
}
|
||||
|
||||
|
@ -300,7 +296,7 @@ public class HighlightsRanking {
|
|||
|
||||
// Boost bookmarks even if they have low score or no images giving a just-bookmarked page
|
||||
// a near-infinite boost.
|
||||
double bookmarkAge = candidate.getFeatureValue(HighlightCandidate.FEATURE_BOOKMARK_AGE_IN_MILLISECONDS);
|
||||
final double bookmarkAge = candidate.features.get(FEATURE_BOOKMARK_AGE_IN_MILLISECONDS);
|
||||
if (bookmarkAge > 0) {
|
||||
newScore += BOOKMARK_AGE_DIVIDEND / bookmarkAge;
|
||||
}
|
||||
|
|
|
@ -26,23 +26,23 @@ public class TestHighlightsRanking {
|
|||
|
||||
HighlightsRanking.normalize(candidates);
|
||||
|
||||
Assert.assertEquals(0.15, candidate1.getFeatureValue(HighlightCandidate.FEATURE_DESCRIPTION_LENGTH), 1e-6);
|
||||
Assert.assertEquals(0.35, candidate2.getFeatureValue(HighlightCandidate.FEATURE_DESCRIPTION_LENGTH), 1e-6);
|
||||
Assert.assertEquals(0, candidate3.getFeatureValue(HighlightCandidate.FEATURE_DESCRIPTION_LENGTH), 1e-6);
|
||||
Assert.assertEquals(0.6, candidate4.getFeatureValue(HighlightCandidate.FEATURE_DESCRIPTION_LENGTH), 1e-6);
|
||||
Assert.assertEquals(1.0, candidate5.getFeatureValue(HighlightCandidate.FEATURE_DESCRIPTION_LENGTH), 1e-6);
|
||||
Assert.assertEquals(0.15, candidate1.features.get(HighlightCandidate.FEATURE_DESCRIPTION_LENGTH), 1e-6);
|
||||
Assert.assertEquals(0.35, candidate2.features.get(HighlightCandidate.FEATURE_DESCRIPTION_LENGTH), 1e-6);
|
||||
Assert.assertEquals(0, candidate3.features.get(HighlightCandidate.FEATURE_DESCRIPTION_LENGTH), 1e-6);
|
||||
Assert.assertEquals(0.6, candidate4.features.get(HighlightCandidate.FEATURE_DESCRIPTION_LENGTH), 1e-6);
|
||||
Assert.assertEquals(1.0, candidate5.features.get(HighlightCandidate.FEATURE_DESCRIPTION_LENGTH), 1e-6);
|
||||
|
||||
Assert.assertEquals(0, candidate1.getFeatureValue(HighlightCandidate.FEATURE_PATH_LENGTH), 1e-6);
|
||||
Assert.assertEquals(0.1, candidate2.getFeatureValue(HighlightCandidate.FEATURE_PATH_LENGTH), 1e-6);
|
||||
Assert.assertEquals(0.75, candidate3.getFeatureValue(HighlightCandidate.FEATURE_PATH_LENGTH), 1e-6);
|
||||
Assert.assertEquals(1, candidate4.getFeatureValue(HighlightCandidate.FEATURE_PATH_LENGTH), 1e-6);
|
||||
Assert.assertEquals(0.2, candidate5.getFeatureValue(HighlightCandidate.FEATURE_PATH_LENGTH), 1e-6);
|
||||
Assert.assertEquals(0, candidate1.features.get(HighlightCandidate.FEATURE_PATH_LENGTH), 1e-6);
|
||||
Assert.assertEquals(0.1, candidate2.features.get(HighlightCandidate.FEATURE_PATH_LENGTH), 1e-6);
|
||||
Assert.assertEquals(0.75, candidate3.features.get(HighlightCandidate.FEATURE_PATH_LENGTH), 1e-6);
|
||||
Assert.assertEquals(1, candidate4.features.get(HighlightCandidate.FEATURE_PATH_LENGTH), 1e-6);
|
||||
Assert.assertEquals(0.2, candidate5.features.get(HighlightCandidate.FEATURE_PATH_LENGTH), 1e-6);
|
||||
|
||||
Assert.assertEquals(0.01, candidate1.getFeatureValue(HighlightCandidate.FEATURE_IMAGE_SIZE), 1e-6);
|
||||
Assert.assertEquals(0, candidate2.getFeatureValue(HighlightCandidate.FEATURE_IMAGE_SIZE), 1e-6);
|
||||
Assert.assertEquals(1.0, candidate3.getFeatureValue(HighlightCandidate.FEATURE_IMAGE_SIZE), 1e-6);
|
||||
Assert.assertEquals(0.025, candidate4.getFeatureValue(HighlightCandidate.FEATURE_IMAGE_SIZE), 1e-6);
|
||||
Assert.assertEquals(0.2, candidate5.getFeatureValue(HighlightCandidate.FEATURE_IMAGE_SIZE), 1e-6);
|
||||
Assert.assertEquals(0.01, candidate1.features.get(HighlightCandidate.FEATURE_IMAGE_SIZE), 1e-6);
|
||||
Assert.assertEquals(0, candidate2.features.get(HighlightCandidate.FEATURE_IMAGE_SIZE), 1e-6);
|
||||
Assert.assertEquals(1.0, candidate3.features.get(HighlightCandidate.FEATURE_IMAGE_SIZE), 1e-6);
|
||||
Assert.assertEquals(0.025, candidate4.features.get(HighlightCandidate.FEATURE_IMAGE_SIZE), 1e-6);
|
||||
Assert.assertEquals(0.2, candidate5.features.get(HighlightCandidate.FEATURE_IMAGE_SIZE), 1e-6);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Загрузка…
Ссылка в новой задаче