Source code for rocelib.recourse_methods.GuidedBinaryLinearSearch

from rocelib.lib.distance_functions.DistanceFunctions import euclidean
from rocelib.recourse_methods.RecourseGenerator import RecourseGenerator
from rocelib.evaluations.robustness_evaluations.MC_Robustness_Implementations.DeltaRobustnessEvaluator import DeltaRobustnessEvaluator


[docs] class GuidedBinaryLinearSearch(RecourseGenerator): """ A recourse method that performs a guided binary search between an original instance and a positively classified instance, attempting to meet a user-defined robustness threshold. """ def _generation_method(self, instance, gamma=0.1, column_name="target", neg_value=0, distance_func=euclidean, **kwargs): """ Generate a counterfactual by repeatedly searching for a robust positive instance and then performing binary search until the Euclidean distance to the original instance is below `gamma`. Parameters ---------- instance : pd.Series The original instance for which to generate a recourse. gamma : float, optional The distance threshold at which to stop the binary search, by default 0.1. column_name : str, optional The name of the target column, by default "target". neg_value : int, optional Controls whether a 'negative' instance is predicted by the model, by default 0. distance_func : callable, optional The distance function used to measure how close two instances are, by default `euclidean`. kwargs : dict Additional arguments to pass to the method (not used here). Returns ------- pd.DataFrame A single-row DataFrame containing the counterfactual instance, along with the 'target' column indicating model prediction and a 'loss' column representing the final distance. """ # Obtain a random positive instance and evaluate its robustness c = self.task.get_random_positive_instance(neg_value, column_name).T opt = DeltaRobustnessEvaluator(self.task) MAX_ITERATIONS = 10 iteration = 0 # Keep searching for a robust positive instance if not robust while not opt.evaluate_single_instance(instance): c = self.task.get_random_positive_instance(neg_value, column_name).T iteration += 1 # Stop if too many attempts if iteration > MAX_ITERATIONS: break # Align columns with the original instance negative = instance.to_frame() c.columns = negative.columns model = self.task.model # Perform binary search until instance distance is below gamma while distance_func(negative, c) > gamma: # Midpoint between the negative instance and current candidate new_neg = c.add(negative, axis=0) / 2 # Update either the negative or candidate based on model's prediction if model.predict_single(new_neg.T) == model.predict_single(negative.T): negative = new_neg else: c = new_neg # Format the resulting counterfactual ct = c.T res = model.predict_single(ct) ct["target"] = res ct["loss"] = distance_func(negative, c) return ct