/* * Copyright 2010 Research Studios Austria Forschungsgesellschaft mBH * * This file is part of easyrec. * * easyrec is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * easyrec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with easyrec. If not, see <http://www.gnu.org/licenses/>. */ package org.easyrec.plugin.itemitem.impl; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.easyrec.model.core.AssociatedItemVO; import org.easyrec.model.core.ItemVO; import org.easyrec.model.core.RatingVO; import org.easyrec.plugin.itemitem.PredictionComputationStrategy; import org.easyrec.plugin.itemitem.model.UserAssoc; import org.easyrec.plugin.itemitem.store.dao.ActionDAO; import org.easyrec.plugin.itemitem.store.dao.UserAssocDAO; import java.util.HashMap; import java.util.List; import java.util.Map; import static com.google.common.base.Preconditions.checkNotNull; /** * Strategy for calculating predictions for users using a weighted sum. <p/> See [Sarwar et al, 2001]. <p/> [Sarwar et * al, 2001] Item-based collaborative filtering recommendation algorithms. In SIAM Data Mining (WWW'01), New York, NY, * USA, 2001. <p/> <p> <b>Company: </b> SAT, Research Studios Austria </p> <p/> <p> <b>Copyright: </b> (c) * 2009 </p> <p/> <p> <b>last modified:</b><br/> $Author$<br/> $Date$<br/> $Revision$ </p> * * @author Patrick Marschik */ public class WeightedPredictionComputationStrategy implements PredictionComputationStrategy { // ------------------------------ FIELDS ------------------------------ private ActionDAO actionDao; private final Log logger = LogFactory.getLog(getClass()); private double maxRatingValue; private double minRatingValue; private boolean normalizePredictions; private UserAssoc sample; private UserAssocDAO userAssocDao; // --------------------------- CONSTRUCTORS --------------------------- public WeightedPredictionComputationStrategy() { } // ------------------------ INTERFACE METHODS ------------------------ // --------------------- Interface PredictionComputationStrategy --------------------- public void beginPrediction(final UserAssoc sample, final int minRatingValue, final int maxRatingValue, final boolean normalizePredictions) { if (actionDao == null) throw new IllegalStateException("DAOs not set"); if (this.sample != null) throw new IllegalStateException("endPrediction not called"); this.sample = checkNotNull(sample); this.minRatingValue = minRatingValue; this.maxRatingValue = maxRatingValue; this.normalizePredictions = normalizePredictions; } public void endPrediction() { // userAssocDao.deleteAlreadyVotedAssocs(sample.getTenant(), sample.getSourceType()); sample = null; } public void predictForUserAndItem(final Integer userId, final ItemVO<Integer, Integer> item, final List<AssociatedItemVO<Integer, Integer>> itemAssocs) { if (actionDao == null || userAssocDao == null) throw new IllegalStateException("DAOs not set"); if (sample == null) throw new IllegalStateException("beginPrediction not called"); final Map<Integer, RatingVO<Integer, Integer>> ratingsOfUserMap = getRatingsOfUserMap(userId); double numerator = 0.0; double denominator = 0.0; // now for each similar item ... for (final AssociatedItemVO<Integer, Integer> itemAssoc : itemAssocs) { // get the rating of the user of that similar item final RatingVO<Integer, Integer> ratingOfUser = ratingsOfUserMap .get(itemAssoc.getItem().getItem()); // user didn't rate the other item so a rating of 0 is assumed if (ratingOfUser == null) continue; double similarity = itemAssoc.getAssocValue(); // similarity is in [-1.0, 1.0] -> move it to [0.0, 2.0] because negative similarities might cancel other similarities similarity += 1.0; double userRating = ratingOfUser.getRatingValue(); numerator += similarity * userRating; denominator += Math.abs(similarity); } if (denominator == 0) return; double prediction = numerator / denominator; if (normalizePredictions) { prediction = Math.max(prediction, minRatingValue); prediction = Math.min(prediction, maxRatingValue); } final UserAssoc userAssoc = new UserAssoc(prediction, sample.getChangeDate(), item, sample.getSourceTypeId(), sample.getTenantId(), userId); userAssocDao.insertOrUpdateUserAssoc(userAssoc); } public void setActionDAO(final ActionDAO actionDao) { this.actionDao = actionDao; } public void setUserAssocDAO(final UserAssocDAO userAssocDao) { this.userAssocDao = userAssocDao; } // -------------------------- OTHER METHODS -------------------------- /** * Creates a mapping of item IDs for which the user has rated to the ratings of the user * * @param userId The user to create the mapping for. * @return The mapping itemId -> RatingVO. */ private Map<Integer, RatingVO<Integer, Integer>> getRatingsOfUserMap(final Integer userId) { final List<RatingVO<Integer, Integer>> ratingsOfUser = actionDao .getLatestRatingsForTenant(sample.getTenantId(), sample.getItemTo().getType(), null, userId, null); final Map<Integer, RatingVO<Integer, Integer>> ratingsOfUserMap = new HashMap<Integer, RatingVO<Integer, Integer>>(); for (final RatingVO<Integer, Integer> rating : ratingsOfUser) { if (ratingsOfUserMap.containsKey(rating.getItem().getItem())) { if (logger.isWarnEnabled()) logger.warn("The user should have only voted once for the same item."); continue; } ratingsOfUserMap.put(rating.getItem().getItem(), rating); } return ratingsOfUserMap; } }