diff --git a/README.md b/README.md index aa08344e6..869620a17 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,7 @@ One important aspect of deploying recommender model is efficient retrieval via A | Supported framework | Cornac wrapper | Examples | | :---: | :---: | :---: | +| [meta/faiss](https://github.com/facebookresearch/faiss) | [FaissANN](cornac/models/ann/recom_ann_faiss.py) | [ann_all.ipynb](examples/ann_all.ipynb) | [nmslib/hnswlib](https://github.com/nmslib/hnswlib) | [HNSWLibANN](cornac/models/ann/recom_ann_hnswlib.py) | [ann_hnswlib.ipynb](tutorials/ann_hnswlib.ipynb), [ann_all.ipynb](examples/ann_all.ipynb) | [google/scann](https://github.com/google-research/google-research/tree/master/scann) | [ScaNNANN](cornac/models/ann/recom_ann_scann.py) | [ann_all.ipynb](examples/ann_all.ipynb) diff --git a/cornac/models/__init__.py b/cornac/models/__init__.py index fb316f330..54dc01389 100644 --- a/cornac/models/__init__.py +++ b/cornac/models/__init__.py @@ -17,6 +17,7 @@ from .recommender import NextBasketRecommender from .amr import AMR +from .ann import FaissANN from .ann import HNSWLibANN from .ann import ScaNNANN from .baseline_only import BaselineOnly diff --git a/cornac/models/ann/__init__.py b/cornac/models/ann/__init__.py index 77fd8630b..a5d1e1b10 100644 --- a/cornac/models/ann/__init__.py +++ b/cornac/models/ann/__init__.py @@ -1,2 +1,3 @@ +from .recom_ann_faiss import FaissANN from .recom_ann_hnswlib import HNSWLibANN from .recom_ann_scann import ScaNNANN diff --git a/cornac/models/ann/recom_ann_faiss.py b/cornac/models/ann/recom_ann_faiss.py new file mode 100644 index 000000000..f30521195 --- /dev/null +++ b/cornac/models/ann/recom_ann_faiss.py @@ -0,0 +1,153 @@ +# Copyright 2023 The Cornac Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + + +import multiprocessing +import numpy as np + +from ..recommender import MEASURE_L2, MEASURE_DOT, MEASURE_COSINE +from .recom_ann_base import BaseANN + + +class FaissANN(BaseANN): + """Approximate Nearest Neighbor Search with Faiss (https://github.com/facebookresearch/faiss). + Faiss provides both CPU and GPU implementation. More on the algorithms: + https://github.com/facebookresearch/faiss/wiki + + Parameters + ---------------- + model: object: :obj:`cornac.models.Recommender`, required + Trained recommender model which to get user/item vectors from. + + nlist: int, default: 100 + The number of cells used for building the index. + + nprobe: int, default: 50 + The number of cells (out of nlist) that are visited to perform a search. + + use_gpu : bool, optional + Whether or not to run Faiss on GPU. Requires faiss-gpu to be installed + instead of faiss-cpu. + + num_threads: int, optional, default: -1 + Default number of threads used for building index. If num_threads = -1, + all cores will be used. + + seed: int, optional, default: None + Random seed for reproducibility. + + name: str, required + Name of the recommender model. + + verbose: boolean, optional, default: False + When True, running logs are displayed. + """ + + def __init__( + self, + model, + nlist=100, + nprobe=50, + use_gpu=False, + num_threads=-1, + seed=None, + name="FaissANN", + verbose=False, + ): + super().__init__(model=model, name=name, verbose=verbose) + + self.model = model + self.nlist = nlist + self.nprobe = nprobe + self.use_gpu = use_gpu + self.num_threads = ( + num_threads if num_threads != -1 else multiprocessing.cpu_count() + ) + self.seed = seed + + self.index = None + self.ignored_attrs.extend( + [ + "index", # will be saved separately + "item_vectors", # redundant after index is built + ] + ) + + def build_index(self): + """Building index from the base recommender model.""" + import faiss + + faiss.omp_set_num_threads(self.num_threads) + + SUPPORTED_MEASURES = { + MEASURE_L2: faiss.METRIC_L2, + MEASURE_DOT: faiss.METRIC_INNER_PRODUCT, + MEASURE_COSINE: faiss.METRIC_INNER_PRODUCT, + } + + assert self.measure in SUPPORTED_MEASURES + + if self.measure == MEASURE_COSINE: + self.item_vectors /= np.linalg.norm(self.item_vectors, axis=1)[ + :, np.newaxis + ] + + self.item_vectors = self.item_vectors.astype("float32") + + self.index = faiss.IndexIVFFlat( + faiss.IndexFlat(self.item_vectors.shape[1]), + self.item_vectors.shape[1], + self.nlist, + SUPPORTED_MEASURES[self.measure], + ) + + if self.use_gpu: + self.index = faiss.index_cpu_to_all_gpus(self.index) + + self.index.train(self.item_vectors) + self.index.add(self.item_vectors) + self.index.nprobe = self.nprobe + + def knn_query(self, query, k): + """Implementing ANN search for a given query. + + Returns + ------- + neighbors, distances: numpy.array and numpy.array + Array of k-nearest neighbors and corresponding distances for the given query. + """ + distances, neighbors = self.index.search(query, k) + return neighbors, distances + + def save(self, save_dir=None): + import faiss + + saved_path = super().save(save_dir) + idx_path = saved_path + ".index" + if self.use_gpu: + self.index = faiss.index_gpu_to_cpu(self.index) + faiss.write_index(self.index, idx_path) + return saved_path + + @staticmethod + def load(model_path, trainable=False): + import faiss + + ann = BaseANN.load(model_path, trainable) + idx_path = ann.load_from + ".index" + ann.index = faiss.read_index(idx_path) + if ann.use_gpu: + ann.index = faiss.index_cpu_to_all_gpus(ann.index) + return ann diff --git a/cornac/models/ann/recom_ann_hnswlib.py b/cornac/models/ann/recom_ann_hnswlib.py index ff4e68547..784c59708 100644 --- a/cornac/models/ann/recom_ann_hnswlib.py +++ b/cornac/models/ann/recom_ann_hnswlib.py @@ -133,7 +133,7 @@ def knn_query(self, query, k): def save(self, save_dir=None): saved_path = super().save(save_dir) - self.index.save_index(saved_path + ".idx") + self.index.save_index(saved_path + ".index") return saved_path @staticmethod @@ -144,7 +144,7 @@ def load(model_path, trainable=False): ann.index = hnswlib.Index( space=SUPPORTED_MEASURES[ann.measure], dim=ann.user_vectors.shape[1] ) - ann.index.load_index(ann.load_from + ".idx") + ann.index.load_index(ann.load_from + ".index") ann.index.set_ef(ann.ef) ann.index.set_num_threads(ann.num_threads) return ann diff --git a/cornac/models/ann/recom_ann_scann.py b/cornac/models/ann/recom_ann_scann.py index af116f1af..662e5944f 100644 --- a/cornac/models/ann/recom_ann_scann.py +++ b/cornac/models/ann/recom_ann_scann.py @@ -22,7 +22,11 @@ from .recom_ann_base import BaseANN -SUPPORTED_MEASURES = {MEASURE_L2: "squared_l2", MEASURE_DOT: "dot_product"} +SUPPORTED_MEASURES = { + MEASURE_L2: "squared_l2", + MEASURE_DOT: "dot_product", + MEASURE_COSINE: "dot_product", +} class ScaNNANN(BaseANN): @@ -108,7 +112,6 @@ def build_index(self): self.item_vectors /= np.linalg.norm(self.item_vectors, axis=1)[ :, np.newaxis ] - self.measure = MEASURE_DOT else: self.partition_params["spherical"] = False @@ -149,7 +152,7 @@ def knn_query(self, query, k): def save(self, save_dir=None): saved_path = super().save(save_dir) - idx_path = saved_path + ".idx" + idx_path = saved_path + ".index" os.makedirs(idx_path, exist_ok=True) self.index.searcher.serialize(idx_path) return saved_path @@ -159,6 +162,6 @@ def load(model_path, trainable=False): from scann.scann_ops.py import scann_ops_pybind ann = BaseANN.load(model_path, trainable) - idx_path = ann.load_from + ".idx" + idx_path = ann.load_from + ".index" ann.index = scann_ops_pybind.load_searcher(idx_path) return ann diff --git a/examples/ann_all.ipynb b/examples/ann_all.ipynb index 856adccdb..84eb43565 100644 --- a/examples/ann_all.ipynb +++ b/examples/ann_all.ipynb @@ -3,16 +3,6 @@ { "cell_type": "code", "execution_count": 1, - "id": "b9a4225b-1a05-4b58-9e1d-1511650ef225", - "metadata": {}, - "outputs": [], - "source": [ - "!pip install -q hnswlib scann" - ] - }, - { - "cell_type": "code", - "execution_count": 2, "id": "74a9e78f-3e8a-4ee2-89fe-b3a3f4784b53", "metadata": {}, "outputs": [], @@ -35,7 +25,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "id": "76a0c130-7dd7-4004-a613-5b123dcc75d2", "metadata": {}, "outputs": [ @@ -70,7 +60,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "e984dd7e18d74f0090247ab9e8247797", + "model_id": "5f2de0606b84458cb20f3c5453487143", "version_major": 2, "version_minor": 0 }, @@ -93,7 +83,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "d563eecf55a045dda657a59890e7ec37", + "model_id": "c074be6de68c4dec92d856cf103f126c", "version_major": 2, "version_minor": 0 }, @@ -105,16 +95,20 @@ "output_type": "display_data" }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "TEST:\n", - "...\n", - " | AUC | Recall@20 | Train (s) | Test (s)\n", - "-- + ------ + --------- + --------- + --------\n", - "MF | 0.8530 | 0.0669 | 1.9484 | 10.3675\n", - "\n" + "ename": "KeyboardInterrupt", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[2], line 30\u001b[0m\n\u001b[1;32m 22\u001b[0m auc \u001b[38;5;241m=\u001b[39m cornac\u001b[38;5;241m.\u001b[39mmetrics\u001b[38;5;241m.\u001b[39mAUC()\n\u001b[1;32m 23\u001b[0m rec_20 \u001b[38;5;241m=\u001b[39m cornac\u001b[38;5;241m.\u001b[39mmetrics\u001b[38;5;241m.\u001b[39mRecall(k\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m20\u001b[39m)\n\u001b[1;32m 25\u001b[0m \u001b[43mcornac\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mExperiment\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 26\u001b[0m \u001b[43m \u001b[49m\u001b[43meval_method\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mratio_split\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 27\u001b[0m \u001b[43m \u001b[49m\u001b[43mmodels\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m[\u001b[49m\u001b[43mmf\u001b[49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 28\u001b[0m \u001b[43m \u001b[49m\u001b[43mmetrics\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m[\u001b[49m\u001b[43mauc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrec_20\u001b[49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 29\u001b[0m \u001b[43m \u001b[49m\u001b[43muser_based\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[0;32m---> 30\u001b[0m \u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrun\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/cornac/cornac/experiment/experiment.py:131\u001b[0m, in \u001b[0;36mExperiment.run\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 128\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_create_result()\n\u001b[1;32m 130\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m model \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmodels:\n\u001b[0;32m--> 131\u001b[0m test_result, val_result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43meval_method\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mevaluate\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 132\u001b[0m \u001b[43m \u001b[49m\u001b[43mmodel\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmodel\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 133\u001b[0m \u001b[43m \u001b[49m\u001b[43mmetrics\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmetrics\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 134\u001b[0m \u001b[43m \u001b[49m\u001b[43muser_based\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43muser_based\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 135\u001b[0m \u001b[43m \u001b[49m\u001b[43mshow_validation\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mshow_validation\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 136\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 138\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mresult\u001b[38;5;241m.\u001b[39mappend(test_result)\n\u001b[1;32m 139\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mval_result \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n", + "File \u001b[0;32m~/cornac/cornac/eval_methods/base_method.py:727\u001b[0m, in \u001b[0;36mBaseMethod.evaluate\u001b[0;34m(self, model, metrics, user_based, show_validation)\u001b[0m\n\u001b[1;32m 725\u001b[0m start \u001b[38;5;241m=\u001b[39m time\u001b[38;5;241m.\u001b[39mtime()\n\u001b[1;32m 726\u001b[0m model\u001b[38;5;241m.\u001b[39mtransform(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtest_set)\n\u001b[0;32m--> 727\u001b[0m test_result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_eval\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 728\u001b[0m \u001b[43m \u001b[49m\u001b[43mmodel\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmodel\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 729\u001b[0m \u001b[43m \u001b[49m\u001b[43mtest_set\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mtest_set\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 730\u001b[0m \u001b[43m \u001b[49m\u001b[43mval_set\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mval_set\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 731\u001b[0m \u001b[43m \u001b[49m\u001b[43muser_based\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43muser_based\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 732\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 733\u001b[0m test_time \u001b[38;5;241m=\u001b[39m time\u001b[38;5;241m.\u001b[39mtime() \u001b[38;5;241m-\u001b[39m start\n\u001b[1;32m 734\u001b[0m test_result\u001b[38;5;241m.\u001b[39mmetric_avg_results[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mTrain (s)\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;241m=\u001b[39m train_time\n", + "File \u001b[0;32m~/cornac/cornac/eval_methods/base_method.py:663\u001b[0m, in \u001b[0;36mBaseMethod._eval\u001b[0;34m(self, model, test_set, val_set, user_based)\u001b[0m\n\u001b[1;32m 660\u001b[0m metric_avg_results[mt\u001b[38;5;241m.\u001b[39mname] \u001b[38;5;241m=\u001b[39m avg_results[i]\n\u001b[1;32m 661\u001b[0m metric_user_results[mt\u001b[38;5;241m.\u001b[39mname] \u001b[38;5;241m=\u001b[39m user_results[i]\n\u001b[0;32m--> 663\u001b[0m avg_results, user_results \u001b[38;5;241m=\u001b[39m \u001b[43mranking_eval\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 664\u001b[0m \u001b[43m \u001b[49m\u001b[43mmodel\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmodel\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 665\u001b[0m \u001b[43m \u001b[49m\u001b[43mmetrics\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mranking_metrics\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 666\u001b[0m \u001b[43m \u001b[49m\u001b[43mtrain_set\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mtrain_set\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 667\u001b[0m \u001b[43m \u001b[49m\u001b[43mtest_set\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtest_set\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 668\u001b[0m \u001b[43m \u001b[49m\u001b[43mval_set\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mval_set\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 669\u001b[0m \u001b[43m \u001b[49m\u001b[43mrating_threshold\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrating_threshold\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 670\u001b[0m \u001b[43m \u001b[49m\u001b[43mexclude_unknowns\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mexclude_unknowns\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 671\u001b[0m \u001b[43m \u001b[49m\u001b[43mverbose\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mverbose\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 672\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 673\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i, mt \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mranking_metrics):\n\u001b[1;32m 674\u001b[0m metric_avg_results[mt\u001b[38;5;241m.\u001b[39mname] \u001b[38;5;241m=\u001b[39m avg_results[i]\n", + "File \u001b[0;32m~/cornac/cornac/eval_methods/base_method.py:206\u001b[0m, in \u001b[0;36mranking_eval\u001b[0;34m(model, metrics, train_set, test_set, val_set, rating_threshold, exclude_unknowns, verbose)\u001b[0m\n\u001b[1;32m 203\u001b[0m u_gt_pos_items \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39mnonzero(u_gt_pos_mask)[\u001b[38;5;241m0\u001b[39m]\n\u001b[1;32m 204\u001b[0m u_gt_neg_items \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39mnonzero(u_gt_neg_mask)[\u001b[38;5;241m0\u001b[39m]\n\u001b[0;32m--> 206\u001b[0m item_rank, item_scores \u001b[38;5;241m=\u001b[39m \u001b[43mmodel\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrank\u001b[49m\u001b[43m(\u001b[49m\u001b[43muser_idx\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mitem_indices\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 208\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i, mt \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(metrics):\n\u001b[1;32m 209\u001b[0m mt_score \u001b[38;5;241m=\u001b[39m mt\u001b[38;5;241m.\u001b[39mcompute(\n\u001b[1;32m 210\u001b[0m gt_pos\u001b[38;5;241m=\u001b[39mu_gt_pos_items,\n\u001b[1;32m 211\u001b[0m gt_neg\u001b[38;5;241m=\u001b[39mu_gt_neg_items,\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 214\u001b[0m item_indices\u001b[38;5;241m=\u001b[39mitem_indices,\n\u001b[1;32m 215\u001b[0m )\n", + "File \u001b[0;32m~/cornac/cornac/models/recommender.py:462\u001b[0m, in \u001b[0;36mRecommender.rank\u001b[0;34m(self, user_idx, item_indices, **kwargs)\u001b[0m\n\u001b[1;32m 460\u001b[0m \u001b[38;5;66;03m# obtain item scores from the model\u001b[39;00m\n\u001b[1;32m 461\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 462\u001b[0m known_item_scores \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mscore\u001b[49m\u001b[43m(\u001b[49m\u001b[43muser_idx\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 463\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ScoreException:\n\u001b[1;32m 464\u001b[0m known_item_scores \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39mones(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtotal_items) \u001b[38;5;241m*\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdefault_score()\n", + "File \u001b[0;32m~/cornac/cornac/models/mf/recom_mf.py:278\u001b[0m, in \u001b[0;36mMF.score\u001b[0;34m(self, user_idx, item_idx)\u001b[0m\n\u001b[1;32m 276\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mknows_user(user_idx):\n\u001b[1;32m 277\u001b[0m known_item_scores \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mu_biases[user_idx]\n\u001b[0;32m--> 278\u001b[0m \u001b[43mfast_dot\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mu_factors\u001b[49m\u001b[43m[\u001b[49m\u001b[43muser_idx\u001b[49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mi_factors\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mknown_item_scores\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 279\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m known_item_scores\n\u001b[1;32m 280\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n", + "\u001b[0;31mKeyboardInterrupt\u001b[0m: " ] } ], @@ -161,7 +155,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "46c5f959-f706-4406-85c9-a4acf2ac20c0", "metadata": {}, "outputs": [], @@ -181,19 +175,10 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "6730a93f-729d-4bea-b5aa-8fb9d9cef867", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 3min 36s, sys: 19.4 ms, total: 3min 36s\n", - "Wall time: 4.51 s\n" - ] - } - ], + "outputs": [], "source": [ "%%time\n", "mf_recs = []\n", @@ -203,7 +188,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "9808c778-9513-483f-bc76-87aa63541bbc", "metadata": {}, "outputs": [], @@ -225,25 +210,18 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "fa280b9d-ec04-41eb-9de2-acfb67fbeb80", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "HNSWLibANN\t\tIndexing=93ms\t\tRetrieval=211ms\t\tRecall=0.99875\n", - "ScaNNANN\t\tIndexing=99ms\t\tRetrieval=511ms\t\tRecall=0.99998\n" - ] - } - ], + "outputs": [], "source": [ "import time\n", + "from cornac.models import FaissANN\n", "from cornac.models import HNSWLibANN\n", "from cornac.models import ScaNNANN\n", "\n", "anns = [\n", + " FaissANN(model=mf, nlist=100, nprobe=50, use_gpu=False, seed=123, num_threads=-1),\n", " HNSWLibANN(model=mf, M=16, ef_construction=100, ef=50, seed=123, num_threads=-1),\n", " ScaNNANN(\n", " model=mf,\n", @@ -279,19 +257,10 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "1fec7413-482b-417b-a12f-362a93aefb85", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "HNSWLibANN True\n", - "ScaNNANN True\n" - ] - } - ], + "outputs": [], "source": [ "for ann in anns:\n", " saved_path = ann.save(\"save_dir\")\n",