// 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.ArrayList; 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.IterativeRecommender; /** * FSM: Factored (User and Item) Similarity Models for Item Recommendation * * @author guoguibing * */ public class FSM extends IterativeRecommender { private int rho; private float alpha; private DenseMatrix X, Y; public FSM(SparseMatrix trainMatrix, SparseMatrix testMatrix, int fold) { super(trainMatrix, testMatrix, fold); isRankingPred = true; } @Override protected void initModel() { X = new DenseMatrix(numItems, numFactors); Y = new DenseMatrix(numItems, numFactors); X.init(smallValue); Y.init(smallValue); P = new DenseMatrix(numUsers, numFactors); Q = new DenseMatrix(numUsers, numFactors); P.init(smallValue); Q.init(smallValue); itemBias = new DenseVector(numItems); itemBias.init(smallValue); rho = cf.getInt("FISM.rho"); alpha = cf.getFloat("FISM.alpha"); } @Override protected void buildModel() { for (int iter = 1; iter <= numIters; iter++) { errs = 0; loss = 0; // item similarity DenseMatrix XS = new DenseMatrix(numItems, numFactors); DenseMatrix YS = new DenseMatrix(numItems, numFactors); // user similarity DenseMatrix PS = new DenseMatrix(numUsers, numFactors); DenseMatrix QS = new DenseMatrix(numUsers, numFactors); for (int u : trainMatrix.rows()) { SparseVector Ru = trainMatrix.row(u); for (VectorEntry ve : Ru) { int i = ve.index(); double rui = ve.get(); SparseVector Ci = trainMatrix.column(i); // make a random sample of negative feedback List<Integer> js = new ArrayList<>(); int len = 0; while (len < rho) { int j = Randoms.uniform(numItems); if (Ru.contains(j) || js.contains(j)) continue; js.add(j); len++; } double wci = Ci.getCount() - 1 > 0 ? Math.pow(Ci.getCount() - 1, -alpha) : 0; // user similarity double sum_ci = 0; double[] sum_cif = new double[numFactors]; for (int f = 0; f < numFactors; f++) { for (VectorEntry vk : Ci) { int v = vk.index(); if (u != v) { double pvf = P.get(v, f); sum_cif[f] += pvf; sum_ci += pvf * Q.get(u, f); } } } // item similarity double sum_ri = 0; double[] sum_rif = new double[numFactors]; double[] sum_rjf = new double[numFactors]; for (int f = 0; f < numFactors; f++) { for (VectorEntry vk : Ru) { int k = vk.index(); double xkf = X.get(k, f); if (k != i) { sum_rif[f] += xkf; sum_ri += xkf * Y.get(i, f); } sum_rjf[f] += xkf; } } double wri = Ru.getCount() - 1 > 0 ? Math.pow(Ru.getCount() - 1, -alpha) : 0; double wrj = Ru.getCount() > 0 ? Math.pow(Ru.getCount(), -alpha) : 0; double ru[] = new double[numFactors]; double ci[] = new double[numFactors]; // update for each unrated item for (int j : js) { // declare variables first to speed up int v, f; double pvf, quf, delta; // item similarity double sum_rj = 0; for (VectorEntry vk : Ru) { int k = vk.index(); sum_rj += DenseMatrix.rowMult(X, k, Y, j); } // user similarity SparseVector Cj = trainMatrix.column(j); double sum_cj = 0; double[] sum_cjf = new double[numFactors]; for (f = 0; f < numFactors; f++) { for (VectorEntry vk : Cj) { v = vk.index(); // sum_j += DenseMatrix.rowMult(P, v, Q, u); pvf = P.get(v, f); sum_cjf[f] += pvf; sum_cj += pvf * Q.get(u, f); } } double wcj = Cj.getCount() > 0 ? Math.pow(Cj.getCount(), -alpha) : 0; double bi = itemBias.get(i), bj = itemBias.get(j); double pui = bi + wri * sum_ri + wci * sum_ci; double puj = bj + wrj * sum_rj + wcj * sum_cj; double ruj = 0; double eij = (rui - ruj) - (pui - puj); errs += eij * eij; loss += eij * eij; // update bi itemBias.add(i, -lRate * (eij + regB * bi)); // update bj itemBias.add(j, -lRate * (eij - regB * bj)); loss += regB * bi * bi - regB * bj * bj; // update yif, yjf for (f = 0; f < numFactors; f++) { double yif = Y.get(i, f), yjf = Y.get(j, f); delta = eij * (-wri) * sum_rif[f] + regI * yif; YS.add(i, f, -lRate * delta); delta = eij * wrj * sum_rjf[f] - regI * yjf; YS.add(j, f, -lRate * delta); loss += regI * (yif * yif - yjf * yjf); ru[f] += eij * (wrj * yjf - wri * yif); } // update quf for (f = 0; f < numFactors; f++) { quf = Q.get(u, f); delta = eij * (wcj * sum_cjf[f] - wci * sum_cif[f]) + regU * quf; QS.add(u, f, -lRate * delta); ci[f] += -eij * quf; loss += regU * quf * quf; } // update pvf for v in Cj for (f = 0; f < numFactors; f++) { for (VectorEntry vk : Cj) { v = vk.index(); pvf = P.get(v, f); delta = eij * wcj * Q.get(u, f) - regU * pvf; PS.add(v, f, -lRate * delta); loss -= regU * pvf * pvf; } } } // update xkf for k in Ru for (int f = 0; f < numFactors; f++) { for (VectorEntry vk : Ru) { int k = vk.index(); if (k != i) { double xkf = X.get(k, f); double delta = ru[f] / rho + regI * xkf; XS.add(k, f, -lRate * delta); loss += regI * xkf * xkf; } } } // update pvf for v in Ci for (int f = 0; f < numFactors; f++) { for (VectorEntry vk : Ci) { int v = vk.index(); if (v != u) { double pvf = P.get(v, f); double delta = wci * ci[f] / rho + regU * pvf; PS.add(v, f, -lRate * delta); loss += regU * pvf * pvf; } } } } } X = X.add(XS); Y = Y.add(YS); 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 i) { double sum_r = 0, sum_c = 0; int count_r = 0, count_c = 0; // item similarity SparseVector Ru = trainMatrix.row(u); for (VectorEntry ve : Ru) { int k = ve.index(); if (k != i) { sum_r += DenseMatrix.rowMult(X, k, Y, i); count_r++; } } double wr = count_r > 0 ? Math.pow(count_r, -alpha) : 0; // user similarity SparseVector Ci = trainMatrix.column(i); for (VectorEntry ve : Ci) { int v = ve.index(); // for test, i and j will be always unequal as j is unrated if (v != u) { sum_c += DenseMatrix.rowMult(P, v, Q, u); count_c++; } } double wc = count_c > 0 ? Math.pow(count_c, -alpha) : 0; return itemBias.get(i) + wr * sum_r + wc * sum_c; } @Override public String toString() { return Strings .toString(new Object[] { binThold, rho, alpha, numFactors, initLRate, regU, regI, regB, numIters }); } }