Advanced¶
CEML can be easily extended and all major components can be customized to fit the users needs.
Below is a (non-exhaustive) list of some (common) use cases:
Custom regularization¶
Instead of using one of the predefined regularizations, we can pass a custom regularization to ceml.sklearn.models.generate_counterfactual()
.
All regularization implementations must be classes derived from ceml.costfunctions.costfunctions.CostFunction
. In case of scikit-learn, if we want to use a gradient based optimization algorithm, we must derive from ceml.backend.jax.costfunctions.costfunctions.CostFunctionDifferentiableJax
- note that ceml.backend.jax.costfunctions.costfunctions.CostFunctionDifferentiableJax
is already dervied from ceml.costfunctions.costfunctions.CostFunction
.
Note
For tensorflow/keras or PyTorch models the base classes are ceml.backend.tensorflow.costfunctions.costfunctions.CostFunctionDifferentiableTf
and ceml.backend.torch.costfunctions.costfunctions.CostFunctionDifferentiableTorch
.
The computation of the regularization itself must be implemented in the score_impl function.
A complete example of a re-implementation of the l2-regularization is given below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | #!/usr/bin/env python3
# -*- coding: utf-8 -*-
import jax.numpy as npx
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.naive_bayes import GaussianNB
from ceml.sklearn import generate_counterfactual
from ceml.backend.jax.costfunctions import CostFunctionDifferentiableJax
# Custom implementation of the l2-regularization. Note that this regularization is differentiable
class MyRegularization(CostFunctionDifferentiableJax):
def __init__(self, x_orig):
self.x_orig = x_orig
super(MyRegularization, self).__init__()
def score_impl(self, x):
return npx.sum(npx.square(x - self.x_orig)) # Note: This expression must be written in jax and it must be differentiable!
if __name__ == "__main__":
# Load data
X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=4242)
# Whitelist of features - list of features we can change/use when computing a counterfactual
features_whitelist = None # All features can be used.
# Create and fit the model
model = GaussianNB() # Note that ceml requires: multi_class='multinomial'
model.fit(X_train, y_train)
# Select data point for explaining its prediction
x = X_test[1,:]
print("Prediction on x: {0}".format(model.predict([x])))
# Create custom regularization function
regularization = MyRegularization(x)
# Compute counterfactual
print("\nCompute counterfactual ....")
print(generate_counterfactual(model, x, y_target=0, features_whitelist=features_whitelist, regularization=regularization, optimizer="bfgs"))
|
Custom loss function¶
In order to use a custom loss function we have to do three things:
Implement the loss function. This is the same as implementing a custom regularization - a regularization is a loss function that works on the input rather than on the output.
Derive a child class from the model class and overwrite the get_loss function to use our custom loss function.
Derive a child class from the counterfactual class of the model and overwrite the rebuild_model function to use our model from the previous step.
A complete example of using a custom loss for a linear regression model is given below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | #!/usr/bin/env python3
# -*- coding: utf-8 -*-
import numpy as np
import jax.numpy as npx
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.linear_model import Ridge
from ceml.sklearn import generate_counterfactual
from ceml.sklearn import LinearRegression, LinearRegressionCounterfactual
from ceml.backend.jax.costfunctions import CostFunctionDifferentiableJax
# Custom implementation of the l2-regularization. Note that this regularization is differentiable.
class MyLoss(CostFunctionDifferentiableJax):
def __init__(self, input_to_output, y_target):
self.y_target = y_target
super(MyLoss, self).__init__(input_to_output)
def score_impl(self, y):
return npx.abs(y - y_target)**4
# Derive a new class from ceml.sklearn.linearregression.LinearRegression and overwrite the get_loss method to use our custom loss MyLoss
class LinearRegressionWithMyLoss(LinearRegression):
def __init__(self, model):
super(LinearRegressionWithMyLoss, self).__init__(model)
def get_loss(self, y_target, pred=None):
if pred is None:
return MyLoss(self.predict, y_target)
else:
return MyLoss(pred, y_target)
# Derive a new class from ceml.sklearn.linearregression.LinearRegressionCounterfactual that uses our new linear regression wrapper LinearRegressionWithMyLoss for computing counterfactuals
class MyLinearRegressionCounterfactual(LinearRegressionCounterfactual):
def __init__(self, model):
super(MyLinearRegressionCounterfactual, self).__init__(model)
def rebuild_model(self, model):
return LinearRegressionWithMyLoss(model)
if __name__ == "__main__":
# Load data
X, y = load_boston(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=4242)
# Whitelist of features - list of features we can change/use when computing a counterfactual
features_whitelist = None # All features can be used.
# Create and fit model
model = Ridge()
model.fit(X_train, y_train)
# Select data point for explaining its prediction
x = X_test[1,:]
print("Prediction on x: {0}".format(model.predict([x])))
# Compute counterfactual
print("\nCompute counterfactual ....")
y_target = 25.0
done = lambda z: np.abs(y_target - z) <= 0.5 # Since we might not be able to achieve `y_target` exactly, we tell ceml that we are happy if we do not deviate more than 0.5 from it.
cf = MyLinearRegressionCounterfactual(model) # Since we are using our own loss function, we can no longer use standard method generate_counterfactual
print(cf.compute_counterfactual(x, y_target=y_target, features_whitelist=features_whitelist, regularization="l2", C=1.0, optimizer="bfgs", done=done))
|
Add a custom optimizer¶
We can use a custom optimization method by:
Dervice a new class from
ceml.optim.optimizer.Optimizer
and implement the custom optimization method.Create a new instance of this class and pass it as the argument for the optimizer parameter to the function
ceml.sklearn.models.generate_counterfactual()
(or any other function that computes a counterfactual).
A complete example of using a custom optimization method for computing counterfactuals from a logistic regression model is given below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | #!/usr/bin/env python3
# -*- coding: utf-8 -*-
import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from scipy.optimize import minimize
from ceml.sklearn import generate_counterfactual
from ceml.optim import Optimizer
# Custom optimization method that simply calls the BFGS optimizer from scipy
class MyOptimizer(Optimizer):
def __init__(self):
self.f = None
self.f_grad = None
self.x0 = None
self.tol = None
self.max_iter = None
super(MyOptimizer, self).__init__()
def init(self, f, f_grad, x0, tol=None, max_iter=None):
self.f = f
self.f_grad = f_grad
self.x0 = x0
self.tol = tol
self.max_iter = max_iter
def is_grad_based(self):
return True
def __call__(self):
optimum = minimize(fun=self.f, x0=self.x0, jac=self.f_grad, tol=self.tol, options={'maxiter': self.max_iter}, method="BFGS")
return np.array(optimum["x"])
if __name__ == "__main__":
# Load data
X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=4242)
# Create and fit model
model = LogisticRegression(solver='lbfgs', multi_class='multinomial')
model.fit(X_train, y_train)
# Select data point for explaining its prediction
x = X_test[1,:]
print("Prediction on x: {0}".format(model.predict([x])))
# Compute counterfactual by using our custom optimizer 'MyOptimizer'
print("\nCompute counterfactual ....")
print(generate_counterfactual(model, x, y_target=0, optimizer=MyOptimizer(), features_whitelist=None, regularization="l1", C=0.5))
|