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

net.librec.recommender.cf.rating.LLORMARecommender Maven / Gradle / Ivy

package net.librec.recommender.cf.rating;

import net.librec.common.LibrecException;
import net.librec.math.algorithm.KernelSmoothing;
import net.librec.math.algorithm.Randoms;
import net.librec.math.structure.DenseMatrix;
import net.librec.math.structure.DenseVector;
import net.librec.math.structure.MatrixEntry;
import net.librec.math.structure.SparseMatrix;
import net.librec.recommender.MatrixFactorizationRecommender;

import java.util.List;

/**
 * 

Local Low-Rank Matrix Approximation

*

* This implementation refers to the method proposed by Lee et al. at ICML 2013. *

* Lcoal Structure: Joonseok Lee, Local Low-Rank Matrix Approximation * , ICML. 2013: 82-90. * * @author GuoGuibing and Keqiang Wang */ public class LLORMARecommender extends MatrixFactorizationRecommender { private int globalNumFactors, localNumFactors; private int globalNumIterations, localNumIterations; private int numThreads; protected double globalRegUser, globalRegItem, localRegUser, localRegItem; private double globalLearnRate, localLearnRate; private SparseMatrix predictMatrix; private int numLocalModels; private DenseMatrix globalUserFactors, globalItemFactors; /* * (non-Javadoc) * * @see net.librec.recommender.AbstractRecommender#setup() */ @Override protected void setup() throws LibrecException { super.setup(); globalNumFactors = conf.getInt("rec.global.factors.num", 20); localNumFactors = numFactors; globalNumIterations = conf.getInt("rec.global.iteration.maximum", 100); localNumIterations = numIterations; globalRegUser = conf.getDouble("rec.global.user.regularization", 0.01); globalRegItem = conf.getDouble("rec.global.item.regularization", 0.01); localRegUser = regUser; localRegItem = regItem; globalLearnRate = conf.getDouble("rec.global.iteration.learnrate", 0.01); localLearnRate = conf.getDouble("rec.iteration.learnrate", 0.01); numThreads = conf.getInt("rec.thread.count", 4); numLocalModels = conf.getInt("rec.model.num", 50); numThreads = numThreads > numLocalModels ? numLocalModels : numThreads; //global svd P Q to calculate the kernel value between users (or items) globalUserFactors = new DenseMatrix(numUsers, globalNumFactors); globalItemFactors = new DenseMatrix(numItems, globalNumFactors); // initialize model globalUserFactors.init(initMean, initStd); globalItemFactors.init(initMean, initStd); this.buildGlobalModel(); predictMatrix = new SparseMatrix(testMatrix); } //global svd P Q private void buildGlobalModel() { for (int globalIter = 1; globalIter <= globalNumIterations; globalIter++) { for (MatrixEntry matrixEntry : trainMatrix) { int userIdx = matrixEntry.row(); // user int itemIdx = matrixEntry.column(); // item double rating = matrixEntry.get(); double predictRating = DenseMatrix.rowMult(globalUserFactors, userIdx, globalItemFactors, itemIdx); double error = rating - predictRating; // update factors for (int factorIdx = 0; factorIdx < globalNumFactors; factorIdx++) { double puf = globalUserFactors.get(userIdx, factorIdx); double qif = globalItemFactors.get(itemIdx, factorIdx); globalUserFactors.add(userIdx, factorIdx, globalLearnRate * (error * qif - globalRegUser * puf)); globalItemFactors.add(itemIdx, factorIdx, globalLearnRate * (error * puf - globalRegItem * qif)); } } }// end of training } @Override protected void trainModel() throws LibrecException { // Pre-calculating similarity: int completeModelCount = 0; LLORMAUpdater[] learners = new LLORMAUpdater[numThreads]; int[] anchorArrayUser = new int[numLocalModels]; int[] anchorArrayItem = new int[numLocalModels]; int modelCount = 0; int[] runningThreadList = new int[numThreads]; int runningThreadCount = 0; int waitingThreadPointer = 0; int nextRunningSlot = 0; SparseMatrix cumPredictionMatrix = new SparseMatrix(testMatrix); SparseMatrix cumWeightMatrix = new SparseMatrix(testMatrix); for (MatrixEntry matrixEntry : testMatrix) { int userIdx = matrixEntry.row(); int itemIdx = matrixEntry.column(); cumPredictionMatrix.set(userIdx, itemIdx, 0.0); cumWeightMatrix.set(userIdx, itemIdx, 0.0); } // Parallel training: while (completeModelCount < numLocalModels) { int anchorUser = Randoms.uniform(numUsers); List itemList = trainMatrix.getColumns(anchorUser); if (itemList != null && itemList.size() > 0) { if (runningThreadCount < numThreads && modelCount < numLocalModels) { // Selecting a new anchor point: int itemListIdx = Randoms.uniform(itemList.size()); int anchorItem = itemList.get(itemListIdx); anchorArrayUser[modelCount] = anchorUser; anchorArrayItem[modelCount] = anchorItem; // Preparing weight vectors: DenseVector userWeights = kernelSmoothing(numUsers, anchorUser, KernelSmoothing.EPANECHNIKOV_KERNEL, 0.8, false); DenseVector itemWeights = kernelSmoothing(numItems, anchorItem, KernelSmoothing.EPANECHNIKOV_KERNEL, 0.8, true); // Starting a new local model learning: learners[nextRunningSlot] = new LLORMAUpdater(modelCount, localNumFactors, numUsers, numItems, anchorUser, anchorItem, localLearnRate, localRegUser, localRegItem, localNumIterations, userWeights, itemWeights, trainMatrix); learners[nextRunningSlot].start(); runningThreadList[runningThreadCount] = modelCount; runningThreadCount++; modelCount++; nextRunningSlot++; } else if (runningThreadCount > 0) { // Joining a local model which was done with learning: try { learners[waitingThreadPointer].join(); } catch (InterruptedException ie) { LOG.error("Join failed: " + ie); } int currentModelThreadIdx = waitingThreadPointer; int currentModelAnchorIdx = completeModelCount; completeModelCount++; // Predicting with the new local model and all previous models: predictMatrix = new SparseMatrix(testMatrix); for (MatrixEntry matrixEntry : testMatrix) { int userIdx = matrixEntry.row(); int itemIdx = matrixEntry.column(); double weight = KernelSmoothing.kernelize(getUserSimilarity(anchorArrayUser[currentModelAnchorIdx], userIdx), 0.8, KernelSmoothing.EPANECHNIKOV_KERNEL) * KernelSmoothing.kernelize(getItemSimilarity(anchorArrayItem[currentModelAnchorIdx], itemIdx), 0.8, KernelSmoothing.EPANECHNIKOV_KERNEL); double newPrediction = (learners[currentModelThreadIdx].getLocalUserFactors().row(userIdx, false) .inner(learners[currentModelThreadIdx].getLocalItemFactors().row(itemIdx, false))) * weight; cumWeightMatrix.set(userIdx, itemIdx, cumWeightMatrix.get(userIdx, itemIdx) + weight); cumPredictionMatrix.set(userIdx, itemIdx, cumPredictionMatrix.get(userIdx, itemIdx) + newPrediction); double prediction = cumPredictionMatrix.get(userIdx, itemIdx) / cumWeightMatrix.get(userIdx, itemIdx); prediction = Double.isNaN(prediction) || prediction == 0.0 ? globalMean : prediction; prediction = prediction < minRate ? minRate : prediction; prediction = prediction > maxRate ? maxRate : prediction; predictMatrix.set(userIdx, itemIdx, prediction); } nextRunningSlot = waitingThreadPointer; waitingThreadPointer = (waitingThreadPointer + 1) % numThreads; runningThreadCount--; } } } } /** * Calculate similarity between two users, based on the global base SVD. * * @param userIdx1 The first user's ID. * @param userIdx2 The second user's ID. * @return The similarity value between two users idx1 and idx2. */ private double getUserSimilarity(int userIdx1, int userIdx2) { double sim; DenseVector userVector1 = globalUserFactors.row(userIdx1); DenseVector userVector2 = globalUserFactors.row(userIdx2); sim = 1 - 2.0 / Math.PI * Math.acos(userVector1.inner(userVector2) / (Math.sqrt(userVector1.inner(userVector1)) * Math.sqrt(userVector2.inner(userVector2)))); if (Double.isNaN(sim)) { sim = 0.0; } return sim; } /** * Calculate similarity between two items, based on the global base SVD. * * @param itemIdx1 The first item's ID. * @param itemIdx2 The second item's ID. * @return The similarity value between two items idx1 and idx2. */ private double getItemSimilarity(int itemIdx1, int itemIdx2) { double sim; DenseVector itemVector1 = globalItemFactors.row(itemIdx1); DenseVector itemVector2 = globalItemFactors.row(itemIdx2); sim = 1 - 2.0 / Math.PI * Math.acos(itemVector1.inner(itemVector2) / (Math.sqrt(itemVector1.inner(itemVector1)) * Math.sqrt(itemVector2.inner(itemVector2)))); if (Double.isNaN(sim)) { sim = 0.0; } return sim; } /** * Given the similarity, it applies the given kernel. * This is done either for all users or for all items. * * @param size The length of user or item vector. * @param anchorIdx The identifier of anchor point. * @param kernelType The type of kernel. * @param width Kernel width. * @param isItemFeature return item kernel if yes, return user kernel otherwise. * @return The kernel-smoothed values for all users or all items. */ private DenseVector kernelSmoothing(int size, int anchorIdx, int kernelType, double width, boolean isItemFeature) { DenseVector newFeatureVector = new DenseVector(size); newFeatureVector.set(anchorIdx, 1.0); for (int index = 0; index < size; index++) { double sim; if (isItemFeature) { sim = getItemSimilarity(index, anchorIdx); } else { // userFeature sim = getUserSimilarity(index, anchorIdx); } newFeatureVector.set(index, KernelSmoothing.kernelize(sim, width, kernelType)); } return newFeatureVector; } @Override protected double predict(int userIdx, int itemIdx) { return predictMatrix.get(userIdx, itemIdx); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy