Skip to content

Commit

Permalink
Support ANN search for existing models (#549)
Browse files Browse the repository at this point in the history
  • Loading branch information
tqtg committed Nov 30, 2023
1 parent e91bb15 commit 678399a
Show file tree
Hide file tree
Showing 38 changed files with 1,214 additions and 441 deletions.
35 changes: 34 additions & 1 deletion cornac/models/amr/recom_amr.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@
from tqdm.auto import tqdm

from ..recommender import Recommender
from ..recommender import ANNMixin, MEASURE_DOT
from ...exception import CornacException
from ...utils import fast_dot
from ...utils import get_rng
from ...utils.init_utils import xavier_uniform


class AMR(Recommender):
class AMR(Recommender, ANNMixin):
"""Adversarial Training Towards Robust Multimedia Recommender System.
Parameters
Expand Down Expand Up @@ -286,3 +287,35 @@ def score(self, user_idx, item_idx=None):
item_score = np.dot(self.gamma_item[item_idx], self.gamma_user[user_idx])
item_score += np.dot(self.theta_item[item_idx], self.gamma_user[user_idx])
return item_score

def get_vector_measure(self):
"""Getting a valid choice of vector measurement in ANNMixin._measures.
Returns
-------
measure: MEASURE_DOT
Dot product aka. inner product
"""
return MEASURE_DOT

def get_user_vectors(self):
"""Getting a matrix of user vectors serving as query for ANN search.
Returns
-------
out: numpy.array
Matrix of user vectors for all users available in the model.
"""
user_vectors = np.concatenate((self.gamma_user, self.gamma_user), axis=1)
return user_vectors

def get_item_vectors(self):
"""Getting a matrix of item vectors used for building the index for ANN search.
Returns
-------
out: numpy.array
Matrix of item vectors for all items available in the model.
"""
item_vectors = np.concatenate((self.gamma_item, self.theta_item), axis=1)
return item_vectors
59 changes: 42 additions & 17 deletions cornac/models/bivaecf/recom_bivaecf.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@
import numpy as np

from ..recommender import Recommender
from ..recommender import ANNMixin, MEASURE_DOT
from ...utils.common import scale
from ...exception import ScoreException


class BiVAECF(Recommender):
class BiVAECF(Recommender, ANNMixin):
"""Bilateral Variational AutoEncoder for Collaborative Filtering.
Parameters
Expand Down Expand Up @@ -160,7 +161,7 @@ def fit(self, train_set, val_set=None):
torch.manual_seed(self.seed)
torch.cuda.manual_seed(self.seed)

if not hasattr(self, "bivaecf"):
if not hasattr(self, "bivae"):
num_items = train_set.matrix.shape[1]
num_users = train_set.matrix.shape[0]
self.bivae = BiVAE(
Expand Down Expand Up @@ -207,26 +208,50 @@ def score(self, user_idx, item_idx=None):
Relative scores that the user gives to the item or to all known items
"""
if self.is_unknown_user(user_idx):
raise ScoreException("Can't make score prediction for user %d" % user_idx)

if item_idx is not None and self.is_unknown_item(item_idx):
raise ScoreException("Can't make score prediction for item %d" % item_idx)

if item_idx is None:
if not self.knows_user(user_idx):
raise ScoreException(
"Can't make score prediction for (user_id=%d)" % user_idx
)
theta_u = self.bivae.mu_theta[user_idx].view(1, -1)
beta = self.bivae.mu_beta
known_item_scores = (
self.bivae.decode_user(theta_u, beta).cpu().numpy().ravel()
)
return known_item_scores
return self.bivae.decode_user(theta_u, beta).cpu().numpy().ravel()
else:
if not (self.knows_user(user_idx) and self.knows_item(item_idx)):
raise ScoreException(
"Can't make score prediction for (user_id=%d, item_id=%d)"
% (user_idx, item_idx)
)
theta_u = self.bivae.mu_theta[user_idx].view(1, -1)
beta_i = self.bivae.mu_beta[item_idx].view(1, -1)
pred = self.bivae.decode_user(theta_u, beta_i).cpu().numpy().ravel()
pred = scale(pred, self.min_rating, self.max_rating, 0.0, 1.0)
return pred
return scale(pred, self.min_rating, self.max_rating, 0.0, 1.0)

def get_vector_measure(self):
"""Getting a valid choice of vector measurement in ANNMixin._measures.
Returns
-------
measure: MEASURE_DOT
Dot product aka. inner product
"""
return MEASURE_DOT

def get_user_vectors(self):
"""Getting a matrix of user vectors serving as query for ANN search.
Returns
-------
out: numpy.array
Matrix of user vectors for all users available in the model.
"""
user_vectors = self.bivae.mu_theta.detach().cpu().numpy()
return user_vectors

def get_item_vectors(self):
"""Getting a matrix of item vectors used for building the index for ANN search.
Returns
-------
out: numpy.array
Matrix of item vectors for all items available in the model.
"""
item_vectors = self.bivae.mu_beta.detach().cpu().numpy()
return item_vectors
41 changes: 39 additions & 2 deletions cornac/models/bpr/recom_bpr.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ cimport numpy as np
from tqdm.auto import trange

from ..recommender import Recommender
from ..recommender import ANNMixin, MEASURE_DOT
from ...exception import ScoreException
from ...utils import get_rng
from ...utils import fast_dot
Expand Down Expand Up @@ -60,7 +61,7 @@ cdef class RNGVector(object):



class BPR(Recommender):
class BPR(Recommender, ANNMixin):
"""Bayesian Personalized Ranking.
Parameters
Expand Down Expand Up @@ -292,4 +293,40 @@ class BPR(Recommender):
else:
item_score = self.i_biases[item_idx]
item_score += np.dot(self.u_factors[user_idx], self.i_factors[item_idx])
return item_score
return item_score

def get_vector_measure(self):
"""Getting a valid choice of vector measurement in ANNMixin._measures.
Returns
-------
measure: MEASURE_DOT
Dot product aka. inner product
"""
return MEASURE_DOT

def get_user_vectors(self):
"""Getting a matrix of user vectors serving as query for ANN search.
Returns
-------
out: numpy.array
Matrix of user vectors for all users available in the model.
"""
user_vectors = np.concatenate(
(self.u_factors, np.ones([self.u_factors.shape[0], 1])), axis=1
)
return user_vectors

def get_item_vectors(self):
"""Getting a matrix of item vectors used for building the index for ANN search.
Returns
-------
out: numpy.array
Matrix of item vectors for all items available in the model.
"""
item_vectors = np.concatenate(
(self.i_factors, self.i_biases.reshape((-1, 1))), axis=1
)
return item_vectors
41 changes: 40 additions & 1 deletion cornac/models/c2pf/recom_c2pf.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@

from cornac.models.c2pf import c2pf
from ..recommender import Recommender
from ..recommender import ANNMixin, MEASURE_DOT


# Recommender class for Collaborative Context Poisson Factorization (C2PF)
class C2PF(Recommender):
class C2PF(Recommender, ANNMixin):
"""Collaborative Context Poisson Factorization.
Parameters
Expand Down Expand Up @@ -295,3 +296,41 @@ def score(self, user_idx, item_idx=None):
# transform user_pred to a flatten array,
user_pred = np.array(user_pred, dtype="float64").flatten()
return user_pred

def get_vector_measure(self):
"""Getting a valid choice of vector measurement in ANNMixin._measures.
Returns
-------
measure: MEASURE_DOT
Dot product aka. inner product
"""
return MEASURE_DOT

def get_user_vectors(self):
"""Getting a matrix of user vectors serving as query for ANN search.
Returns
-------
out: numpy.array
Matrix of user vectors for all users available in the model.
"""
if self.variant == "rc2pf":
user_vectors = np.concatenate((self.Theta, self.Theta), axis=1)
else:
user_vectors = self.Theta
return user_vectors

def get_item_vectors(self):
"""Getting a matrix of item vectors used for building the index for ANN search.
Returns
-------
out: numpy.array
Matrix of item vectors for all items available in the model.
"""
if self.variant == "rc2pf":
item_vectors = np.concatenate((self.Beta, self.Xi), axis=1)
else:
item_vectors = self.Beta
return item_vectors
56 changes: 41 additions & 15 deletions cornac/models/cdl/recom_cdl.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@
from tqdm.auto import trange

from ..recommender import Recommender
from ..recommender import ANNMixin, MEASURE_DOT
from ...exception import ScoreException
from ...utils import get_rng
from ...utils.init_utils import xavier_uniform


class CDL(Recommender):
class CDL(Recommender, ANNMixin):
"""Collaborative Deep Learning.
Parameters
Expand Down Expand Up @@ -280,18 +281,43 @@ def score(self, user_idx, item_idx=None):
res : A scalar or a Numpy array
Relative scores that the user gives to the item or to all known items
"""
if self.is_unknown_user(user_idx):
raise ScoreException("Can't make score prediction for user %d" % user_idx)

if item_idx is not None and self.is_unknown_item(item_idx):
raise ScoreException("Can't make score prediction for item %d" % item_idx)

if item_idx is None:
if not self.knows_user(user_idx):
raise ScoreException(
"Can't make score prediction for (user_id=%d)" % user_idx
)
known_item_scores = self.V.dot(self.U[user_idx, :])
return known_item_scores
else:
if not (self.knows_user(user_idx) and not self.knows_item(item_idx)):
raise ScoreException(
"Can't make score prediction for (user_id=%d, item_id=%d)"
% (user_idx, item_idx)
)
user_pred = self.V[item_idx, :].dot(self.U[user_idx, :])
return user_pred
return self.V.dot(self.U[user_idx, :])

return self.V[item_idx, :].dot(self.U[user_idx, :])

def get_vector_measure(self):
"""Getting a valid choice of vector measurement in ANNMixin._measures.
Returns
-------
measure: MEASURE_DOT
Dot product aka. inner product
"""
return MEASURE_DOT

def get_user_vectors(self):
"""Getting a matrix of user vectors serving as query for ANN search.
Returns
-------
out: numpy.array
Matrix of user vectors for all users available in the model.
"""
return self.U

def get_item_vectors(self):
"""Getting a matrix of item vectors used for building the index for ANN search.
Returns
-------
out: numpy.array
Matrix of item vectors for all items available in the model.
"""
return self.V

0 comments on commit 678399a

Please sign in to comment.