Tensorflow & Keras

Since keras is a higher-lever interface for tensorflow and nowadays part of tensorflow , we do not need to distinguish between keras and tensorflow models when using ceml.

Computing a counterfactual of a tensorflow/keras model is done by using the ceml.tfkeras.counterfactual.generate_counterfactual() function.

Note

We have to run in eager execution mode when computing a counterfactual! Since tensorflow 2, eager execution is enabled by default.

We must provide the tensorflow/keras model within a class that is derived from the ceml.model.model.ModelWithLoss class. In this class, we must overwrite the predict function and get_loss function which returns a loss that we want to use - a couple of differentiable loss functions are implemented in ceml.backend.tensorflow.costfunctions.

Besides the model, we must specify the input whose prediction we want to explain and the desired target prediction (prediction of the counterfactual). In addition we can restrict the features that can be used for computing a counterfactual, specify a regularization of the counterfactual and specifying the optimization algorithm used for computing a counterfactual.

A complete example of a softmax regression model using the negative-log-likelihood is given below:

 1#!/usr/bin/env python3
 2# -*- coding: utf-8 -*-
 3import tensorflow as tf
 4import numpy as np
 5from sklearn.datasets import load_iris
 6from sklearn.model_selection import train_test_split
 7from sklearn.metrics import accuracy_score
 8
 9from ceml.tfkeras import generate_counterfactual
10from ceml.backend.tensorflow.costfunctions import NegLogLikelihoodCost
11from ceml.model import ModelWithLoss
12
13
14# Neural network - Softmax regression
15class Model(ModelWithLoss):
16    def __init__(self, input_size, num_classes):
17        super(Model, self).__init__()
18
19        self.model = tf.keras.models.Sequential([
20            tf.keras.layers.Dense(num_classes, activation='softmax', input_shape=(input_size,))
21        ])
22    
23    def fit(self, x_train, y_train, num_epochs=800):
24        self.model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
25
26        self.model.fit(x_train, y_train, epochs=num_epochs, verbose=False)
27
28    def predict(self, x):
29        return np.argmax(self.model(x), axis=1)
30    
31    def predict_proba(self, x):
32        return self.model(x)
33    
34    def __call__(self, x):
35        return self.predict(x)
36
37    def get_loss(self, y_target, pred=None):
38        return NegLogLikelihoodCost(input_to_output=self.model.predict_proba, y_target=y_target)
39
40
41if __name__ == "__main__":
42    tf.random.set_seed(42)   # Fix random seed
43
44    # Load data
45    X, y = load_iris(return_X_y=True)
46
47    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=1)
48
49    # Create and fit model
50    model = Model(4, 3)
51    model.fit(X_train, y_train)
52
53    # Evaluation
54    y_pred = model.predict(X_test)
55    print("Accuracy: {0}".format(accuracy_score(y_test, y_pred)))
56
57    # Select a data point whose prediction has to be explained
58    x_orig = X_test[1,:]
59    print("Prediction on x: {0}".format(model.predict(np.array([x_orig]))))
60
61    # Whitelist of features we can use/change when computing the counterfactual
62    features_whitelist = None
63
64    # Compute counterfactual
65    optimizer = tf.compat.v1.train.GradientDescentOptimizer(learning_rate=1.0)    # Init optimization algorithm
66    optimizer_args = {"max_iter": 1000}
67
68    print("\nCompute counterfactual ....") 
69    print(generate_counterfactual(model, x_orig, y_target=0, features_whitelist=features_whitelist, regularization="l1", C=0.01, optimizer=optimizer, optimizer_args=optimizer_args))