diff --git a/docs/source/user/iamadeveloper.rst b/docs/source/user/iamadeveloper.rst index 3e9daef70..0669507c7 100644 --- a/docs/source/user/iamadeveloper.rst +++ b/docs/source/user/iamadeveloper.rst @@ -279,6 +279,10 @@ Then, you could create the ``dataset`` object as follows: # Load the data into a dataset object dataset = cornac.data.Dataset.from_uir(data) +.. note:: + + Cornac also supports the UIRT format (user, item, rating, timestamp). + However, the this format is only for models that support timestamps. Training Models --------------- @@ -363,31 +367,91 @@ could be as follows: Saving a Trained Model ---------------------- -To save a trained model, you can use the ``save_dir`` parameter in the experiment. -For example, to save models from the experiment in the previous section, -we can do the following: +There are 2 ways to saved a trained model. You can either save the model +in an experiment, or manually save the model by code. -.. code-block:: python +.. dropdown:: Option 1: Saving all models in an Experiment + + To save the model in an experiment, add the ``save_dir`` parameter. + For example, to save models from the experiment in the previous section, + we can do the following: + + .. code-block:: python + + # Save all models in the experiment by adding + # the 'save_dir' parameter in the experiment + cornac.Experiment( + eval_method=rs, + models=models, + metrics=metrics, + user_based=True, + save_dir="saved_models" + ).run() + + This will save all trained models in the ``saved_models`` folder of where you + executed the python code. + + .. code-block:: bash + :caption: Folder directory + + - example.py + - saved_models + |- BPR + | |- yyyy-MM-dd HH:mm:ss.SSSSSS.pkl + |- PMF + |- yyyy-MM-dd HH:mm:ss.SSSSSS.pkl + +.. dropdown:: Option 2: Saving the model individually + + To save the model individually, you can use the ``save()`` method. + + .. code-block:: python + + # Instantiate the BPR model + model = BPR(k=10, max_iter=100, learning_rate=0.01, lambda_reg=0.01, seed=123) + + # Use the fit() function to train the model + model.fit(dataset) + + # Save the trained model + model.save(save_dir="saved_models") + + This will save the trained model in the ``saved_models`` folder of where you + executed the python code. + + .. code-block:: bash + :caption: Folder directory - # Save the trained model - cornac.Experiment(eval_method=rs, models=models, metrics=metrics, user_based=True, save_dir="saved_models").run() + - example.py + - saved_models + |- BPR + |- yyyy-MM-dd HH:mm:ss.SSSSSS.pkl -This will save the trained models in the ``saved_models`` folder of where you -executed the python code. + +Loading a Trained Model +----------------------- + +To load a trained model, you can use the ``load()`` function. You could either +load a folder containing .pkl files, or load a specific .pkl file. .. code-block:: bash :caption: Folder directory - + - example.py - saved_models |- BPR |- yyyy-MM-dd HH:mm:ss.SSSSSS.pkl +Option 1: By loading a folder containing multiple .pkl files, Cornac would pick +the latest .pkl file in the folder. -Loading a Trained Model ------------------------ +.. code-block:: python + + # Load the trained model + model = BPR.load("saved_models/BPR/") -To load a trained model, you can use the ``load()`` function. +Option 2: By loading a specific .pkl file, Cornac would load the specific +model indicated. .. code-block:: python @@ -426,6 +490,35 @@ obtain recommendations for users. recs = model.recommend(user_id="U1") print(recs) +Running an API Service +---------------------- + +Cornac also provides an API service that you can use to run your own +recommendation service. This is useful if you want to build a recommendation +system for your own application. + +.. code-block:: bash + + python -m cornac.serving --model_dir save_dir/BPR --model_class cornac.models.BPR + +This will serve an API for the model saved in the directory ``save_dir/BPR``. + +To obtain a recommendation, do a call to the API endpoint ``/recommend`` with +the following parameters: + +- ``uid``: The user ID to obtain recommendations for +- ``k``: The number of recommendations to obtain + +.. code-block:: bash + + curl http://127.0.0.1:8080/recommend \ + --request POST \ + --header "Content-Type: application/json" \ + --data '{"uid":"63", "k": 5}' + + # Response: {"recommendations": ["50", "181", "100", "258", "286"], "data_received": {"uid": "63", "k": 5}} + + What's Next? ------------ diff --git a/docs/source/user/iamaresearcher.rst b/docs/source/user/iamaresearcher.rst index 66d09e45a..c25bddd1f 100644 --- a/docs/source/user/iamaresearcher.rst +++ b/docs/source/user/iamaresearcher.rst @@ -36,7 +36,7 @@ View the models, datasets, metrics that are currently built into Cornac: - :doc:`/api_ref/datasets` - :doc:`/api_ref/metrics` -Create experiments +Perform experiments ------------------ Cornac provides a set of tools to help you create experiments. The main tool is @@ -55,7 +55,7 @@ and the Precision, Recall evaluation metrics. import cornac from cornac.eval_methods import RatioSplit from cornac.models import BPR, PMF - from cornac.metrics import RMSE, Precision, Recall + from cornac.metrics import Precision, Recall # Load a sample dataset (e.g., MovieLens) ml_100k = cornac.datasets.movielens.load_feedback() @@ -70,7 +70,7 @@ and the Precision, Recall evaluation metrics. ] # Define metrics to evaluate the models - metrics = [RMSE(), Precision(k=10), Recall(k=10)] + metrics = [Precision(k=10), Recall(k=10)] # put it together in an experiment, voilĂ ! cornac.Experiment(eval_method=rs, models=models, metrics=metrics, user_based=True).run() @@ -285,85 +285,327 @@ dataset. Adding your Own Model --------------------- -In order to add your own model, these are the files in which you have to create: +Adding your own model on Cornac is easy. Cornac is designed to be flexible and +extensible, allowing researchers to easily add their own models into the +framework. -These are the files to be added: +Below is an example of the ``PMF`` model which was already added into Cornac. +We will use this as a reference to add our own model. .. code-block:: bash cornac |-- cornac | |-- models - | |-- mymodel + | |-- pmf | |-- __init__.py - | |-- recom_mymodel.py + | |-- recom_pmf.py |-- examples - |-- mymodel_example.py + |-- pmf_ratio.py + +.. dropdown:: 1. Create the base folder for your model + + .. code-block:: bash -.. dropdown:: View models/mymodel/__init__.py + cornac + |-- cornac + |-- models + |-- pmf + +.. dropdown:: 2. Create the ``__init__.py`` file in the ``pmf`` folder + + Add the following line to the ``__init__.py`` file in your model folder. + The ``.recom_pmf`` coincides with the name of the file that contains the + model, and ``PMF`` coincides with the name of the class in the + ``recon_pmf`` file. .. code-block:: python - :caption: cornac/cornac/models/mymodel/__init__.py + :caption: cornac/cornac/models/pmf/__init__.py + + from .recom_pmf import PMF + + +.. dropdown:: 3. Create the ``recom_pmf.py`` file in the ``pmf`` folder + + The ``recom_pmf.py`` file contains the logic of the model. This includes + the training and testing portions of the model. + + Core to the ``recom_pmf.py`` file is the ``PMF`` class. This class inherits + from the :class:`~cornac.models.Recommender` class. The ``PMF`` class + implements the following methods: + + - :meth:`~cornac.models.Recommender.__init__`: The constructor of the class + - :meth:`~cornac.models.Recommender.fit`: The training procedure of the model + - :meth:`~cornac.models.Recommender.score`: The scoring function of the model + + .. code-block:: python + :caption: __init__ method: The constructor + + # Here we initialize parameters and variables + + def __init__( + self, + k=5, + max_iter=100, + learning_rate=0.001, + gamma=0.9, + lambda_reg=0.001, + name="PMF", + variant="non_linear", + trainable=True, + verbose=False, + init_params=None, + seed=None, + ): + Recommender.__init__(self, name=name, trainable=trainable, verbose=verbose) + self.k = k + self.max_iter = max_iter + self.learning_rate = learning_rate + self.gamma = gamma + self.lambda_reg = lambda_reg + self.variant = variant + self.seed = seed + + self.ll = np.full(max_iter, 0) + self.eps = 0.000000001 + + # Init params if provided + self.init_params = {} if init_params is None else init_params + self.U = self.init_params.get("U", None) # matrix of user factors + self.V = self.init_params.get("V", None) # matrix of item factors + + .. code-block:: python + :caption: fit method: The training procedure + + # Here we implement the training procedure of the model + + def fit(self, train_set, val_set=None): + """Fit the model to observations. + + Parameters + ---------- + train_set: :obj:`cornac.data.Dataset`, required + User-Item preference data as well as additional modalities. + + val_set: :obj:`cornac.data.Dataset`, optional, default: None + User-Item preference data for model selection purposes (e.g., early stopping). + + Returns + ------- + self : object + """ + Recommender.fit(self, train_set) + + from cornac.models.pmf import pmf + + if self.trainable: + # converting data to the triplet format (needed for cython function pmf) + (uid, iid, rat) = train_set.uir_tuple + rat = np.array(rat, dtype="float32") + if self.variant == "non_linear": # need to map the ratings to [0,1] + if [self.min_rating, self.max_rating] != [0, 1]: + rat = scale(rat, 0.0, 1.0, self.min_rating, self.max_rating) + uid = np.array(uid, dtype="int32") + iid = np.array(iid, dtype="int32") + + if self.verbose: + print("Learning...") + + # use pre-trained params if exists, otherwise from constructor + init_params = {"U": self.U, "V": self.V} + + if self.variant == "linear": + res = pmf.pmf_linear( + uid, + iid, + rat, + k=self.k, + n_users=self.num_users, + n_items=self.num_items, + n_ratings=len(rat), + n_epochs=self.max_iter, + lambda_reg=self.lambda_reg, + learning_rate=self.learning_rate, + gamma=self.gamma, + init_params=init_params, + verbose=self.verbose, + seed=self.seed, + ) + elif self.variant == "non_linear": + res = pmf.pmf_non_linear( + uid, + iid, + rat, + k=self.k, + n_users=self.num_users, + n_items=self.num_items, + n_ratings=len(rat), + n_epochs=self.max_iter, + lambda_reg=self.lambda_reg, + learning_rate=self.learning_rate, + gamma=self.gamma, + init_params=init_params, + verbose=self.verbose, + seed=self.seed, + ) + else: + raise ValueError('variant must be one of {"linear","non_linear"}') + + self.U = np.asarray(res["U"]) + self.V = np.asarray(res["V"]) + + if self.verbose: + print("Learning completed") + + elif self.verbose: + print("%s is trained already (trainable = False)" % (self.name)) + + return self + + .. code-block:: python + :caption: score method: The scoring function + + # Here we implement the scoring function of the model. + # If item-idx is not provided, return scores for all known items + # of the users. Otherwise, return the score of the user-item pair + + def score(self, user_idx, item_idx=None): + """Predict the scores/ratings of a user for an item. + + Parameters + ---------- + user_idx: int, required + The index of the user for whom to perform score prediction. + + item_idx: int, optional, default: None + The index of the item for which to perform score prediction. + If None, scores for all known items will be returned. - from .recom_mymodel import MyModel + Returns + ------- + res : A scalar or a Numpy array + Relative scores that the user gives to the item or to all known items -.. dropdown:: View models/mymodel/recom_mymodel.py + """ + 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) or 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, :]) + + if self.variant == "non_linear": + user_pred = sigmoid(user_pred) + user_pred = scale(user_pred, self.min_rating, self.max_rating, 0.0, 1.0) + + return user_pred + + Putting everything together, below we have the whole recom_pmf.py file: .. code-block:: python - :caption: cornac/cornac/models/mymodel/recom_mymodel.py + :caption: cornac/cornac/models/pmf/recom_pmf.py + + import numpy as np from ..recommender import Recommender + from ...utils.common import sigmoid + from ...utils.common import scale + from ...exception import ScoreException - # Your model must extend the Recommender class - class MyModel(Recommender): - """Documentation for My New Model. + + class PMF(Recommender): + """Probabilistic Matrix Factorization. Parameters ---------- - name: string, optional, default: 'MyModel' - Name of the recommender model. + k: int, optional, default: 5 + The dimension of the latent factors. - num_epochs: int, optional, default: 20 - Number of epochs. + max_iter: int, optional, default: 100 + Maximum number of iterations or the number of epochs for SGD. - batch_size: int, optional, default: 256 - Batch size. + learning_rate: float, optional, default: 0.001 + The learning rate for SGD_RMSProp. + + gamma: float, optional, default: 0.9 + The weight for previous/current gradient in RMSProp. - early_stopping: {min_delta: float, patience: int}, optional, default: None - If `None`, no early stopping. Meaning of the arguments: + lambda_reg: float, optional, default: 0.001 + The regularization coefficient. + + name: string, optional, default: 'PMF' + The name of the recommender model. - - `min_delta`: the minimum increase in monitored value on validation set to be considered as improvement, \ - i.e. an increment of less than min_delta will count as no improvement. - - `patience`: number of epochs with no improvement after which training should be stopped. + variant: {"linear","non_linear"}, optional, default: 'non_linear' + Pmf variant. If 'non_linear', the Gaussian mean is the output of a Sigmoid function.\ + If 'linear' the Gaussian mean is the output of the identity function. trainable: boolean, optional, default: True - When False, the model is not trained and Cornac assumes that the model is already \ - pre-trained. - + When False, the model is not trained and Cornac assumes that the model already \ + pre-trained (U and V are not None). + verbose: boolean, optional, default: False When True, some running logs are displayed. + init_params: dict, optional, default: None + List of initial parameters, e.g., init_params = {'U':U, 'V':V}. + + U: ndarray, shape (n_users, k) + User latent factors. + + V: ndarray, shape (n_items, k) + Item latent factors. + + seed: int, optional, default: None + Random seed for parameters initialization. + References ---------- - * Add your references here. + * Mnih, Andriy, and Ruslan R. Salakhutdinov. Probabilistic matrix factorization. \ + In NIPS, pp. 1257-1264. 2008. """ def __init__( self, - name="MyModel", - num_epochs=20, - batch_size=256, - early_stopping=None, + k=5, + max_iter=100, + learning_rate=0.001, + gamma=0.9, + lambda_reg=0.001, + name="PMF", + variant="non_linear", trainable=True, - verbose=True, + verbose=False, + init_params=None, seed=None, ): - super().__init__(name=name, trainable=trainable, verbose=verbose) - self.num_epochs = num_epochs - self.batch_size = batch_size - self.early_stopping = early_stopping + Recommender.__init__(self, name=name, trainable=trainable, verbose=verbose) + self.k = k + self.max_iter = max_iter + self.learning_rate = learning_rate + self.gamma = gamma + self.lambda_reg = lambda_reg + self.variant = variant self.seed = seed + self.ll = np.full(max_iter, 0) + self.eps = 0.000000001 + + # Init params if provided + self.init_params = {} if init_params is None else init_params + self.U = self.init_params.get("U", None) # matrix of user factors + self.V = self.init_params.get("V", None) # matrix of item factors + def fit(self, train_set, val_set=None): """Fit the model to observations. @@ -379,55 +621,74 @@ These are the files to be added: ------- self : object """ - Recommender.fit(self, train_set, val_set) + Recommender.fit(self, train_set) + + from cornac.models.pmf import pmf if self.trainable: - # do model training here - pass + # converting data to the triplet format (needed for cython function pmf) + (uid, iid, rat) = train_set.uir_tuple + rat = np.array(rat, dtype="float32") + if self.variant == "non_linear": # need to map the ratings to [0,1] + if [self.min_rating, self.max_rating] != [0, 1]: + rat = scale(rat, 0.0, 1.0, self.min_rating, self.max_rating) + uid = np.array(uid, dtype="int32") + iid = np.array(iid, dtype="int32") + + if self.verbose: + print("Learning...") + + # use pre-trained params if exists, otherwise from constructor + init_params = {"U": self.U, "V": self.V} + + if self.variant == "linear": + res = pmf.pmf_linear( + uid, + iid, + rat, + k=self.k, + n_users=self.num_users, + n_items=self.num_items, + n_ratings=len(rat), + n_epochs=self.max_iter, + lambda_reg=self.lambda_reg, + learning_rate=self.learning_rate, + gamma=self.gamma, + init_params=init_params, + verbose=self.verbose, + seed=self.seed, + ) + elif self.variant == "non_linear": + res = pmf.pmf_non_linear( + uid, + iid, + rat, + k=self.k, + n_users=self.num_users, + n_items=self.num_items, + n_ratings=len(rat), + n_epochs=self.max_iter, + lambda_reg=self.lambda_reg, + learning_rate=self.learning_rate, + gamma=self.gamma, + init_params=init_params, + verbose=self.verbose, + seed=self.seed, + ) + else: + raise ValueError('variant must be one of {"linear","non_linear"}') + + self.U = np.asarray(res["U"]) + self.V = np.asarray(res["V"]) + + if self.verbose: + print("Learning completed") + + elif self.verbose: + print("%s is trained already (trainable = False)" % (self.name)) return self - # this function is used for early stopping - def monitor_value(self, train_set, val_set): - """Calculating monitored value used for early stopping on validation set (`val_set`). - This function will be called by `early_stop()` function. - - Parameters - ---------- - train_set: :obj:`cornac.data.Dataset`, required - User-Item preference data as well as additional modalities. - - val_set: :obj:`cornac.data.Dataset`, optional, default: None - User-Item preference data for model selection purposes (e.g., early stopping). - - Returns - ------- - res : float - Monitored value on validation set. - Return `None` if `val_set` is `None`. - """ - if val_set is None: - return None - - from ...metrics import NDCG - from ...eval_methods import ranking_eval - - # add the metrics to use for early stopping comparison. - # values are tracked and compared every epoch - - # in this example we use ndcg as the early stopping - # function. - - ndcg_100 = ranking_eval( - model=self, - metrics=[NDCG(k=100)], - train_set=train_set, - test_set=val_set, - )[0][0] - - return ndcg_100 - - # this function is used for score prediction def score(self, user_idx, item_idx=None): """Predict the scores/ratings of a user for an item. @@ -444,134 +705,94 @@ These are the files to be added: ------- res : A scalar or a Numpy array Relative scores that the user gives to the item or to all known items - """ - # return the score from your model, given the user_idx and/or item_idx + """ if item_idx is None: - # return all scores - return [0.0] - - return 0.0 + 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) or 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, :]) -.. dropdown:: View examples/mymodel_example.py + if self.variant == "non_linear": + user_pred = sigmoid(user_pred) + user_pred = scale(user_pred, self.min_rating, self.max_rating, 0.0, 1.0) + + return user_pred + + +.. dropdown:: 4. Create the example file in the ``examples`` folder .. code-block:: python - :caption: cornac/examples/mymodel_example.py + :caption: cornac/examples/pmf_ratio.py + """Example to run Probabilistic Matrix Factorization (PMF) model with Ratio Split evaluation strategy""" + import cornac - from cornac.eval_methods import RatioSplit from cornac.datasets import movielens - from cornac.models.mymodel import MyModel + from cornac.eval_methods import RatioSplit + from cornac.models import PMF - # Load the dataset - feedback = movielens.load_feedback(variant="100K") + # Load the MovieLens 100K dataset + ml_100k = movielens.load_feedback() - # Define an evaluation method to split feedback into train and test sets + # Instantiate an evaluation method. ratio_split = RatioSplit( - data=feedback, - test_size=0.2, - rating_threshold=1.0, - seed=123, - exclude_unknowns=True, - verbose=True, + data=ml_100k, test_size=0.2, rating_threshold=4.0, exclude_unknowns=False ) - # Instantiate the recommender models to be compared - mymodel = MyModel( - name="MyModel", - num_epochs=20, - batch_size=256, - early_stopping=None, - trainable=True, - verbose=True, - ) + # Instantiate a PMF recommender model. + pmf = PMF(k=10, max_iter=100, learning_rate=0.001, lambda_reg=0.001) - # Instantiate evaluation metrics - ndcg_50 = cornac.metrics.NDCG(k=50) - rec_50 = cornac.metrics.Recall(k=50) + # Instantiate evaluation metrics. + mae = cornac.metrics.MAE() + rmse = cornac.metrics.RMSE() + rec_20 = cornac.metrics.Recall(k=20) + pre_20 = cornac.metrics.Precision(k=20) - # Put everything together into an experiment and run it + # Instantiate and then run an experiment. cornac.Experiment( eval_method=ratio_split, - models=[mymodel], - metrics=[ndcg_50, rec_50], + models=[pmf], + metrics=[mae, rmse, rec_20, pre_20], + user_based=True, ).run() - -These are the files to be edited: +To add your model to the overall Cornac package, you need to edit the following +file: .. code-block:: bash cornac |-- cornac - | |-- models - | |-- __init__.py + |-- models + |-- __init__.py -.. dropdown:: View models/__init__.py +.. dropdown:: Edit the models/__init__.py .. code-block:: python + :caption: cornac/cornac/models/__init__.py - from .vbpr import VBPR - ... # other models removed for brevity - from .vmf import VMF - from .wmf import WMF - from .mymodel import MyModel # Add this line - -The bulk of the omdel goes into the ``recom_mymodel.py`` file. -While it is possible to leave all your logic and training/testing -into the same file, it is generally recommended that you - -you need to create a class that inherits from -the :class:`~cornac.models.Recommender` class. The class must implement the -following methods: - -- :meth:`~cornac.models.Recommender.__init__` -- :meth:`~cornac.models.Recommender.fit` -- :meth:`~cornac.models.Recommender.score` - -Let's say we are implementing a new model called `MyModel`. The following code -snippet shows how to implement the `MyModel` class: - -.. code-block:: python - - import numpy as np - import cornac - - class MyModel(cornac.models.Recommender): - def __init__(self, name="MyModel", k=10, max_iter=100, learning_rate=0.01, lambda_reg=0.01, seed=None): - super().__init__(name=name, trainable=True, verbose=0) - self.k = k - self.max_iter = max_iter - self.learning_rate = learning_rate - self.lambda_reg = lambda_reg - self.seed = seed - - def fit(self, train_set): - # do something here - return self - - def score(self, user_idx, item_idx): - # do something here - return 0.0 - -In the `fit` function, you need to implement the training procedure of your model. -In the `score` function, you need to implement the scoring function of your model. -The `score` function will be used to compute the predicted scores of the model -for each user-item pair in the testing set. - -In order to test your model, you first have to create an example -(preferably in the examples folder). The example should contain the following -steps: + from .amr import AMR + ... # models removed for brevity + from .pmf import PMF # Add this line + ... # models removed for brevity -1. Load a dataset -2. Split the dataset into training and testing sets -3. Instantiate your model -4. Fit and do an experiment with the model - -However, to make changes to the model, you need to rebuild Cornac. We will -discuss this in the next section. +Now you have implemented your model, it is time to test it. +In order to do so, you have to rebuild Cornac. We will discuss on how to do +this in the next section. Development Workflow -------------------- @@ -630,6 +851,105 @@ To do so, simply add you model to the list of models in the experiment. # run the experiment and compare the results cornac.Experiment(eval_method=rs, models=models, metrics=metrics, user_based=True).run() +Using Additional packages +------------------------- + +Cornac is built on top of the popular scientific computing libraries such as +NumPy, SciPy, and scikit-learn. It is also designed to be compatible with the +popular deep learning libraries such as PyTorch and TensorFlow. + +If you are using additional packages in your model, you can add them into the +``requirements.txt`` file. This will ensure that the packages are installed + +.. code-block:: bash + + cornac + |-- cornac + | |-- models + | |-- ngcf + | | |-- __init__.py + | | |-- recom_ngcf.py + | |-- requirements.txt <-- Add this file + |-- examples + |-- ngcf_example.py + +Your requirements.txt file should look like this: + +.. code-block:: bash + :caption: cornac/cornac/models/ngcf/requirements.txt + + torch>=2.0.0 + dgl>=1.1.0 + +This is generated by doing a ``pip freeze > requirements.txt`` command on your +environment. + +Model file structure +^^^^^^^^^^^^^^^^^^^^ + +Your model file should have special dependencies imported only in the +fit/score functions. This is to ensure that Cornac can be built without +installing the additional packages. + +For example, in the code snippet below from the ``NGCF`` model, the ``fit`` +function imports the ``torch`` package. This is to ensure that the ``torch`` +package is only imported when the ``fit`` function is called. + +.. code-block:: python + :caption: cornac/cornac/models/ngcf/recom_ngcf.py + + def fit(self, train_set, val_set=None): + """Fit the model to observations. + + Parameters + ---------- + train_set: :obj:`cornac.data.Dataset`, required + User-Item preference data as well as additional modalities. + + val_set: :obj:`cornac.data.Dataset`, optional, default: None + User-Item preference data for model selection purposes (e.g., early stopping). + + Returns + ------- + self : object + """ + Recommender.fit(self, train_set, val_set) + + if not self.trainable: + return self + + # model setup + import torch + from .ngcf import Model + from .ngcf import construct_graph + + self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + if self.seed is not None: + torch.manual_seed(self.seed) + if torch.cuda.is_available(): + torch.cuda.manual_seed_all(self.seed) + + graph = construct_graph(train_set, self.total_users, self.total_items).to(self.device) + model = Model( + graph, + self.emb_size, + self.layer_sizes, + self.dropout_rates, + self.lambda_reg, + ).to(self.device) + + # remaining codes removed for brevity + +Adding a New Metric +------------------- + +Cornac provides a wide range of evaluation metrics for you to use. However, if +you would like to add your own metric, you can do so by extending the +:class:`~cornac.metrics.Metric` class. + +View the add metric tutorial at +https://github.com/PreferredAI/cornac/blob/master/tutorials/add_metric.md. + Conclusion ---------- We hope you find Cornac useful for your research. Please share with us on how