<jupyter_start><jupyter_text>Code to generate Figure 4<jupyter_code>#import pandas as pd
import numpy as np

from sklearn.model_selection import train_test_split
from sklearn import preprocessing

from utils.svm import LinearSupportVectorMachine_noOffset
from utils.RandFourier import RandomFourier
from utils.functions import * 

from matplotlib.ticker import (MultipleLocator, FormatStrFormatter, AutoMinorLocator)

import matplotlib
import matplotlib.pyplot as plt

figwidth = 8
figheight = 4<jupyter_output><empty_output><jupyter_text>Load and preprocess the data<jupyter_code># Load Dataset
X, Y, labels = load_wdbc()

# Creat Train and Test sets
X_train, X_test, ytrain, ytest = train_test_split(X, Y, test_size=0.3, random_state=1)
y_train = np.asarray(ytrain)
y_test = np.asarray(ytest)

# Preprocess
scaler = preprocessing.StandardScaler()
scaler.fit(X_train)

X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)<jupyter_output><empty_output><jupyter_text>Find a prototype for each class<jupyter_code># prototype[0]: class 1
# prototype[1]: class -1

prototype = [np.mean(X_train_scaled[y_train == 1], axis=0), np.mean(X_train_scaled[y_train == -1], axis=0)]<jupyter_output><empty_output><jupyter_text>Setting the parameters`F`: number of features for the RBF feature map approximation  `noise_lambda`: noise scale<jupyter_code>n = X_train_scaled.shape[0] # number of training data points
C = np.sqrt(n) # SVM parameter should scale as such (consistent classifier)
print("C:",C)

# number of features (should be an even number)
F = int(2*50) 

beta = 5

# calculate noise scale: lambda 
noise_lambda = 4*C*np.sqrt(F)/(beta*n)
print("noise scale lambda:",noise_lambda)
print("privacy beta:",beta)<jupyter_output>C: 19.949937343260004
noise scale lambda: 0.4010037656936684
privacy beta: 5<jupyter_text>Random Fourier Features: RBF kernel approximation<jupyter_code>FM_transform = RandomFourier(n_components=F, random_state=1).fit(X_train_scaled)
X_train_FM = FM_transform.transform(X_train_scaled)
X_test_FM = FM_transform.transform(X_test_scaled)<jupyter_output><empty_output><jupyter_text>Train SVM Classifier<jupyter_code>SVM = LinearSupportVectorMachine_noOffset(C=C)
lagr_multipliers, idx_support_vectors, support_vectors, support_vector_labels = SVM.fit(X_train_FM, y_train)

# weight vector
w = np.dot(lagr_multipliers*support_vector_labels,support_vectors)

evaluation = np.sign(SVM.predict(X_test_FM).flatten()) == y_test
print("Classification accuracy:", sum(evaluation)/len(evaluation))<jupyter_output>Classification accuracy: 0.9239766081871345<jupyter_text>Generate Laplace noise<jupyter_code>np.random.seed(seed=1)
mu = np.random.laplace(loc=0.0, scale=noise_lambda, size=(1,F))

evaluation_privacy = np.sign(SVM.predict(X_test_FM, mu).flatten()) == y_test
print("Classification accuracy:", sum(evaluation_privacy)/len(evaluation_privacy))<jupyter_output>Classification accuracy: 0.8771929824561403<jupyter_text>Check if prototypes are correctly classified<jupyter_code>prototype_prediction = np.sign(SVM.predict(FM_transform.transform(prototype), noise=mu))
check = prototype_prediction.flatten() == [1,-1]
if np.all(check):
    print("Correct prototypes' predictions!")
else:
    print("Misclassification of prototype",check)<jupyter_output>Correct prototypes' predictions!<jupyter_text>Select a test data instance whose classification we want to explain and select the prototype of different class<jupyter_code>#np.random.seed(seed=1)
#idx_selected = np.random.randint(0,len(X_test))
idx_selected = 30
instance = X_test_scaled[idx_selected]
instance_transformed = FM_transform.transform(instance.reshape(1, -1))

prediction_instance = np.sign(SVM.predict(instance_transformed, noise=mu).flatten())
print("Index of selected test data:", idx_selected, "-- Predicted class:", prediction_instance, "-- True class label:", y_test[idx_selected])

# choose prototype of different class than instance
selected_prototype = prototype[np.where(prediction_instance != [1,-1])[0][0]]
selected_prototype_transformed = FM_transform.transform(selected_prototype.reshape(1, -1))<jupyter_output>Index of selected test data: 30 -- Predicted class: [1.] -- True class label: 1<jupyter_text>Counterfactual Explanation<jupyter_code>proba = 0.9

# Bisection
explanation, plot_convergence = bisection_chance(instance, prediction_instance, selected_prototype, SVM, FM_transform, mu, noise_lambda, p=proba, acc=0.0001)

# plot convergence
fig, ax = plt.subplots(figsize=[figwidth, figheight], dpi=100)
ax.plot(np.absolute(plot_convergence), c="tab:blue", marker=".")
ax.set(xlabel="iteration", ylabel="$||{x}^{ub} - {x}^{lb}||$", title='')
ax.grid()
plt.xticks(np.arange(0, 17, 1.0))
plt.show()
# Plot Explanation unscaled
x = np.arange(len(labels))  # the label locations
width = 0.4  # the width of the bars

fig, ax = plt.subplots(figsize=[figwidth, figheight], dpi=100)
ax.bar(x+1 , (unscale(explanation,scaler)-unscale(instance,scaler))/unscale(instance,scaler) , width, edgecolor='tab:blue', color="tab:blue")

ax.set_xlim((0,31))
ax.xaxis.set_minor_locator(MultipleLocator(1))
ax.xaxis.set_major_locator(MultipleLocator(3))
ax.set(xlabel="feature number i", ylabel="scaling to $x_i$", title='')
ax.grid(axis="y")
plt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12341712341712342, 'fraction_numerical': 0.02608202608202608, 'mean_word_length': 8.300527240773286, 'pattern_counts': {'":': 0, '<': 36, '<?xml version=': 0, '>': 36, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '446538', 'license': 'MIT', 'n_tokens_mistral': 1944, 'n_tokens_neox': 1824, 'n_words': 367, 'path': 'Single Counterfactual Explanation.ipynb', 'repo_name': 'rami-mochaourab/robust-explanation-SVM', 'size': '5291'}