// Copyright (C) 2014 Guibing Guo // // 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 <http://www.gnu.org/licenses/>. // package librec.undefined; import happy.coding.io.Strings; import happy.coding.math.Randoms; import java.util.List; import librec.data.DenseMatrix; import librec.data.DenseVector; import librec.data.SparseMatrix; import librec.data.SparseVector; import librec.data.VectorEntry; import librec.intf.SocialRecommender; import com.google.common.collect.Table; import com.google.common.collect.Table.Cell; /** * FUST: Factored User Similarity Models with Trust for Top-N Recommender * Systems * * @author guoguibing * */ public class FUSTrmse extends SocialRecommender { private float rho, alpha, tau; private int nnz; private float regLambda, regBeta, regGamma; public FUSTrmse(SparseMatrix trainMatrix, SparseMatrix testMatrix, int fold) { super(trainMatrix, testMatrix, fold); isRankingPred = true; } @Override protected void initModel() { P = new DenseMatrix(numUsers, numFactors); Q = new DenseMatrix(numUsers, numFactors); P.init(0.01); Q.init(0.01); userBias = new DenseVector(numUsers); itemBias = new DenseVector(numItems); userBias.init(0.01); itemBias.init(0.01); nnz = trainMatrix.size(); rho = cf.getFloat("FISM.rho"); alpha = cf.getFloat("FISM.alpha"); tau = cf.getFloat("FUST.trust.tau"); regLambda = cf.getFloat("FISM.reg.lambda"); regBeta = cf.getFloat("FISM.reg.beta"); regGamma = cf.getFloat("FISM.reg.gamma"); } @Override protected void buildModel() { int sampleSize = (int) (rho * nnz); int totalSize = trainMatrix.numRows() * numItems; for (int iter = 1; iter <= numIters; iter++) { errs = 0; loss = 0; // temporal data DenseMatrix PS = new DenseMatrix(numUsers, numFactors); DenseMatrix QS = new DenseMatrix(numUsers, numFactors); // new training data by sampling negative values Table<Integer, Integer, Double> R = trainMatrix.getDataTable(); // make a random sample of negative feedback (total - nnz) List<Integer> indices = null; try { indices = Randoms.randInts(sampleSize, 0, totalSize - nnz); } catch (Exception e) { e.printStackTrace(); } int index = 0, count = 0; boolean isDone = false; for (int u = 0; u < trainMatrix.numRows(); u++) { for (int j = 0; j < numItems; j++) { double ruj = trainMatrix.get(u, j); if (ruj != 0) continue; // rated items if (count++ == indices.get(index)) { R.put(u, j, 0.0); index++; if (index >= indices.size()) { isDone = true; break; } } } if (isDone) break; } // update throughout each user-item-rating (u, j, ruj) cell for (Cell<Integer, Integer, Double> cell : R.cellSet()) { int u = cell.getRowKey(); int j = cell.getColumnKey(); double ruj = cell.getValue(); // for efficiency, use the below code to predict ruj instead of // simply using "predict(u,j)" SparseVector Cj = trainMatrix.column(j); double bu = userBias.get(u), bj = itemBias.get(j); double sum_vu = 0, sum_t = 0; for (VectorEntry ve : Cj) { int v = ve.index(); // for training, i and j should be equal as j may be rated // or unrated if (v != u) { double tuv = Math.pow(1 + socialMatrix.get(u, v), tau); sum_vu += tuv * DenseMatrix.rowMult(P, v, Q, u); sum_t += tuv; } } double wu = sum_t > 0 ? Math.pow(sum_t, -alpha) : 0; double pred = bu + bj + wu * sum_vu; double euj = pred - ruj; errs += euj * euj; loss += euj * euj; // update bu userBias.add(u, -lRate * (euj + regLambda * bu)); // update bj itemBias.add(j, -lRate * (euj + regGamma * bj)); loss += regLambda * bu * bu + regGamma * bj * bj; // update quf for (int f = 0; f < numFactors; f++) { double quf = Q.get(u, f); double sum_vf = 0; for (VectorEntry ve : Cj) { int v = ve.index(); if (v != u) { double tuv = socialMatrix.get(u, v); sum_vf += Math.pow(1 + tuv, tau) * P.get(v, f); } } double delta = euj * wu * sum_vf + regBeta * quf; QS.add(u, f, -lRate * delta); loss += regBeta * quf * quf; } // update pvf for (VectorEntry ve : Cj) { int v = ve.index(); if (v != u) { for (int f = 0; f < numFactors; f++) { double pvf = P.get(v, f); double tuv = socialMatrix.get(u, v); double delta = euj * wu * Math.pow(1 + tuv, tau) * Q.get(u, f) + regBeta * pvf; PS.add(v, f, -lRate * delta); loss += regBeta * pvf * pvf; } } } } P = P.add(PS); Q = Q.add(QS); errs *= 0.5; loss *= 0.5; if (isConverged(iter)) break; } } @Override protected double predict(int u, int j) { double sum = 0, sum_t = 0; SparseVector Cj = trainMatrix.column(j); for (VectorEntry ve : Cj) { int v = ve.index(); // for test, i and j will be always unequal as j is unrated if (v != u) { double tuv = Math.pow(socialMatrix.get(u, v) + 1, tau); sum += tuv * DenseMatrix.rowMult(P, v, Q, u); sum_t += tuv; } } double kappa = sum_t > 0 ? Math.pow(sum_t, -alpha) : 0; return userBias.get(u) + itemBias.get(j) + kappa * sum; } @Override public String toString() { return super.toString() + "," + Strings.toString(new Object[] { tau, rho, alpha, regLambda, regBeta, regGamma }, ","); } }