Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

z-scoREC Implementation #485

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,12 @@ The recommender models supported by Cornac are listed below. Why don't you join

| Year | Model and paper | Additional dependencies | Examples |
| :---: | --- | :---: | :---: |
| 2022 | [ImposeSVD: Incrementing PureSVD For Top-N Recommendations for Cold-Start Problems and Sparse Datasets (z-scoREC)](cornac/models/zscorec), [paper]( https://doi.org/10.1093/comjnl/bxac106) | N/A | [zscorec_netflix.py](examples/zscorec_netflix.py)
| 2021 | [Bilateral Variational Autoencoder for Collaborative Filtering (BiVAECF)](cornac/models/bivaecf), [paper](https://dl.acm.org/doi/pdf/10.1145/3437963.3441759) | [requirements.txt](cornac/models/bivaecf/requirements.txt) | [PreferredAI/bi-vae](https://github.com/PreferredAI/bi-vae)
| | [Causal Inference for Visual Debiasing in Visually-Aware Recommendation (CausalRec)](cornac/models/causalrec), [paper](https://arxiv.org/abs/2107.02390) | [requirements.txt](cornac/models/causalrec/requirements.txt) | [causalrec_clothing.py](examples/causalrec_clothing.py)
| | [Explainable Recommendation with Comparative Constraints on Product Aspects (ComparER)](cornac/models/comparer), [paper](https://dl.acm.org/doi/pdf/10.1145/3437963.3441754) | N/A | [PreferredAI/ComparER](https://github.com/PreferredAI/ComparER)
| 2020 | [Adversarial Training Towards Robust Multimedia Recommender System (AMR)](cornac/models/amr), [paper](https://ieeexplore.ieee.org/document/8618394) | [requirements.txt](cornac/models/amr/requirements.txt) | [amr_clothing.py](examples/amr_clothing.py)
| 2019 | [Embarrassingly Shallow Autoencoders for Sparse Data (EASEᴿ)](cornac/models/ease), [paper](https://arxiv.org/pdf/1905.03375.pdf) | N/A | [ease_movielens.py](examples/ease_movielens.py)
| 2018 | [Collaborative Context Poisson Factorization (C2PF)](cornac/models/c2pf), [paper](https://www.ijcai.org/proceedings/2018/0370.pdf) | N/A | [c2pf_exp.py](examples/c2pf_example.py)
| | [Multi-Task Explainable Recommendation (MTER)](cornac/models/mter), [paper](https://arxiv.org/pdf/1806.03568.pdf) | N/A | [mter_exp.py](examples/mter_example.py)
| | [Neural Attention Rating Regression with Review-level Explanations (NARRE)](cornac/models/narre), [paper](http://www.thuir.cn/group/~YQLiu/publications/WWW2018_CC.pdf) | [requirements.txt](cornac/models/narre/requirements.txt) | [narre_example.py](examples/narre_example.py)
Expand Down Expand Up @@ -153,6 +155,7 @@ The recommender models supported by Cornac are listed below. Why don't you join
| | [User K-Nearest-Neighbors (UserKNN)](cornac/models/knn), [paper](https://arxiv.org/pdf/1301.7363.pdf) | N/A | [knn_movielens.py](examples/knn_movielens.py)
| | [Weighted Matrix Factorization (WMF)](cornac/models/wmf), [paper](http://yifanhu.net/PUB/cf.pdf) | [requirements.txt](cornac/models/wmf/requirements.txt) | [wmf_exp.py](examples/wmf_example.py)


## Support

Your contributions at any level of the library are welcome. If you intend to contribute, please:
Expand Down
2 changes: 2 additions & 0 deletions cornac/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from .ctr import CTR
from .cvae import CVAE
from .cvaecf import CVAECF
from .ease import EASE
from .efm import EFM
from .global_avg import GlobalAvg
from .hft import HFT
Expand Down Expand Up @@ -59,6 +60,7 @@
from .vbpr import VBPR
from .vmf import VMF
from .wmf import WMF
from .zscorec import zscoREC

try:
from .fm import FM
Expand Down
1 change: 1 addition & 0 deletions cornac/models/ease/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .recom_ease import EASE
135 changes: 135 additions & 0 deletions cornac/models/ease/recom_ease.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import numpy as np

from cornac.models.recommender import Recommender
from cornac.exception import ScoreException

class EASE(Recommender):
"""Embarrassingly Shallow Autoencoders for Sparse Data.

Parameters
----------
name: string, optional, default: 'EASEᴿ'
The name of the recommender model.

lamb: float, optional, default: 500
L2-norm regularization-parameter λ ∈ R+.

posB: boolean, optional, default: False
Remove Negative Weights

trainable: boolean, optional, default: True
When False, the model is not trained and Cornac assumes that the model is already \
trained.

verbose: boolean, optional, default: False
When True, some running logs are displayed.

seed: int, optional, default: None
Random seed for parameters initialization.

References
----------
* Steck, H. (2019, May). "Embarrassingly shallow autoencoders for sparse data." \
In The World Wide Web Conference (pp. 3251-3257).
"""

def __init__(
self,
name="EASEᴿ",
lamb=500,
posB=True,
trainable=True,
verbose=True,
seed=None,
B=None,
U=None,
):
Recommender.__init__(self, name=name, trainable=trainable, verbose=verbose)
self.lamb = lamb
self.posB = posB
self.verbose = verbose
self.seed = seed
self.B = B
self.U = U

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)

# A rating matrix
self.U = self.train_set.matrix

# Gram matrix is X^t X, compute dot product
G = self.U.T.dot(self.U).toarray()

diag_indices = np.diag_indices(G.shape[0])

G[diag_indices] = G.diagonal() + self.lamb

P = np.linalg.inv(G)

B = P / (-np.diag(P))

B[diag_indices] = 0.0

# if self.posB remove -ve values
if self.posB:
B[B<0]=0

# save B for predictions
self.B=B

return self


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.

Returns
-------
res : A scalar or a Numpy array
Relative scores that the user gives to the item or to all known items

"""
if item_idx is None:
if self.train_set.is_unk_user(user_idx):
raise ScoreException(
"Can't make score prediction for (user_id=%d)" % user_idx
)

known_item_scores = self.U[user_idx, :].dot(self.B)
return known_item_scores
else:
if self.train_set.is_unk_user(user_idx) or self.train_set.is_unk_item(
item_idx
):
raise ScoreException(
"Can't make score prediction for (user_id=%d, item_id=%d)"
% (user_idx, item_idx)
)

user_pred = self.B[item_idx, :].dot(self.U[user_idx, :])

return user_pred
1 change: 1 addition & 0 deletions cornac/models/zscorec/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .recom_zscorec import zscoREC
141 changes: 141 additions & 0 deletions cornac/models/zscorec/recom_zscorec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import numpy as np

from cornac.models.recommender import Recommender
from cornac.exception import ScoreException

import scipy.sparse as sps
import scipy.stats as stats
import numpy as np

class zscoREC(Recommender):
"""ImposeSVD: Incrementing PureSVD For Top-N Recommendations for Cold-Start Problems and Sparse Datasets.

Parameters
----------
name: string, optional, default: 'zscoREC'
The name of the recommender model.

lamb: float, optional, default: .2
Shifting parameter λ ∈ R

posZ: boolean, optional, default: False
Remove Negative Weights

trainable: boolean, optional, default: True
When False, the model is not trained and Cornac assumes that the model is already \
trained.

verbose: boolean, optional, default: False
When True, some running logs are displayed.

seed: int, optional, default: None
Random seed for parameters initialization.

References
----------
* Hakan Yilmazer, Selma Ayşe Özel, ImposeSVD: Incrementing PureSVD For Top-N Recommendations for Cold-Start Problems and Sparse Datasets,
The Computer Journal, 2022;, bxac106, https://doi.org/10.1093/comjnl/bxac106.
"""

def __init__(
self,
name="zscoREC",
lamb=.2,
posZ=True,
trainable=True,
verbose=True,
seed=None,
Z=None,
U=None,
):
Recommender.__init__(self, name=name, trainable=trainable, verbose=verbose)
self.lamb = lamb
self.posZ = posZ
self.verbose = verbose
self.seed = seed
self.Z = Z
self.U = U

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)

# A rating matrix
self.U = self.train_set.matrix

# Gram matrix is X^t X, Eq.(2) in paper
G = sps.csr_matrix(self.U.T.dot(self.U), dtype="uint64")

# Shifting Gram matrix Eq.(7) in paper
""" In a structure view, the shifting operation on the second
matrix (which is the same as the matrix for the Gram-matrix
estimation) is performed as row-based degree shifting on
Gram-matrix.
"""
W = G - np.tile(G.diagonal()*self.lam, (G.shape[0],1)).transpose()

# Column-based z-score normalization
# fit each item's column (could be parallel for big streams)
Z = stats.mstats.zscore(np.array(W), axis=0, ddof=1, nan_policy='omit')

# if self.posZ remove -ve values
if self.posZ:
Z[Z<0]=0

# save Z for predictions
self.Z = Z

return self


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.

Returns
-------
res : A scalar or a Numpy array
Relative scores that the user gives to the item or to all known items

"""
if item_idx is None:
if self.train_set.is_unk_user(user_idx):
raise ScoreException(
"Can't make score prediction for (user_id=%d)" % user_idx
)

known_item_scores = self.U[user_idx, :].dot(self.Z)
return known_item_scores
else:
if self.train_set.is_unk_user(user_idx) or self.train_set.is_unk_item(
item_idx
):
raise ScoreException(
"Can't make score prediction for (user_id=%d, item_id=%d)"
% (user_idx, item_idx)
)

user_pred = self.Z[item_idx, :].dot(self.U[user_idx, :])

return user_pred
12 changes: 11 additions & 1 deletion docs/source/models.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ Recommender (Generic Class)
.. automodule:: cornac.models.recommender
:members:

ImposeSVD: Incrementing PureSVD For Top-N Recommendations for Cold-Start Problems and Sparse Datasets (z-scoREC)
----------------------------------------------------
.. automodule:: cornac.models.zscorec.recom_zscorec
:members:

Bilateral VAE for Collaborative Filtering (BiVAECF)
----------------------------------------------------
.. automodule:: cornac.models.bivaecf.recom_bivaecf
Expand All @@ -33,6 +38,11 @@ Adversarial Training Towards Robust Multimedia Recommender System (AMR)
.. automodule:: cornac.models.amr.recom_amr
:members:

Embarrassingly Shallow Autoencoders for Sparse Data (EASEᴿ)
--------------------------------------------------
.. automodule:: cornac.models.ease.recom_ease
:members:

Collaborative Context Poisson Factorization (C2PF)
----------------------------------------------------
.. automodule:: cornac.models.c2pf.recom_c2pf
Expand Down Expand Up @@ -231,4 +241,4 @@ User K-Nearest-Neighbors (UserKNN)
Weighted Matrix Factorization (WMF)
--------------------------------------------------
.. automodule:: cornac.models.wmf.recom_wmf
:members:
:members:
4 changes: 4 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@

[bpr_netflix.py](bpr_netflix.py) - Example to run Bayesian Personalized Ranking (BPR) with Netflix dataset.

[ease_movielens.py](ease_movielens.py) - Embarrassingly Shallow Autoencoders (EASEᴿ) with MovieLens 1M dataset.

[fm_example.py](fm_example.py) - Example to run Factorization Machines (FM) with MovieLens 100K dataset.

[hpf_movielens.py](hpf_movielens.py) - (Hierarchical) Poisson Factorization vs BPR on MovieLens data.
Expand All @@ -91,3 +93,5 @@
[vaecf_citeulike.py](vaecf_citeulike.py) - Variational Autoencoder for Collaborative Filtering (VAECF) with CiteULike dataset.

[wmf_example.py](wmf_example.py) - Weighted Matrix Factorization with CiteULike dataset.

[zscorec_movielens.py](zscorec_movielens.py) - z-scoREC model with MovieLens 1M dataset.
Loading