// 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.math.Randoms; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import librec.data.DenseVector; import librec.data.MatrixEntry; import librec.data.SparseMatrix; import librec.data.SparseVector; import librec.data.SymmMatrix; import librec.intf.IterativeRecommender; public class BaseNM extends IterativeRecommender { protected SymmMatrix itemCorrs, last_S; protected boolean isPosOnly; protected double minSim; public BaseNM(SparseMatrix trainMatrix, SparseMatrix testMatrix, int fold) { super(trainMatrix, testMatrix, fold); algoName = "BaseNM"; isPosOnly = cf.isOn("is.similarity.pos"); minSim = isPosOnly ? 0.0 : Double.NEGATIVE_INFINITY; } @Override protected void updates() { super.updates(); if (itemCorrs != null) last_S = itemCorrs.clone(); } @Override protected void undos(int iter) { super.undos(iter); if (last_S != null) itemCorrs = last_S.clone(); } @Override protected void initModel() { // user, item biases userBias = new DenseVector(numUsers); itemBias = new DenseVector(numItems); userBias.init(initMean, initStd); itemBias.init(initMean, initStd); // item correlation matrix itemCorrs = new SymmMatrix(numItems); // ignore items without any training ratings: can greatly reduce memory usage Set<Integer> items = new HashSet<>(); for (int i = 0; i < numItems; i++) if (trainMatrix.column(i).getCount() == 0) items.add(i); for (int i = 0; i < numItems; i++) { if (items.contains(i)) continue; itemCorrs.set(i, i, 0.0); for (int j = i + 1; j < numItems; j++) { if (items.contains(j)) continue; double val = isPosOnly ? Randoms.uniform(0.0, 0.01) : Randoms.gaussian(initMean, initStd); itemCorrs.set(i, j, val); } } } protected void buildModel() { for (int iter = 1; iter <= numIters; iter++) { loss = 0; errs = 0; for (MatrixEntry me : trainMatrix) { int u = me.row(); // user int j = me.column(); // item double ruj = me.get(); if (ruj <= 0.0) continue; // a set of similar items SparseVector uv = trainMatrix.row(u, j); List<Integer> items = new ArrayList<>(); for (int i : uv.getIndex()) { if (itemCorrs.get(j, i) > minSim) items.add(i); } double w = Math.sqrt(items.size()); // obtain the prediction double bu = userBias.get(u), bj = itemBias.get(j); double pred = globalMean + bu + bj; double sum_sji = 0; for (int i : items) { double sji = itemCorrs.get(j, i); double rui = uv.get(i); double bui = globalMean + bu + itemBias.get(i); pred += sji * (rui - bui) / w; sum_sji += sji / w; } double euj = ruj - pred; errs += euj * euj; loss += euj * euj; // update similarity frist since bu and bj are used here for (int i : items) { double sji = itemCorrs.get(j, i); double rui = uv.get(i); double bui = globalMean + bu + itemBias.get(i); double delta = lRate * (euj * (rui - bui) / w - regU * sji); itemCorrs.add(j, i, delta); loss += regU * sji * sji; } // update factors double sgd = euj * (1 - sum_sji) - regU * bu; userBias.add(u, lRate * sgd); loss += regU * bu * bu; sgd = euj * (1 - sum_sji) - regI * bj; itemBias.add(j, lRate * sgd); loss += regI * bj * bj; } errs *= 0.5; loss *= 0.5; if (isConverged(iter)) break; }// end of training } @Override protected double predict(int u, int j) { double bu = userBias.get(u); double pred = globalMean + bu + itemBias.get(j); // get a number of similar items except item j SparseVector uv = trainMatrix.row(u, j); int[] items = uv.getIndex(); int k = 0; double sum = 0; for (int i : items) { double sji = itemCorrs.get(j, i); if (sji != 0 && sji > minSim) { double rui = trainMatrix.get(u, i); double bui = globalMean + bu + itemBias.get(i); sum += sji * (rui - bui); k++; } } if (k > 0) pred += sum / Math.sqrt(k); return pred; } @Override public String toString() { return super.toString() + "," + isPosOnly; } }