All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.librec.recommender.cf.ranking.SLIMRecommender Maven / Gradle / Ivy

/**
 * Copyright (C) 2016 LibRec
 * 

* This file is part of LibRec. * LibRec 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. *

* LibRec 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 LibRec. If not, see . */ package net.librec.recommender.cf.ranking; import net.librec.annotation.ModelData; import net.librec.common.LibrecException; import net.librec.math.structure.DenseMatrix; import net.librec.math.structure.SparseVector; import net.librec.math.structure.SymmMatrix; import net.librec.math.structure.VectorEntry; import net.librec.recommender.AbstractRecommender; import net.librec.util.Lists; import java.util.*; /** * Xia Ning and George Karypis, SLIM: Sparse Linear Methods for Top-N Recommender Systems, ICDM 2011.
*

* Related Work: *

    *
  • Levy and Jack, Efficient Top-N Recommendation by Linear Regression, ISRS 2013. This paper reports experimental * results on the MovieLens (100K, 10M) and Epinions datasets in terms of precision, MRR and HR@N (i.e., Recall@N).
  • *
  • Friedman et al., Regularization Paths for Generalized Linear Models via Coordinate Descent, Journal of * Statistical Software, 2010.
  • *
* * @author guoguibing and Keqiang Wang */ @ModelData({"isRanking", "slim", "coefficientMatrix", "trainMatrix", "similarityMatrix", "knn"}) public class SLIMRecommender extends AbstractRecommender { /** * the number of iterations */ protected int numIterations; /** * W in original paper, a sparse matrix of aggregation coefficients */ private DenseMatrix coefficientMatrix; /** * item's nearest neighbors for kNN > 0 */ private Set[] itemNNs; /** * regularization parameters for the L1 or L2 term */ private float regL1Norm, regL2Norm; /** * number of nearest neighbors */ protected static int knn; /** * item similarity matrix */ private SymmMatrix similarityMatrix; /** * item's nearest neighbors for kNN <=0, i.e., all other items */ private Set allItems; /** * initialization * * @throws LibrecException if error occurs */ @Override protected void setup() throws LibrecException { super.setup(); knn = conf.getInt("rec.neighbors.knn.number", 50); numIterations = conf.getInt("rec.iterator.maximum"); regL1Norm = conf.getFloat("rec.slim.regularization.l1", 1.0f); regL2Norm = conf.getFloat("rec.slim.regularization.l2", 1.0f); coefficientMatrix = new DenseMatrix(numItems, numItems); // initial guesses: make smaller guesses (e.g., W.init(0.01)) to speed up training coefficientMatrix.init(); similarityMatrix = context.getSimilarity().getSimilarityMatrix(); for (int itemIdx = 0; itemIdx < numItems; itemIdx++) { coefficientMatrix.set(itemIdx, itemIdx, 0.0d); } createItemNNs(); } /** * train model * * @throws LibrecException if error occurs */ @Override protected void trainModel() throws LibrecException { // number of iteration cycles for (int iter = 1; iter <= numIterations; iter++) { loss = 0.0d; // each cycle iterates through one coordinate direction for (int itemIdx = 0; itemIdx < numItems; itemIdx++) { // find k-nearest neighbors Set nearestNeighborCollection = knn > 0 ? itemNNs[itemIdx] : allItems; double[] userRatingEntries = new double[numUsers]; Iterator userItr = trainMatrix.rowIterator(itemIdx); while (userItr.hasNext()) { VectorEntry userRatingEntry = userItr.next(); userRatingEntries[userRatingEntry.index()] = userRatingEntry.get(); } // for each nearest neighbor nearestNeighborItemIdx, update coefficienMatrix by the coordinate // descent update rule for (Integer nearestNeighborItemIdx : nearestNeighborCollection) { if (nearestNeighborItemIdx != itemIdx) { double gradSum = 0.0d, rateSum = 0.0d, errors = 0.0d; Iterator nnUserRatingItr = trainMatrix.rowIterator(nearestNeighborItemIdx); if (!nnUserRatingItr.hasNext()) { continue; } int nnCount = 0; while (nnUserRatingItr.hasNext()) { VectorEntry nnUserVectorEntry = nnUserRatingItr.next(); int nnUserIdx = nnUserVectorEntry.index(); double nnRating = nnUserVectorEntry.get(); double rating = userRatingEntries[nnUserIdx]; double error = rating - predict(nnUserIdx, itemIdx, nearestNeighborItemIdx); gradSum += nnRating * error; rateSum += nnRating * nnRating; errors += error * error; nnCount++; } gradSum /= nnCount; rateSum /= nnCount; errors /= nnCount; double coefficient = coefficientMatrix.get(nearestNeighborItemIdx, itemIdx); loss += errors + 0.5 * regL2Norm * coefficient * coefficient + regL1Norm * coefficient; double update = 0.0d; if (regL1Norm < Math.abs(gradSum)) { if (gradSum > 0) { update = (gradSum - regL1Norm) / (regL2Norm + rateSum); } else { // One doubt: in this case, wij<0, however, the // paper says wij>=0. How to gaurantee that? update = (gradSum + regL1Norm) / (regL2Norm + rateSum); } } coefficientMatrix.set(nearestNeighborItemIdx, itemIdx, update); } } } if (isConverged(iter) && earlyStop) { break; } } } /** * predict a specific ranking score for user userIdx on item itemIdx. * * @param userIdx user index * @param itemIdx item index * @param excludedItemIdx excluded item index * @return a prediction without the contribution of excluded item */ protected double predict(int userIdx, int itemIdx, int excludedItemIdx) { double predictRating = 0; Iterator itemEntryIterator = trainMatrix.colIterator(userIdx); while (itemEntryIterator.hasNext()) { VectorEntry itemEntry = itemEntryIterator.next(); int nearestNeighborItemIdx = itemEntry.index(); double nearestNeighborPredictRating = itemEntry.get(); if (itemNNs[itemIdx].contains(nearestNeighborItemIdx) && nearestNeighborItemIdx != excludedItemIdx) { predictRating += nearestNeighborPredictRating * coefficientMatrix.get(nearestNeighborItemIdx, itemIdx); } } return predictRating; } @Override protected boolean isConverged(int iter) { double delta_loss = lastLoss - loss; lastLoss = loss; // print out debug info if (verbose) { String recName = getClass().getSimpleName().toString(); String info = recName + " iter " + iter + ": loss = " + loss + ", delta_loss = " + delta_loss; LOG.info(info); } return iter > 1 ? delta_loss < 1e-5 : false; } /** * predict a specific ranking score for user userIdx on item itemIdx. * * @param userIdx user index * @param itemIdx item index * @return predictive ranking score for user userIdx on item itemIdx * @throws LibrecException if error occurs */ @Override protected double predict(int userIdx, int itemIdx) throws LibrecException { // create item knn list if not exists, for local offline model if (!(null != itemNNs && itemNNs.length > 0)) { createItemNNs(); } return predict(userIdx, itemIdx, -1); } /** * Create item KNN list. */ public void createItemNNs() { itemNNs = new HashSet[numItems]; // find the nearest neighbors for each item based on item similarity List> tempItemSimList; if (knn > 0) { for (int itemIdx = 0; itemIdx < numItems; ++itemIdx) { SparseVector similarityVector = similarityMatrix.row(itemIdx); if (knn < similarityVector.size()) { tempItemSimList = new ArrayList<>(similarityVector.size() + 1); Iterator simItr = similarityVector.iterator(); while (simItr.hasNext()) { VectorEntry simVectorEntry = simItr.next(); tempItemSimList.add(new AbstractMap.SimpleImmutableEntry<>(simVectorEntry.index(), simVectorEntry.get())); } tempItemSimList = Lists.sortListTopK(tempItemSimList, true, knn); itemNNs[itemIdx] = new HashSet<>((int) (tempItemSimList.size() / 0.5)); for (Map.Entry tempItemSimEntry : tempItemSimList) { itemNNs[itemIdx].add(tempItemSimEntry.getKey()); } } else { itemNNs[itemIdx] = similarityVector.getIndexSet(); } } } else { allItems = new HashSet<>(trainMatrix.columns()); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy