From 8fbd66016c182cdbe8ae1044a54927d983c35155 Mon Sep 17 00:00:00 2001 From: Hercules Date: Sun, 23 Jan 2022 07:38:30 +0200 Subject: [PATCH] Optimize SVDpp training loop by reducing Python / C-API interactions - adds dependency on C++ compilation --- setup.py | 24 ++++---- .../matrix_factorization.pyx | 59 ++++++++++++++----- 2 files changed, 59 insertions(+), 24 deletions(-) diff --git a/setup.py b/setup.py index 00342907..e8878f57 100644 --- a/setup.py +++ b/setup.py @@ -58,7 +58,7 @@ else: USE_CYTHON = True -__version__ = '1.1.1' +__version__ = '1.1.2' here = path.abspath(path.dirname(__file__)) @@ -87,16 +87,20 @@ Extension( 'surprise.prediction_algorithms.matrix_factorization', ['surprise/prediction_algorithms/matrix_factorization' + ext], + include_dirs=[np.get_include()], + language="c++"), # generate and compile C++ code + Extension( + 'surprise.prediction_algorithms.optimize_baselines', + ['surprise/prediction_algorithms/optimize_baselines' + ext], + include_dirs=[np.get_include()]), + Extension( + 'surprise.prediction_algorithms.slope_one', + ['surprise/prediction_algorithms/slope_one' + ext], + include_dirs=[np.get_include()]), + Extension( + 'surprise.prediction_algorithms.co_clustering', + ['surprise/prediction_algorithms/co_clustering' + ext], include_dirs=[np.get_include()]), - Extension('surprise.prediction_algorithms.optimize_baselines', - ['surprise/prediction_algorithms/optimize_baselines' + ext], - include_dirs=[np.get_include()]), - Extension('surprise.prediction_algorithms.slope_one', - ['surprise/prediction_algorithms/slope_one' + ext], - include_dirs=[np.get_include()]), - Extension('surprise.prediction_algorithms.co_clustering', - ['surprise/prediction_algorithms/co_clustering' + ext], - include_dirs=[np.get_include()]), ] if USE_CYTHON: diff --git a/surprise/prediction_algorithms/matrix_factorization.pyx b/surprise/prediction_algorithms/matrix_factorization.pyx index 25f3d75d..60234faf 100644 --- a/surprise/prediction_algorithms/matrix_factorization.pyx +++ b/surprise/prediction_algorithms/matrix_factorization.pyx @@ -14,6 +14,10 @@ from .algo_base import AlgoBase from .predictions import PredictionImpossible from ..utils import get_rng +from libcpp.map cimport map as mapcpp +from libcpp.vector cimport vector as vectorcpp +from cython.operator import dereference, postincrement + class SVD(AlgoBase): """The famous *SVD* algorithm, as popularized by `Simon Funk @@ -441,25 +445,51 @@ class SVDpp(AlgoBase): (trainset.n_items, self.n_factors)) u_impl_fdb = np.zeros(self.n_factors, np.double) + cdef mapcpp[int, vectorcpp[int]] Iu_items + cdef mapcpp[int, double] Iu_len_sqrts + + for i in range(trainset.n_users): + for j, _ in trainset.ur[i]: + Iu_items[i].push_back(j) + + cdef mapcpp[int, vectorcpp[int]].iterator it = Iu_items.begin() + + while(it != Iu_items.end()): + Iu_len_sqrts[dereference(it).first] = 1.0 / np.sqrt(dereference(it).second.size()) + postincrement(it) + + cdef int facts = self.n_factors + + cdef double err_qif_sqrt = 0.0 + for current_epoch in range(self.n_epochs): if self.verbose: print(" processing epoch {}".format(current_epoch)) + for u, i, r in trainset.all_ratings(): - # items rated by u. This is COSTLY - Iu = [j for (j, _) in trainset.ur[u]] - sqrt_Iu = np.sqrt(len(Iu)) + # items rated by u. + Iu = Iu_items[u] + sqrt_Iu = Iu_len_sqrts[u] # compute user implicit feedback - u_impl_fdb = np.zeros(self.n_factors, np.double) - for j in Iu: - for f in range(self.n_factors): - u_impl_fdb[f] += yj[j, f] / sqrt_Iu + f = 0 + while f < facts: + u_impl_fdb[f] = 0 + f += 1 + for j in Iu: + f = 0 + while f < facts: + u_impl_fdb[f] += yj[j, f] * sqrt_Iu + f += 1 + # compute current error - dot = 0 # - for f in range(self.n_factors): + dot = 0 + f = 0 + while f < facts: dot += qi[i, f] * (pu[u, f] + u_impl_fdb[f]) + f += 1 err = r - (global_mean + bu[u] + bi[i] + dot) @@ -468,15 +498,16 @@ class SVDpp(AlgoBase): bi[i] += lr_bi * (err - reg_bi * bi[i]) # update factors - for f in range(self.n_factors): + f = 0 + while f < facts: puf = pu[u, f] qif = qi[i, f] pu[u, f] += lr_pu * (err * qif - reg_pu * puf) - qi[i, f] += lr_qi * (err * (puf + u_impl_fdb[f]) - - reg_qi * qif) + qi[i, f] += lr_qi * (err * (puf + u_impl_fdb[f]) - reg_qi * qif) + err_qif_sqrt = err * qif * sqrt_Iu for j in Iu: - yj[j, f] += lr_yj * (err * qif / sqrt_Iu - - reg_yj * yj[j, f]) + yj[j, f] += lr_yj * (err_qif_sqrt - reg_yj * yj[j, f]) + f += 1 self.bu = bu self.bi = bi