Source code for cornac.explainer.exp_phi4mf

from .explainer import Explainer
import pandas as pd
import numpy as np
from mlxtend.preprocessing import TransactionEncoder
from mlxtend.frequent_patterns import association_rules, apriori


[docs] class Exp_PHI4MF(Explainer): """Post Hoc Interpretability of Latent Factor Models for Recommendation Systems. Explain by generating association rules from the recommendations of the model. Parameters ---------- rec_model: object, recommender model The recommender model to be explained. dataset: object, dataset The dataset object that is used to explain. rec_k: int, optional, default: 10 Number of recommendations to generate association rules. min_supp: float, optional, default: 0.001 minimum support for the apriori algorithm min_conf: float, optional, default: 0.001 minimum confidence for the apriori algorithm min_lift: float, optional, default: 0.01 minimum lift for the apriori algorithm name: string, optional, default: 'Exp_PHI4MF' References ---------- [1] Georgina Peake and Jun Wang. 2018. Explanation Mining: Post Hoc Interpretability of Latent Factor Models for Recommendation Systems. In Proceedings of the 24th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining, ACM, London United Kingdom, 2060-2069. DOI:https://doi.org/10.1145/3219819.3220072 """ def __init__( self, rec_model, dataset, rec_k=10, min_supp=0.001, min_conf=0.001, min_lift=0.01, name="Exp_PHI4MF", ): super().__init__(name=name, rec_model=rec_model, dataset=dataset) self.min_supp = min_supp self.min_conf = min_conf self.min_lift = min_lift self.rec_k = rec_k if self.model is None: raise NotImplementedError("The model is None.") if self.dataset is None: raise NotImplementedError("The dataset is None.") if hasattr(self.dataset, "train_set"): self.dataset = self.dataset.train_set self.rules = None self.item_idx_2_id = None self.uir_df = None def generate_rules(self): transactions = [] for user_id, user_idx in self.dataset.uid_map.items(): if self.model.is_unknown_user(user_idx): continue item_ids = self.model.recommend(user_id, self.rec_k, remove_seen=False) # item_idx = [self.dataset.iid_map[item] for item in item_ids] transactions.append(item_ids) try : te = TransactionEncoder() te_transactions = te.fit_transform(transactions) te_transactions = pd.DataFrame(te_transactions, columns=te.columns_) itemsets = apriori( te_transactions, min_support=self.min_supp, use_colnames=True, ) rules = association_rules(itemsets, metric="lift", min_threshold=self.min_lift) rules = rules[rules["confidence"] > self.min_conf] rules = rules[rules["consequents"].apply(lambda x: len(list(x)) == 1)] rules["consequents"] = rules["consequents"].apply(lambda x: list(x)[0]) self.rules = rules[["consequents", "antecedents", "confidence"]] print("Association rules generated") except: raise ValueError("Association rules generation failed") return self.rules
[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 Explanations as a dictionary of antecedents: confidence. """ if self.rules is None: self.generate_rules() feature_k = kwargs.get("feature_k", 10) if self.item_idx_2_id is None: self.item_idx_2_id = {v: k for k, v in self.dataset.iid_map.items()} if self.uir_df is None: self.uir_df = pd.DataFrame( np.array(self.dataset.uir_tuple).T, columns=["user", "item", "rating"] ) self.uir_df["user"] = self.uir_df["user"].astype(int) self.uir_df["item"] = self.uir_df["item"].astype(int) self.uir_df["item_id"] = self.uir_df["item"].apply(lambda x: self.item_idx_2_id[x]) if user_id not in self.dataset.uid_map: print(f"User {user_id} not in dataset") return {} user_idx = self.dataset.uid_map[user_id] if self.model.is_unknown_user(user_idx): return {} if item_id not in self.dataset.iid_map: print(f"Item {item_id} not in dataset") return {} item_idx = self.dataset.iid_map[item_id] if self.model.is_unknown_item(item_idx): return {} user_items = set(self.uir_df[self.uir_df.user == user_idx]["item_id"]) rules = self.rules[self.rules['consequents'] == item_id] rules = rules[rules['antecedents'].apply(lambda x: set(x).issubset(user_items))] if rules.empty: return {} exp = rules.sort_values(by=["confidence"], ascending=False)[:feature_k] explanation = {} for _, e in exp.iterrows(): text = str(list(e['antecedents'])) explanation[text] = e['confidence'] return explanation