package happy.research.cf; import happy.coding.math.Maths; import happy.coding.math.Sims; import happy.coding.math.Stats; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; public class Performance { private String method = null; private Measures ms = new Measures(); public final static int[] cutoffs = { 5, 10, 15, 20 }; /* predictable relevant ratings: {user, {item, rating}} */ private Map<String, Map<String, Prediction>> userPreds = new HashMap<>(); public Performance(String method) { this.method = method; } public int size() { return userPreds.size(); } /** * performance of ranked recommendation list * * @param topN the size of recommendation list * @return the measurements for the ranking performance */ public Measures ranking(Map<String, Map<String, Rating>> test, boolean sort_by_prediction) { // cut-off at position N Map<String, List<Prediction>> ranked_preds = null; if (sort_by_prediction) ranked_preds = this.sortByPred(); else ranked_preds = this.sortByConf(cutoffs[0]); // rated => relevant for (int cutoff : cutoffs) { List<Double> precisions = new ArrayList<>(); List<Double> recalls = new ArrayList<>(); List<Double> RRs = new ArrayList<>(); List<Double> APs = new ArrayList<>(); List<Double> nDCGs = new ArrayList<>(); for (Entry<String, List<Prediction>> en : ranked_preds.entrySet()) { String user = en.getKey(); List<Prediction> rec_preds = en.getValue(); Map<String, Rating> test_ratings = test.get(user); double iDCG = 0; int n_rated = 0; if (test_ratings.size() > cutoff) n_rated = cutoff; else n_rated = test_ratings.size(); for (int i = 0; i < n_rated; i++) { iDCG += 1.0 / Maths.log(i + 2, 2); } int n_rec = rec_preds.size(); if (n_rec > cutoff) rec_preds = rec_preds.subList(0, cutoff); double DCG = 0; int tp = 0; double RR = 0; boolean first_relevant = false; List<Double> pks = new ArrayList<>(); for (int i = 0; i < rec_preds.size(); i++) { Prediction pred = rec_preds.get(i); double truth = pred.getTruth(); if (truth > 0) { int rank = i + 1; DCG += 1.0 / Maths.log(rank + 1, 2); tp++; // precision at k pks.add((tp + 0.0) / rank); if (!first_relevant) { RR = 1.0 / rank; first_relevant = true; } } } precisions.add((tp + 0.0) / n_rec); recalls.add((tp + 0.0) / test_ratings.size()); RRs.add(RR); APs.add(Stats.mean(pks)); nDCGs.add(DCG / iDCG); } double precision = Stats.mean(precisions); double recall = Stats.mean(recalls); double F1 = Stats.hMean(precision, recall); ms.addPrecision(cutoff, precision); ms.addRecall(cutoff, recall); ms.addF1(cutoff, F1); ms.addNDCG(cutoff, Stats.mean(nDCGs)); ms.addMAP(cutoff, Stats.mean(APs)); ms.addMRR(cutoff, Stats.mean(RRs)); } return ms; } /** * performance of predicting items' ratings * * @return the measurements for the predictive performance */ public Measures prediction(Map<String, Map<String, Rating>> test) { List<Double> AEs = new ArrayList<>(); List<Double> SEs = new ArrayList<>(); List<Double> AUEs = new ArrayList<>(); // absolute user errors List<Double> confs = new ArrayList<>(); // all the prediction confidence int covered_ratings = 0, covered_users = 0; int total_ratings = 0, total_users = 0; for (Entry<String, Map<String, Rating>> en : test.entrySet()) { // test user String user = en.getKey(); total_users++; // test ratings Map<String, Rating> trs = en.getValue(); total_ratings += trs.size(); // predicted ratings Map<String, Prediction> preds = userPreds.get(user); if (preds == null) continue; covered_users++; List<Double> UEs = new ArrayList<>(); for (Prediction pred : preds.values()) { double e = pred.error(); AEs.add(e); confs.add(pred.getConf()); SEs.add(e * e); UEs.add(e); covered_ratings++; } double aue = Stats.mean(UEs); AUEs.add(aue); } ms.setMAE(Stats.mean(AEs)); ms.setMAUE(Stats.mean(AUEs)); ms.setMACE(Stats.average(AEs, confs)); ms.setUC(covered_users, total_users); ms.setRMSE(Math.sqrt(Stats.mean(SEs))); ms.setRC(covered_ratings, total_ratings); return ms; } /** * diversity performance of ranked recommendation list * * @param data: including both training and testing data: * 0) training data: {user, {item, rating}} * 1) training data: {item, {user, rating}} * 2) testing data: {item, {user, rating}} * @param user_test: testing data: {user, {item, rating}} * @param topN the size of recommendation list * @return the measurements for the diversity performance of ranked recommendation list */ public Measures diversity(Map<String, Map<String, Rating>>[] data, int topN, boolean sort_by_prediction) { Map<String, Map<String, Rating>> trainUsers = data[0]; Map<String, Map<String, Rating>> trainItems = data[1]; Map<String, Map<String, Rating>> testItems = data[2]; Map<String, List<Prediction>> ranked_preds = null; if (sort_by_prediction) ranked_preds = this.sortByPred(); else ranked_preds = this.sortByConf(topN); /* compute item dissimilarity */ Map<String, Map<String, Double>> itemDisSims = new HashMap<>(); List<String> items = new ArrayList<>(testItems.keySet()); for (int i = 0; i < items.size(); i++) { String itemA = items.get(i); Map<String, Double> itemDiss = null; Map<String, Rating> trainRatingsA = trainItems.get(itemA); if (trainRatingsA == null || trainRatingsA.size() < 1) continue; if (itemDisSims.containsKey(itemA)) itemDiss = itemDisSims.get(itemA); else itemDiss = new HashMap<>(); for (int j = i + 1; j < items.size(); j++) { String itemB = items.get(j); Map<String, Rating> trainRatingsB = trainItems.get(itemB); if (trainRatingsB == null || trainRatingsB.size() < 1) continue; List<Double> r1 = new ArrayList<>(); List<Double> r2 = new ArrayList<>(); for (String userA : trainRatingsA.keySet()) { if (trainRatingsB.containsKey(userA)) { r1.add(trainRatingsA.get(userA).getRating()); r2.add(trainRatingsB.get(userA).getRating()); } } double sim = Sims.pcc(r1, r2); if (!Double.isNaN(sim)) itemDiss.put(itemB, 1 - sim); } itemDisSims.put(itemA, itemDiss); } /* inter-user diversity */ List<Double> UDs = new ArrayList<>(); /* intra-user diversity (item novelty) */ List<Double> INs = new ArrayList<>(); /* set diversity */ List<Double> SDs = new ArrayList<>(); int n_train_users = trainUsers.size(); List<String> users = new ArrayList<>(ranked_preds.keySet()); for (int i = 0; i < users.size(); i++) { String userA = users.get(i); List<Prediction> asPreds = ranked_preds.get(userA); // inter-user diversity for (int j = i + 1; j < users.size(); j++) { String userB = users.get(j); List<Prediction> bsPreds = ranked_preds.get(userB); Map<String, Prediction> item_preds = toItemPredMap(bsPreds); int count = 0; for (Prediction ap : asPreds) { String item = ap.getItemId(); if (item_preds.containsKey(item)) count++; } double UD = 1 - (count + 0.0) / topN; UDs.add(UD); } // intra-user diversity int n_items = asPreds.size(); double ins = 0; for (Prediction pred : asPreds) { String item = pred.getItemId(); Map<String, Rating> userRatings = trainItems.get(item); if (userRatings == null) continue; double in = 1 - Maths.log(userRatings.size(), n_train_users); ins += in; } INs.add(ins / n_items); // set diversity for (int j = 0; j < asPreds.size(); j++) { String item1 = asPreds.get(j).getItemId(); for (int k = j + 1; k < asPreds.size(); k++) { String item2 = asPreds.get(k).getItemId(); double dist = 0; if (itemDisSims.containsKey(item1)) { Map<String, Double> distSims = itemDisSims.get(item1); if (distSims.containsKey(item2)) { dist = distSims.get(item2); SDs.add(dist); } } else if (itemDisSims.containsKey(item2)) { Map<String, Double> distSims = itemDisSims.get(item2); if (distSims.containsKey(item1)) { dist = distSims.get(item1); SDs.add(dist); } } } } } ms.setUD(Stats.mean(UDs)); ms.setIN(Stats.mean(INs)); ms.setSD(Stats.mean(SDs)); return ms; } private Map<String, Prediction> toItemPredMap(List<Prediction> preds) { Map<String, Prediction> item_preds = new HashMap<>(); for (Prediction pred : preds) { String item = pred.getItemId(); item_preds.put(item, pred); } return item_preds; } /** * @return a sorted recommendation list sorted by predictions */ protected Map<String, List<Prediction>> sortByPred() { Map<String, List<Prediction>> recLists = new HashMap<>(); for (Entry<String, Map<String, Prediction>> en : userPreds.entrySet()) { String user = en.getKey(); List<Prediction> preds = new ArrayList<>(en.getValue().values()); Collections.sort(preds, new Comparator<Prediction>() { @Override public int compare(Prediction p1, Prediction p2) { double pred1 = p1.getPred(); double pred2 = p2.getPred(); if (pred1 > pred2) return 1; else if (pred1 == pred2) return 0; else return -1; } }); Collections.reverse(preds); recLists.put(user, preds); } return recLists; } /** * @return a sorted recommendation list sorted by predictions */ protected Map<String, List<Prediction>> sortByConf(int topN) { Map<String, List<Prediction>> recLists = new HashMap<>(); for (Entry<String, Map<String, Prediction>> en : userPreds.entrySet()) { String user = en.getKey(); List<Prediction> preds = new ArrayList<>(en.getValue().values()); Collections.sort(preds, new Comparator<Prediction>() { @Override public int compare(Prediction p1, Prediction p2) { double pred1 = p1.getConf(); double pred2 = p2.getConf(); if (pred1 > pred2) return 1; else if (pred1 == pred2) return 0; else return -1; } }); Collections.reverse(preds); if (preds.size() > topN) preds = preds.subList(0, topN); recLists.put(user, preds); } return recLists; } /** * @return a sorted recommendation list sorted by ground truth */ protected Map<String, List<Prediction>> sortByTruth(int topN) { Map<String, List<Prediction>> recLists = new HashMap<>(); for (Entry<String, Map<String, Prediction>> en : userPreds.entrySet()) { String user = en.getKey(); List<Prediction> preds = new ArrayList<>(en.getValue().values()); Collections.sort(preds, new Comparator<Prediction>() { @Override public int compare(Prediction p1, Prediction p2) { double truth1 = p1.getTruth(); double truth2 = p2.getTruth(); if (truth1 > truth2) return 1; else if (truth1 == truth2) return 0; else return -1; } }); Collections.reverse(preds); if (preds.size() > topN) preds = preds.subList(0, topN); recLists.put(user, preds); } return recLists; } public synchronized void addPredicts(Prediction pred) { String user = pred.getUserId(); String item = pred.getItemId(); Map<String, Prediction> rs = null; if (userPreds.containsKey(user)) rs = userPreds.get(user); else rs = new HashMap<>(); rs.put(item, pred); userPreds.put(user, rs); } public String getMethod() { return method; } }