net.librec.recommender.FactorizationMachineRecommender 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;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import net.librec.common.LibrecException;
import net.librec.math.structure.*;
import net.librec.recommender.item.RecommendedItemList;
import net.librec.recommender.item.RecommendedList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Factorization Machine Recommender
*
* Rendle, Steffen, et al., Fast Context-aware Recommendations with Factorization Machines, SIGIR, 2011.
*
* @author Tang Jiaxi and Ma Chen
*/
public abstract class FactorizationMachineRecommender extends AbstractRecommender {
/**
* LOG
*/
protected final Log LOG = LogFactory.getLog(this.getClass());
/**
* train Tensor
*/
protected SparseTensor trainTensor;
/**
* testTensor
*/
protected SparseTensor testTensor;
/**
* validTensor
*/
protected SparseTensor validTensor;
/**
* global bias
*/
protected double w0;
/**
* appender vector size: number of users + number of items + number of contextual conditions
*/
protected int p;
/**
* number of factors
*/
protected int k;
/**
* number of ratings
*/
protected int n;
/**
* weight vector
*/
protected DenseVector W; // p
/**
* parameter matrix
*/
protected DenseMatrix V; // p x k
/**
* parameter matrix
*/
protected DenseMatrix Q; // n x k
/**
* regularization term for weight and factors
*/
protected float regW0, regW, regF;
/**
* the number of latent factors
*/
protected int numFactors;
/**
* the number of iterations
*/
protected int numIterations;
/**
* setup
*
* @throws LibrecException if error occurs
*
*/
protected void setup() throws LibrecException {
conf = context.getConf();
isRanking = conf.getBoolean("rec.recommender.isranking");
if (isRanking) {
topN = conf.getInt("rec.recommender.ranking.topn", 5);
}
earlyStop = conf.getBoolean("rec.recommender.earlyStop");
numIterations = conf.getInt("rec.iterator.maximum");
trainTensor = (SparseTensor) getDataModel().getTrainDataSet();
testTensor = (SparseTensor) getDataModel().getTestDataSet();
validTensor = (SparseTensor) getDataModel().getValidDataSet();
userMappingData = getDataModel().getUserMappingData();
itemMappingData = getDataModel().getItemMappingData();
numUsers = userMappingData.size();
numItems = itemMappingData.size();
globalMean = trainTensor.mean();
maxRate = conf.getDouble("rec.recommender.maxrate", 12.0);
minRate = conf.getDouble("rec.recommender.minrate", 0.0);
// initialize the parameters of FM
for (int dim = 0; dim < trainTensor.numDimensions; dim++) {
p += trainTensor.dimensions[dim]; // set the size of appender vectors
}
n = trainTensor.size(); // set the number of ratings
numFactors = k = conf.getInt("rec.factor.number");
// init all weight with zero
w0 = 0;
W = new DenseVector(p);
W.init(0);
// init factors with small value
V = new DenseMatrix(p, k);
V.init(0, 0.1);
regW0 = conf.getFloat("rec.fm.regw0", 0.01f);
regW = conf.getFloat("rec.fm.regW", 0.01f);
regF = conf.getFloat("rec.fm.regF", 10f);
}
/**
* Predict the rating given a sparse appender vector.
* @param userId user Id
* @param itemId item Id
* @param x the given vector to predict.
*
* @return predicted rating
* @throws LibrecException if error occurs
*/
protected double predict(int userId, int itemId, SparseVector x) throws LibrecException {
double res = 0;
// global bias
res += w0;
// 1-way interaction
for (VectorEntry ve : x) {
double val = ve.get();
int ind = ve.index();
res += val * W.get(ind);
}
// 2-way interaction
for (int f = 1; f < k; f++) {
double sum1 = 0;
double sum2 = 0;
for (VectorEntry ve : x) {
double xi = ve.get();
int i = ve.index();
double vif = V.get(i, f);
sum1 += vif * xi;
sum2 += vif * vif * xi * xi;
}
res += (sum1 * sum1 - sum2) / 2;
}
return res;
}
/**
* Predict the rating given a sparse appender vector.
* if {@code bound} is true,The predicted rating value will be
* bounded in {@code [minRate, maxRate]}
*
* @param x the given vector
* @param userId the user id
* @param itemId the item id
* @param bound whether to bound the predicted rating
*
* @return predicted rating
* @throws LibrecException if error occurs
*/
protected double predict(int userId, int itemId, SparseVector x, boolean bound) throws LibrecException {
double pred = predict(userId, itemId, x);
if (bound) {
if (pred > maxRate)
pred = maxRate;
if (pred < minRate)
pred = minRate;
}
return pred;
}
/**
* recommend
* * predict the ratings in the test data
*
* @return predictive rating matrix
* @throws LibrecException if error occurs
*/
protected RecommendedList recommendRating() throws LibrecException {
testMatrix = testTensor.rateMatrix();
recommendedList = new RecommendedItemList(numUsers - 1, numUsers);
// each user-item pair appears in the final recommend list only once
Table ratingMapping = HashBasedTable.create();
int userDimension = testTensor.getUserDimension();
int itemDimension = testTensor.getItemDimension();
for (TensorEntry tensorEntry : testTensor) {
int[] entryKeys = tensorEntry.keys();
SparseVector featureVector = tenserKeysToFeatureVector(entryKeys);
double predictRating = predict(entryKeys[userDimension], entryKeys[itemDimension], featureVector, true);
if (Double.isNaN(predictRating)) {
predictRating = globalMean;
}
int[] userItemInd = getUserItemIndex(featureVector);
int userIdx = userItemInd[0];
int itemIdx = userItemInd[1];
if (!ratingMapping.contains(userIdx, itemIdx)) {
ratingMapping.put(userIdx, itemIdx, predictRating);
recommendedList.addUserItemIdx(userIdx, itemIdx, predictRating);
}
}
return recommendedList;
}
/**
* getUserItemIndex
* * get the user index and item index from a sparse appender vector
*
* @return user index and item index
*/
private int[] getUserItemIndex(SparseVector x) {
int[] inds = x.getIndex();
int userInd = inds[0];
int itemInd = inds[1] - numUsers;
return new int[]{userInd, itemInd};
}
/**
* Transform the keys of a tensor entry into a sparse vector.
* @param tenserKeys the given keys of a tensor entry
*
* @return sparse appender vector
*/
protected SparseVector tenserKeysToFeatureVector(int[] tenserKeys) {
int capacity = p;
int[] index = new int[tenserKeys.length];
double[] data = new double[tenserKeys.length];
int colPrefix = 0;
for (int i = 0; i < tenserKeys.length; i++) {
data[i] = 1;
index[i] += colPrefix + tenserKeys[i];
colPrefix += trainTensor.dimensions[i];
}
return new SparseVector(capacity, index, data);
}
}