net.librec.recommender.cf.ranking.FISMaucRecommender 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 com.google.common.cache.LoadingCache;
import net.librec.annotation.ModelData;
import net.librec.common.LibrecException;
import net.librec.math.algorithm.Randoms;
import net.librec.math.structure.DenseMatrix;
import net.librec.math.structure.DenseVector;
import net.librec.math.structure.SparseVector;
import net.librec.recommender.MatrixFactorizationRecommender;
import java.util.List;
import java.util.concurrent.ExecutionException;
/**
* Kabbur et al., FISM: Factored Item Similarity Models for Top-N Recommender Systems, KDD 2013.
*
* @author SunYatong
*/
@ModelData({"isRanking", "fismrmse", "P", "Q", "itemBiases", "userBiases"})
public class FISMaucRecommender extends MatrixFactorizationRecommender {
private float rho,alpha,beta,gamma;
/**
* bias regularization
*/
private double lRate;
/**
* items and users biases vector
*/
private DenseVector itemBiases;
/**
* two low-rank item matrices, an item-item similarity was learned as a product of these two matrices
*/
private DenseMatrix P, Q;
/**
* user-items cache, item-users cache
*/
protected LoadingCache> userItemsCache;
/**
* Guava cache configuration
*/
protected static String cacheSpec;
@Override
protected void setup() throws LibrecException {
super.setup();
P = new DenseMatrix(numItems, numFactors);
Q = new DenseMatrix(numItems, numFactors);
P.init(0,0.01);
Q.init(0,0.01);
itemBiases = new DenseVector(numItems);
itemBiases.init(0,0.01);
rho = conf.getFloat("rec.fismauc.rho");//3-15
alpha = conf.getFloat("rec.fismauc.alpha",0.5f);
beta = conf.getFloat("rec.fismauc.beta",0.6f);
gamma=conf.getFloat("rec.fismauc.gamma",0.1f);
lRate=conf.getDouble("rec.iteration.learnrate",0.0001);
cacheSpec = conf.get("guava.cache.spec", "maximumSize=200,expireAfterAccess=2m");
userItemsCache = trainMatrix.rowColumnsCache(cacheSpec);
userItemsCache = trainMatrix.rowColumnsCache(cacheSpec);
}
@Override
protected void trainModel() throws LibrecException{
for (int iter = 1; iter <= numIterations; iter++) {
loss = 0;
// for all u in C
for (int u = 0; u < numUsers; u++) {
SparseVector Ru = trainMatrix.row(u);
int Ru_p_size = Ru.size();
if(Ru_p_size == 0 || Ru_p_size == 1) {
Ru_p_size = 2;
}
// for all i in Ru+
for (int i : Ru.getIndex()) {
// x <- 0
DenseVector x = new DenseVector(numFactors);
x.init(0);
// t <- (n - 1)^(-alpha) Σ pj (j!=i)
DenseVector t = new DenseVector(numFactors);
t.init(0);
for (int j : Ru.getIndex()) {
if(i != j){
t = t.add(P.row(j));
}
}
t = t.scale(Math.pow(Ru_p_size - 1, -alpha));
for (int tindex = 0; tindex < numFactors; tindex ++) {
if (Double.isNaN(t.get(tindex))) {
LOG.info("user:" + u + ", item:" + i + ", Ru_p_size:" + Ru_p_size);
}
}
// Z <- SampleZeros(rho)
int sampleSize = (int)(rho * Ru_p_size);
// make a random sample of negative feedback for Ru-
List negative_indices = null;
try {
negative_indices = Randoms.randInts(sampleSize, 0, numItems);
for (int list_idx = 0; list_idx < negative_indices.size(); list_idx++) {
int index = negative_indices.get(list_idx);
// if negative_indices contains positive index, remove it
if (Ru.get(index) > 0.1) {
negative_indices.remove(list_idx);
}
}
} catch (Exception e) {
e.printStackTrace();
}
// for all j in Z
for (int j : negative_indices) {
double bi = itemBiases.get(i);
double bj = itemBiases.get(j);
// update pui puj rui ruj
double rui = Ru.get(i);
double pui = bi + Q.row(i).inner(t);
double puj = bj + Q.row(j).inner(t);
double ruj = 0.0;
double e = (rui - ruj) - (pui - puj);
loss += e*e;
// update bi bj
itemBiases.add(i, lRate * (e - gamma * bi));
itemBiases.add(j, lRate * (e - gamma * bj));
// update qi qj
DenseVector delta_qi=t.scale(e).minus(Q.row(i).scale(beta));
DenseVector qi = Q.row(i).add(delta_qi.scale(lRate));
Q.setRow(i, qi);
DenseVector delta_qj=t.scale(e).minus(Q.row(j).scale(beta));
DenseVector qj = Q.row(j).add(delta_qj.scale(lRate));
Q.setRow(j, qj);
// update x
x = x.add(qi.minus(qj).scale(e));
}
// for all j in Ru+\{i}
for (int j : Ru.getIndex()) {
if (j != i) {
// update pj
DenseVector delta_pj = x.scale(Math.pow(rho, -1) * Math.pow(Ru_p_size - 1, -alpha)).minus(P.row(j).scale(beta));
P.setRow(j, P.row(j).add(delta_pj.scale(lRate)));
}
}
}
}
for (int i = 0; i ratedItems = null;
try {
ratedItems = userItemsCache.get(u);
} catch (ExecutionException e) {
e.printStackTrace();
}
for (int i : ratedItems) {
// for test, i and j will be always unequal as j is unrated
if (i != j) {
sum += DenseMatrix.rowMult(P, i, Q, j);
count++;
}
}
double wu = count > 0 ? Math.pow(count, -alpha) : 0;
return pred + wu * sum;
}
}