/*
* Copyright (C) 2015 RankSys (http://ranksys.org)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.ranksys.diversity.prop.reranking;
import es.uam.eps.ir.ranksys.core.feature.FeatureData;
import es.uam.eps.ir.ranksys.core.Recommendation;
import es.uam.eps.ir.ranksys.diversity.binom.BinomialModel;
import es.uam.eps.ir.ranksys.novdiv.reranking.GreedyReranker;
import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap;
import org.jooq.lambda.tuple.Tuple2;
import org.ranksys.core.util.tuples.Tuple2od;
import java.util.Comparator;
/**
* Proportionality re-ranking method.
* <br>
*
* Dang, V., Croft, W. B. (2012). Diversity by Proportionality: An Election-based Approach to Search Result Diversification. In Proceedings of the 35th International ACM SIGIR Conference on Research and Development in Information Retrieval (pp. 65–74). New York, NY, USA: ACM. doi:10.1145/2348283.2348296
*
* @author Saúl Vargas (Saul.Vargas@mendeley.com)
*
* @param <U> type of user
* @param <I> type of item
* @param <F> type of feature
*/
public class PM<U, I, F> extends GreedyReranker<U, I> {
private final double lambda;
private final FeatureData<I, F, ?> featureData;
private final BinomialModel<U, I, F> binomialModel;
/**
* Constructor.
*
* @param binomialModel binomial model for features
* @param featureData feature data
* @param lambda relevance-diversity tradeoff
* @param cutoff metric cutoff
*/
public PM(FeatureData<I, F, ?> featureData, BinomialModel<U, I, F> binomialModel, double lambda, int cutoff) {
super(cutoff);
this.lambda = lambda;
this.featureData = featureData;
this.binomialModel = binomialModel;
}
@Override
protected GreedyUserReranker getUserReranker(Recommendation<U, I> recommendation, int maxLength) {
return new UserPM(recommendation, maxLength);
}
private class UserPM extends GreedyUserReranker {
private final BinomialModel<U, I, F>.UserBinomialModel ubm;
private final Object2DoubleOpenHashMap<F> featureCount;
private final Object2DoubleOpenHashMap<Object> probNorm;
private F lcf;
public UserPM(Recommendation<U, I> recommendation, int maxLength) {
super(recommendation, maxLength);
this.ubm = binomialModel.getModel(recommendation.getUser());
this.featureCount = new Object2DoubleOpenHashMap<>();
featureCount.defaultReturnValue(0.0);
this.probNorm = new Object2DoubleOpenHashMap<>();
recommendation.getItems()
.forEach(i -> featureData.getItemFeatures(i.v1).sequential()
.forEach(fv -> probNorm.addTo(fv.v1, i.v2)));
this.lcf = getLcf();
}
private F getLcf() {
return ubm.getFeatures().stream().max(Comparator.comparingDouble(this::quotient)).get();
}
private double quotient(F f) {
return ubm.p(f) / (featureCount.getDouble(f) + 0.5);
}
@Override
protected double value(Tuple2od<I> iv) {
return featureData.getItemFeatures(iv.v1)
.map(Tuple2::v1)
.mapToDouble(f -> (f.equals(lcf) ? lambda : (1 - lambda)) * quotient(f) * iv.v2 / probNorm.getDouble(f))
.sum();
}
@Override
protected void update(Tuple2od<I> biv) {
double norm = featureData.getItemFeatures(biv.v1)
.map(Tuple2::v1)
.mapToDouble(f -> biv.v2 / probNorm.getDouble(f))
.sum();
featureData.getItemFeatures(biv.v1).sequential()
.map(Tuple2::v1)
.forEach(f -> {
double v = biv.v2 / (probNorm.getDouble(f) * norm);
featureCount.addTo(f, v);
});
lcf = getLcf();
}
}
}