// 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.Configer; import happy.coding.io.FileIO; import happy.coding.io.Logs; import happy.coding.io.Strings; import happy.coding.io.net.EMailer; import happy.coding.system.Dates; import happy.coding.system.Systems; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import librec.baseline.ConstantGuess; import librec.baseline.GlobalAverage; import librec.baseline.ItemAverage; import librec.baseline.MostPopular; import librec.baseline.RandomGuess; import librec.baseline.UserAverage; import librec.data.DataDAO; import librec.data.DataSplitter; import librec.data.SparseMatrix; import librec.ext.AR; import librec.ext.Hybrid; import librec.ext.NMF; import librec.ext.PD; import librec.ext.PRankD; import librec.ext.SlopeOne; import librec.intf.Recommender; import librec.intf.Recommender.Measure; import librec.ranking.BPR; import librec.ranking.CLiMF; import librec.ranking.FISMauc; import librec.ranking.FISMrmse; import librec.ranking.GBPR; import librec.ranking.RankALS; import librec.ranking.RankSGD; import librec.ranking.SBPR; import librec.ranking.SLIM; import librec.ranking.WRMF; import librec.rating.BPMF; import librec.rating.BiasedMF; import librec.rating.ItemKNN; import librec.rating.PMF; import librec.rating.RSTE; import librec.rating.RegSVD; import librec.rating.SVDPlusPlus; import librec.rating.SoRec; import librec.rating.SoReg; import librec.rating.SocialMF; import librec.rating.TrustMF; import librec.rating.TrustSVD; import librec.rating.UserKNN; /** * Main Class of the LibRec Library * * @author guoguibing * */ public class LibRec { // version: MAJOR version (significant changes), followed by MINOR version // (small changes, bug fixes) private static String version = "1.0"; // configuration private static Configer cf; private static String algorithm; // params for multiple runs at once public static int paramIdx; public static boolean isMultRun = true; // rate DAO object private static DataDAO rateDao; // rating matrix public static SparseMatrix rateMatrix = null; public static void main(String[] args) throws Exception { // Logs.debug(LibRec.readme()); // get configuration file cf = new Configer("librec.conf"); // debug info debugInfo(); // prepare data rateDao = new DataDAO(cf.getPath("dataset.training")); rateMatrix = rateDao.readData(cf.getDouble("val.binary.threshold")); // config general recommender Recommender.cf = cf; Recommender.rateMatrix = rateMatrix; Recommender.rateDao = rateDao; // required: only one parameter varying for multiple run Recommender.params = RecUtils.buildParams(cf); // run algorithms if (Recommender.params.size() > 0) { // multiple run for (Entry<String, List<Float>> en : Recommender.params.entrySet()) { for (int i = 0, im = en.getValue().size(); i < im; i++) { LibRec.paramIdx = i; runAlgorithm(); // useful for some methods which do not use the parameters // defined in Recommender.params if (!isMultRun) break; } } } else { // single run runAlgorithm(); } // collect results String destPath = FileIO.makeDirectory("Results"); String dest = destPath + algorithm + "@" + Dates.now() + ".txt"; FileIO.copyFile("results.txt", dest); notifyMe(dest); } /** * general interface to run a recommendation algorithm */ private static void runAlgorithm() throws Exception { String testPath = cf.getPath("dataset.testing"); if (cf.getString("recommender").equals("tp")) TrustPredictor.update(); if (!testPath.equals("-1")) runTestFile(testPath); else if (cf.isOn("is.cross.validation")) runCrossValidation(); else if (cf.getDouble("val.ratio") > 0) runRatio(); else runGiven(); } /** * interface to run cross validation approach */ private static void runCrossValidation() throws Exception { int kFold = cf.getInt("num.kfold"); DataSplitter ds = new DataSplitter(rateMatrix, kFold); Thread[] ts = new Thread[kFold]; Recommender[] algos = new Recommender[kFold]; boolean isPara = cf.isOn("is.parallel.folds"); for (int i = 0; i < kFold; i++) { Recommender algo = getRecommender(ds.getKthFold(i + 1), i + 1); algos[i] = algo; ts[i] = new Thread(algo); ts[i].start(); if (!isPara) ts[i].join(); } if (isPara) for (Thread t : ts) t.join(); // average performance of k-fold Map<Measure, Double> avgMeasure = new HashMap<>(); for (Recommender algo : algos) { for (Entry<Measure, Double> en : algo.measures.entrySet()) { Measure m = en.getKey(); double val = avgMeasure.containsKey(m) ? avgMeasure.get(m) : 0.0; avgMeasure.put(m, val + en.getValue() / kFold); } } printEvalInfo(algos[0], avgMeasure); } /** * Interface to run ratio-validation approach */ private static void runRatio() throws Exception { DataSplitter ds = new DataSplitter(rateMatrix); double ratio = cf.getDouble("val.ratio"); Recommender algo = getRecommender(ds.getRatio(ratio), -1); algo.execute(); printEvalInfo(algo, algo.measures); } /** * Interface to run (Given N)-validation approach */ private static void runGiven() throws Exception { DataSplitter ds = new DataSplitter(rateMatrix); int n = cf.getInt("num.given.n"); double ratio = cf.getDouble("val.given.ratio"); Recommender algo = getRecommender(ds.getGiven(n > 0 ? n : ratio), -1); algo.execute(); printEvalInfo(algo, algo.measures); } /** * Interface to run testing using data from an input file * */ private static void runTestFile(String path) throws Exception { DataDAO testDao = new DataDAO(path, rateDao.getUserIds(), rateDao.getItemIds()); SparseMatrix testMatrix = testDao.readData(false); Recommender algo = getRecommender(new SparseMatrix[] { rateMatrix, testMatrix }, -1); algo.execute(); printEvalInfo(algo, algo.measures); } /** * print out the evaluation information for a specific algorithm */ private static void printEvalInfo(Recommender algo, Map<Measure, Double> ms) { String result = Recommender.getEvalInfo(ms); String time = Dates.parse(ms.get(Measure.TrainTime).longValue()) + "," + Dates.parse(ms.get(Measure.TestTime).longValue()); String evalInfo = String.format("%s,%s,%s,%s", algo.algoName, result, algo.toString(), time); Logs.info(evalInfo); } /** * @throws Exception * */ private static void notifyMe(String dest) throws Exception { if (!cf.isOn("is.email.notify")) return; EMailer notifier = new EMailer(); Properties props = notifier.getProps(); props.setProperty("mail.debug", "false"); String host = cf.getString("mail.smtp.host"); String port = cf.getString("mail.smtp.port"); props.setProperty("mail.smtp.host", host); props.setProperty("mail.smtp.port", port); props.setProperty("mail.smtp.auth", cf.getString("mail.smtp.auth")); props.put("mail.smtp.socketFactory.port", port); props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); final String user = cf.getString("mail.smtp.user"); final String pwd = cf.getString("mail.smtp.password"); props.setProperty("mail.smtp.user", user); props.setProperty("mail.smtp.password", pwd); props.setProperty("mail.from", user); props.setProperty("mail.to", cf.getString("mail.to")); props.setProperty("mail.subject", FileIO.getCurrentFolder() + "." + algorithm + " [" + Systems.getIP() + "]"); props.setProperty("mail.text", "Program was finished @" + Dates.now()); String msg = "Program [" + algorithm + "] has been finished !"; notifier.send(msg, dest); } /** * @return a recommender to be run */ private static Recommender getRecommender(SparseMatrix[] data, int fold) throws Exception { SparseMatrix trainMatrix = data[0], testMatrix = data[1]; algorithm = cf.getString("recommender"); switch (algorithm.toLowerCase()) { /* ongoing */ case "trustsvd2": return new TrustSVD2(trainMatrix, testMatrix, fold); case "trustsvd_dt": return new TrustSVD_DT(trainMatrix, testMatrix, fold); case "trustsvd++": return new TrustSVDPlusPlus(trainMatrix, testMatrix, fold); case "rbmf": return new RBMF(trainMatrix, testMatrix, fold); case "fusmrmse": return new FUSMrmse(trainMatrix, testMatrix, fold); case "fusm": case "fusmauc": return new FUSMauc(trainMatrix, testMatrix, fold); case "fust": return new FUSTrmse(trainMatrix, testMatrix, fold); case "fustauc": return new FUSTauc(trainMatrix, testMatrix, fold); case "tp": return new TrustPredictor(trainMatrix, testMatrix, fold); case "timesvd++": return new TimeSVDPlusPlus(trainMatrix, testMatrix, fold); case "fsm": return new FSM(trainMatrix, testMatrix, fold); case "aaai-basemf": return new BaseMF(trainMatrix, testMatrix, fold); case "aaai-dmf": return new DMF(trainMatrix, testMatrix, fold); case "aaai-basenm": return new BaseNM(trainMatrix, testMatrix, fold); case "aaai-dnm": return new DNM(trainMatrix, testMatrix, fold); case "aaai-drm": return new DRM(trainMatrix, testMatrix, fold); /* item ranking */ case "rankals": return new RankALS(trainMatrix, testMatrix, fold); case "ranksgd": return new RankSGD(trainMatrix, testMatrix, fold); case "climf": return new CLiMF(trainMatrix, testMatrix, fold); case "bpr": return new BPR(trainMatrix, testMatrix, fold); case "wrmf": return new WRMF(trainMatrix, testMatrix, fold); case "slim": return new SLIM(trainMatrix, testMatrix, fold); case "fismrmse": return new FISMrmse(trainMatrix, testMatrix, fold); case "fism": case "fismauc": return new FISMauc(trainMatrix, testMatrix, fold); case "sbpr": return new SBPR(trainMatrix, testMatrix, fold); case "gbpr": return new GBPR(trainMatrix, testMatrix, fold); /* user ratings */ case "userknn": return new UserKNN(trainMatrix, testMatrix, fold); case "itemknn": return new ItemKNN(trainMatrix, testMatrix, fold); case "regsvd": return new RegSVD(trainMatrix, testMatrix, fold); case "biasedmf": return new BiasedMF(trainMatrix, testMatrix, fold); case "svd++": return new SVDPlusPlus(trainMatrix, testMatrix, fold); case "pmf": return new PMF(trainMatrix, testMatrix, fold); case "bpmf": return new BPMF(trainMatrix, testMatrix, fold); case "socialmf": return new SocialMF(trainMatrix, testMatrix, fold); case "trustmf": return new TrustMF(trainMatrix, testMatrix, fold); case "rste": return new RSTE(trainMatrix, testMatrix, fold); case "sorec": return new SoRec(trainMatrix, testMatrix, fold); case "soreg": return new SoReg(trainMatrix, testMatrix, fold); case "trustsvd": return new TrustSVD(trainMatrix, testMatrix, fold); /* baselines */ case "globalavg": return new GlobalAverage(trainMatrix, testMatrix, fold); case "useravg": return new UserAverage(trainMatrix, testMatrix, fold); case "itemavg": return new ItemAverage(trainMatrix, testMatrix, fold); case "random": return new RandomGuess(trainMatrix, testMatrix, fold); case "constant": return new ConstantGuess(trainMatrix, testMatrix, fold); case "mostpop": return new MostPopular(trainMatrix, testMatrix, fold); /* extension */ case "nmf": return new NMF(trainMatrix, testMatrix, fold); case "hybrid": return new Hybrid(trainMatrix, testMatrix, fold); case "slopeone": return new SlopeOne(trainMatrix, testMatrix, fold); case "pd": return new PD(trainMatrix, testMatrix, fold); case "ar": return new AR(trainMatrix, testMatrix, fold); case "prankd": return new PRankD(trainMatrix, testMatrix, fold); default: throw new Exception("No recommender is specified!"); } } /** * Print out debug information */ private static void debugInfo() { String cv = "kFold: " + cf.getInt("num.kfold") + (cf.isOn("is.parallel.folds") ? " [Parallel]" : " [Singleton]"); float ratio = (float) cf.getDouble("val.ratio"); int givenN = cf.getInt("num.given.n"); float givenRatio = cf.getFloat("val.given.ratio"); String cvInfo = cf.isOn("is.cross.validation") ? cv : (ratio > 0 ? "ratio: " + ratio : "given: " + (givenN > 0 ? givenN : givenRatio)); String testPath = cf.getPath("dataset.testing"); boolean isTestingFlie = !testPath.equals("-1"); String mode = isTestingFlie ? String.format("Testing:: %s.", Strings.last(testPath, 38)) : cvInfo; if (!Recommender.isRankingPred) { String view = cf.getString("rating.pred.view"); switch (view.toLowerCase()) { case "cold-start": mode += ", " + view; break; case "trust-degree": mode += String.format(", %s [%d, %d]", new Object[] { view, cf.getInt("min.trust.degree"), cf.getInt("max.trust.degree") }); break; case "all": default: break; } } String debugInfo = String.format("Training: %s, %s", Strings.last(cf.getPath("dataset.training"), 38), mode); Logs.info(debugInfo); } /** * Print out software information */ public static String readme() { return "\nLibRec " + version + " Copyright (C) 2014 Guibing Guo \n\n" /* Description */ + "LibRec is free software: you can redistribute it and/or modify \n" + "it under the terms of the GNU General Public License as published by \n" + "the Free Software Foundation, either version 3 of the License, \n" + "or (at your option) any later version. \n\n" /* Usage */ + "LibRec is distributed in the hope that it will be useful, \n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of \n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the \n" + "GNU General Public License for more details. \n\n" /* licence */ + "You should have received a copy of the GNU General Public License \n" + "along with LibRec. If not, see <http://www.gnu.org/licenses/>."; } }