net.librec.recommender.context.rating.TrustSVDRecommender 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.context.rating;
import com.google.common.cache.LoadingCache;
import net.librec.annotation.ModelData;
import net.librec.common.LibrecException;
import net.librec.math.structure.DenseMatrix;
import net.librec.math.structure.DenseVector;
import net.librec.math.structure.MatrixEntry;
import net.librec.recommender.SocialRecommender;
import java.util.List;
import java.util.concurrent.ExecutionException;
/**
* Guo et al., TrustSVD: Collaborative Filtering with Both the Explicit and Implicit Influence of User Trust and
* of Item Ratings, AAAI 2015.
*
* @author guoguibing and Keqiang Wang
*/
@ModelData({"isRating", "trustsvd", "userFactors", "itemFactors", "impItemFactors", "userBiases", "itemBiases", "socialMatrix", "trainMatrix"})
public class TrustSVDRecommender extends SocialRecommender {
/**
* impItemFactors denotes the implicit influence of items rated by user u in the past on the ratings of unknown items in the future.
*/
private DenseMatrix impItemFactors;
/**
* the user-specific latent appender vector of users (trustees)trusted by user u
*/
private DenseMatrix trusteeFactors;
/**
* weights of users(trustees) trusted by user u
*/
private DenseVector trusteeWeights;
/**
* weights of users(trusters) who trust user u
*/
private DenseVector trusterWeights;
/**
* weights of items rated by user u
*/
private DenseVector impItemWeights;
/**
* user biases and item biases
*/
private DenseVector userBiases, itemBiases;
/**
* bias regularization
*/
protected double regBias;
/**
* user-items cache, user-trustee cache
*/
protected LoadingCache> userItemsCache, userTrusteeCache;
/**
* Guava cache configuration
*/
protected static String cacheSpec;
/**
* initial the model
*
* @throws LibrecException if error occurs
*/
@Override
public void setup() throws LibrecException {
super.setup();
// userFactors.init(1.0);
// itemFactors.init(1.0);
regBias = conf.getDouble("rec.bias.regularization", 0.01);
cacheSpec = conf.get("guava.cache.spec", "maximumSize=200,expireAfterAccess=2m");
//initialize userBiases and itemBiases
userBiases = new DenseVector(numUsers);
itemBiases = new DenseVector(numItems);
userBiases.init(initMean, initStd);
itemBiases.init(initMean, initStd);
//initialize trusteeFactors and impItemFactors
trusteeFactors = new DenseMatrix(numUsers, numFactors);
impItemFactors = new DenseMatrix(numItems, numFactors);
trusteeFactors.init(initMean, initStd);
impItemFactors.init(initMean, initStd);
//initialize trusteeWeights, trusterWeights, impItemWeights
trusteeWeights = new DenseVector(numUsers);
trusterWeights = new DenseVector(numUsers);
impItemWeights = new DenseVector(numItems);
for (int userIdx = 0; userIdx < numUsers; userIdx++) {
int userFriendCount = socialMatrix.columnSize(userIdx);
trusteeWeights.set(userIdx, userFriendCount > 0 ? 1.0 / Math.sqrt(userFriendCount) : 1.0);
userFriendCount = socialMatrix.rowSize(userIdx);
trusterWeights.set(userIdx, userFriendCount > 0 ? 1.0 / Math.sqrt(userFriendCount) : 1.0);
}
for (int itemIdx = 0; itemIdx < numItems; itemIdx++) {
int itemUsersCount = trainMatrix.columnSize(itemIdx);
impItemWeights.set(itemIdx, itemUsersCount > 0 ? 1.0 / Math.sqrt(itemUsersCount) : 1.0);
}
//initialize user-items cache, user-trustee cache
userItemsCache = trainMatrix.rowColumnsCache(cacheSpec);
userTrusteeCache = socialMatrix.rowColumnsCache(cacheSpec);
}
/**
* train model process
*
* @throws LibrecException if error occurs
*/
@Override
protected void trainModel() throws LibrecException {
for (int iter = 1; iter <= numIterations; iter++) {
loss = 0.0d;
// temp user Factors and trustee factors
DenseMatrix tempUserFactors = new DenseMatrix(numUsers, numFactors);
DenseMatrix trusteeTempFactors = new DenseMatrix(numUsers, numFactors);
for (MatrixEntry matrixEntry : trainMatrix) {
int userIdx = matrixEntry.row(); // user userIdx
int itemIdx = matrixEntry.column(); // item itemIdx
double realRating = matrixEntry.get(); // real rating on item itemIdx rated by user userIdx
// To speed up, directly access the prediction instead of invoking "predictRating = predict(userIdx,itemIdx)"
double userBiasValue = userBiases.get(userIdx);
double itemBiasValue = itemBiases.get(itemIdx);
double predictRating = globalMean + userBiasValue + itemBiasValue + DenseMatrix.rowMult(userFactors, userIdx, itemFactors, itemIdx);
// get the implicit influence predict rating using items rated by user userIdx
List impItemsList = null;
try {
impItemsList = userItemsCache.get(userIdx);
} catch (ExecutionException e) {
e.printStackTrace();
}
if (impItemsList.size() > 0) {
double sum = 0;
for (int impItemIdx : impItemsList)
sum += DenseMatrix.rowMult(impItemFactors, impItemIdx, itemFactors, itemIdx);
predictRating += sum / Math.sqrt(impItemsList.size());
}
// the user-specific influence of users (trustees)trusted by user userIdx
List trusteesList = null;
try {
trusteesList = userTrusteeCache.get(userIdx);
} catch (ExecutionException e) {
e.printStackTrace();
}
if (trusteesList.size() > 0) {
double sum = 0.0;
for (int trusteeIdx : trusteesList)
sum += DenseMatrix.rowMult(trusteeFactors, trusteeIdx, itemFactors, itemIdx);
predictRating += sum / Math.sqrt(trusteesList.size());
}
double error = predictRating - realRating;
loss += error * error;
double userWeightDenom = Math.sqrt(impItemsList.size());
double trusteeWeightDenom = Math.sqrt(trusteesList.size());
double userWeight = 1.0 / userWeightDenom;
double itemWeight = impItemWeights.get(itemIdx);
// update factors
// stochastic gradient descent sgd
double sgd = error + regBias * userWeight * userBiasValue;
userBiases.add(userIdx, -learnRate * sgd);
sgd = error + regBias * itemWeight * itemBiasValue;
itemBiases.add(itemIdx, -learnRate * sgd);
loss += regBias * userWeight * userBiasValue * userBiasValue +
regBias * itemWeight * itemBiasValue * itemBiasValue;
double[] sumImpItemsFactors = new double[numFactors];
for (int factorIdx = 0; factorIdx < numFactors; factorIdx++) {
double sum = 0;
for (int impItemIdx : impItemsList)
sum += impItemFactors.get(impItemIdx, factorIdx);
sumImpItemsFactors[factorIdx] = userWeightDenom > 0 ? sum / userWeightDenom : sum;
}
double[] sumTrusteesFactors = new double[numFactors];
for (int factorIdx = 0; factorIdx < numFactors; factorIdx++) {
double sum = 0;
for (int trusteeIdx : trusteesList)
sum += trusteeFactors.get(trusteeIdx, factorIdx);
sumTrusteesFactors[factorIdx] = trusteeWeightDenom > 0 ? sum / trusteeWeightDenom : sum;
}
for (int factorIdx = 0; factorIdx < numFactors; factorIdx++) {
double userFactorValue = userFactors.get(userIdx, factorIdx);
double itemFactorValue = itemFactors.get(itemIdx, factorIdx);
double deltaUser = error * itemFactorValue + regUser * userWeight * userFactorValue;
double deltaItem = error * (userFactorValue + sumImpItemsFactors[factorIdx] + sumTrusteesFactors[factorIdx])
+ regItem * itemWeight * itemFactorValue;
tempUserFactors.add(userIdx, factorIdx, deltaUser);
itemFactors.add(itemIdx, factorIdx, -learnRate * deltaItem);
loss += regUser * userWeight * userFactorValue * userFactorValue
+ regItem * itemWeight * itemFactorValue * itemFactorValue;
for (int impItemIdx : impItemsList) {
double impItemFactorValue = impItemFactors.get(impItemIdx, factorIdx);
double impItemWeightValue = impItemWeights.get(impItemIdx);
double deltaImpItem = error * itemFactorValue / userWeightDenom + regItem * impItemWeightValue * impItemFactorValue;
impItemFactors.add(impItemIdx, factorIdx, -learnRate * deltaImpItem);
loss += regItem * impItemWeightValue * impItemFactorValue * impItemFactorValue;
}
// update trusteeTempFactors
for (int trusteeIdx : trusteesList) {
double trusteeFactorValue = trusteeFactors.get(trusteeIdx, factorIdx);
double trusteeWeightValue = trusteeWeights.get(trusteeIdx);
double deltaTrustee = error * itemFactorValue / trusteeWeightDenom + regUser * trusteeWeightValue * trusteeFactorValue;
trusteeTempFactors.add(trusteeIdx, factorIdx, deltaTrustee);
loss += regUser * trusteeWeightValue * trusteeFactorValue * trusteeFactorValue;
}
}
}
for (MatrixEntry socialMatrixEntry : socialMatrix) {
int userIdx = socialMatrixEntry.row();
int trusteeIdx = socialMatrixEntry.column();
double socialValue = socialMatrixEntry.get();
if (socialValue == 0)
continue;
double predtictSocialValue = DenseMatrix.rowMult(userFactors, userIdx, trusteeFactors, trusteeIdx);
double socialError = predtictSocialValue - socialValue;
loss += regSocial * socialError * socialError;
double deriValue = regSocial * socialError;
double trusterWeightValue = trusterWeights.get(userIdx);
for (int factorIdx = 0; factorIdx < numFactors; factorIdx++) {
double userFactorValue = userFactors.get(userIdx, factorIdx);
double trusteeFactorValue = trusteeFactors.get(trusteeIdx, factorIdx);
tempUserFactors.add(userIdx, factorIdx, deriValue * trusteeFactorValue + regSocial * trusterWeightValue * userFactorValue);
trusteeTempFactors.add(trusteeIdx, factorIdx, deriValue * userFactorValue);
loss += regSocial * trusterWeightValue * userFactorValue * userFactorValue;
}
}
userFactors.addEqual(tempUserFactors.scale(-learnRate));
trusteeFactors.addEqual(trusteeTempFactors.scale(-learnRate));
loss *= 0.5d;
if (isConverged(iter) && earlyStop) {
break;
}
updateLRate(iter);
}// end of training
}
/**
* predict a specific rating for user userIdx on item itemIdx.
*
* @param userIdx user index
* @param itemIdx item index
* @return predictive rating for user userIdx on item itemIdx
* @throws LibrecException if error occurs
*/
protected double predict(int userIdx, int itemIdx) throws LibrecException {
double predictRating = globalMean + userBiases.get(userIdx) + itemBiases.get(itemIdx) + DenseMatrix.rowMult(userFactors, userIdx, itemFactors, itemIdx);
//the implicit influence of items rated by user in the past on the ratings of unknown items in the future.
List userItemsList = null;
try {
userItemsList = userItemsCache.get(userIdx);
} catch (ExecutionException e) {
e.printStackTrace();
}
if (userItemsList.size() > 0) {
double sum = 0;
for (int userItemIdx : userItemsList)
sum += DenseMatrix.rowMult(impItemFactors, userItemIdx, itemFactors, itemIdx);
predictRating += sum / Math.sqrt(userItemsList.size());
}
// the user-specific influence of users (trustees)trusted by user u
List trusteeList = null;
try {
trusteeList = userTrusteeCache.get(userIdx);
} catch (ExecutionException e) {
e.printStackTrace();
}
if (trusteeList.size() > 0) {
double sum = 0.0;
for (int trusteeIdx : trusteeList)
sum += DenseMatrix.rowMult(trusteeFactors, trusteeIdx, itemFactors, itemIdx);
predictRating += sum / Math.sqrt(trusteeList.size());
}
return predictRating;
}
@Override
protected double predict(int userIdx, int itemIdx, boolean bounded) throws LibrecException {
double predictRating = predict(userIdx, itemIdx);
return predictRating;
}
}