""" Implements LIME_Global. Verifies if sensitives features have high contributions. """ from collections import Counter import pandas as pd import numpy as np from lime import submodular_pick from lime.lime_text import LimeTextExplainer clusters = 50 def features_contributions(predict_fn, train, class_names, sample_size, kernel_width=5): explainer = LimeTextExplainer(class_names=class_names, kernel_width=kernel_width) if sample_size > len(train): sample_size = len(train) indexes = np.random.choice(range(sample_size), sample_size) explanations = [explainer.explain_instance(train[i].lower(), predict_fn, num_features=1000) for i in indexes] # sp_obj = submodular_pick.SubmodularPick(explainer, train, predict_fn, sample_size=sample_size, # num_features=1000, clusters=clusters) # explanations = sp_obj.sp_explanations return explainer, explanations def fairness_eval(model, train, max_features, sensitive_features, feature_names, class_names, sample_size, threshold=None): explainer, explanations = features_contributions(model.prob, train, class_names, sample_size) contributions = Counter() for exp in explanations: vocab = exp.domain_mapper.indexed_string.inverse_vocab words_weights = {vocab[i]: weight for i, weight in exp.local_exp[1]} a1 = Counter(words_weights) contributions.update(a1) if threshold != None and threshold > 0: actual_sensitive, is_fair, df = fairness_valid_threshold(contributions, feature_names, sensitive_features, threshold) else: actual_sensitive, is_fair, df = fairness_valid_top(contributions, feature_names, sensitive_features, max_features) return actual_sensitive, is_fair, df, explainer def fairness_valid_top(contributions, feature_names, sensitive_features, max_features): actual_sensitive = [] ans_data = [] sorted_dict = sorted(contributions.items(), key=lambda x: abs(x[1]), reverse=True) if max_features is None or max_features < 0 : max_features = len(sorted_dict) for i in range(max_features): feature, value = sorted_dict[i] ans_data.append([i, feature, value]) if feature in sensitive_features: actual_sensitive.append(feature) df = pd.DataFrame(ans_data, columns=["Index", "Word", "Contribution"]) return actual_sensitive, len(actual_sensitive) < 2, df def fairness_valid_threshold(contributions, feature_names, sensitive_features, threshold): actual_sensitive = [] ans_data = [] n_contributions = normalize(contributions) sorted_dict = sorted(n_contributions.items(), key=lambda x: abs(x[1]), reverse=True) for key,value in sorted_dict: if abs(value) < threshold: break ans_data.append([key,feature_names[key],value]) for pair in ans_data: key = pair[0] if key in sensitive_features: actual_sensitive.append(key) df = pd.DataFrame(ans_data, columns = ["Index", "Feature", "Contribution"]) return actual_sensitive, len(actual_sensitive) < 2, df def normalize(b): a = b.copy() values = [abs(x[1]) for x in a.items()] # values = list(map(abs, a.items())) minv = np.min(values) maxv = np.max(values) for key in a.keys(): v = a[key] normalized = (abs(v) - minv) / (maxv - minv) a[key] = normalized if v >= 0 else -normalized return a