import pandas as pd
import numpy as np
from .explainer import Explainer
[docs]
class Exp_ALS(Explainer):
"""Alternating Least Squares of Matrix Factorization for implicit feedback datasets.
Parameters
----------
rec_model: object, recommender model
The recommender model to be explained.
dataset: object, dataset
The dataset object that is used to explain.
name: string, optional, default: 'Exp_ALS'
References
----------
[1] Y. Hu, Y. Koren, and C. Volinsky, “Collaborative Filtering for Implicit Feedback Datasets,” in 2008 Eighth IEEE International Conference on Data Mining, Pisa, Italy: IEEE, Dec. 2008, pp. 263-272. doi: 10.1109/ICDM.2008.22.
[2] https://github.com/ludovikcoba/recoxplainer/blob/master/recoxplainer/explain/model_based_als_explain.py
"""
def __init__(self, rec_model, dataset, name="Exp_ALS"):
super().__init__(name=name, rec_model=rec_model, dataset=dataset)
[docs]
def explain_one_recommendation_to_user(self, user_id, item_id, **kwargs):
"""Provide explanation for one user and one item
Parameters
----------
user_id: str
One user's id.
item_id: str
One item's id.
feature_k: int, optional, default:10
Number of features in explanations created by explainer.
Returns
-------
explanations: dict
Items and their contributions.
"""
self.number_of_contributions = kwargs.get("feature_k", 10)
uir_df = pd.DataFrame(
np.array(self.dataset.uir_tuple).T, columns=["user", "item", "rating"]
)
if user_id not in self.dataset.uid_map:
return {}
if item_id not in self.dataset.iid_map:
return {}
uir_df["user"] = uir_df["user"].astype(int)
uir_df["item"] = uir_df["item"].astype(int)
user_idx = self.dataset.uid_map[user_id]
item_idx = self.dataset.iid_map[item_id]
if self.model.is_unknown_user(user_idx):
return {}
if self.model.is_unknown_item(item_idx):
return {}
users_by_item = uir_df[uir_df["item"] == item_idx]["user"]
items_by_user = uir_df[uir_df["user"] == user_idx]["item"]
# alpha = 1.0
# if hasattr(self.model, 'alpha'):
# alpha = self.model.alpha
if not hasattr(self.model, "alpha"):
raise AttributeError("The explainer does not support this recommender.")
if self.model is None:
raise NotImplementedError("The model is None.")
alpha = self.model.alpha
current_interactions = np.zeros(self.num_items)
user_items = self.dataset.matrix * alpha
# current_interactions[items_by_user] = user_items[user_idx, items_by_user]
for item in items_by_user:
# c_ui = 1 + a r_ui
current_interactions[item] = user_items[user_idx, item] + 1
C_u = np.diag(current_interactions)
Y_T = self.model.i_factors.T
Y = self.model.i_factors
W_u = np.matmul(Y_T, np.matmul(C_u, Y)) + self.model.lambda_reg * np.eye(
self.model.k
)
if len(items_by_user) > 1:
W_u = np.linalg.inv(W_u)
else:
W_u = np.linalg.pinv(W_u)
temp = np.matmul(Y, W_u)
if item_idx >= len(Y):
return {}
y_j = Y[item_idx]
# sim_to_rec = np.matmul(temp, y_j.T)
sim_to_rec = np.dot(temp, y_j)
sim_to_rec = sim_to_rec[items_by_user]
confidence = current_interactions[items_by_user]
contribution = {"item": items_by_user, "contribution": sim_to_rec * confidence}
contribution = pd.DataFrame(contribution)
contribution = contribution.sort_values(by=["contribution"], ascending=False)
item_idx2id = {v: k for k, v in self.dataset.iid_map.items()}
contribution["item"] = contribution["item"].apply(lambda x: item_idx2id[x])
# return {"item_id": contribution['item'].values[:self.number_of_contributions],
# "contribution": contribution['contribution'].values[:self.number_of_contributions]}
return {
v: k
for v, k in zip(
contribution["item"].values[: self.number_of_contributions],
contribution["contribution"].values[: self.number_of_contributions],
)
}