Skip to content

Model explainer

Explainer

Parent class for the explainers

Source code in template_vision/monitoring/model_explainer.py
class Explainer:
    '''Parent class for the explainers'''

    def __init__(self, *args, **kwargs) -> None:
        '''Initialization of the parent class'''
        self.logger = logging.getLogger(__name__)

    def explain_instance(self, content: Image.Image, **kwargs) -> Any:
        '''Explains a prediction

        Args:
            content (Image.Image): Image to be explained
        Returns:
            (?): An explanation object
        '''
        raise NotImplementedError("'explain_instance' needs to be overridden")

    def explain_instance_as_html(self, content: Image.Image, **kwargs) -> str:
        '''Explains a prediction - returns an HTML object

        Args:
            content (Image.Image): Image to be explained
        Returns:
            str: An HTML code with the explanation
        '''
        raise NotImplementedError("'explain_instance_as_html' needs to be overridden")

    def explain_instance_as_json(self, content: Image.Image, **kwargs) -> Union[dict, list]:
        '''Explains a prediction - returns an JSON serializable object

        Args:
            content (str): Text to be explained
        Returns:
            str: A JSON serializable object with the explanation
        '''
        raise NotImplementedError("'explain_instance_as_json' needs to be overridden")

__init__(*args, **kwargs)

Initialization of the parent class

Source code in template_vision/monitoring/model_explainer.py
def __init__(self, *args, **kwargs) -> None:
    '''Initialization of the parent class'''
    self.logger = logging.getLogger(__name__)

explain_instance(content, **kwargs)

Explains a prediction

Parameters:

Name Type Description Default
content Image

Image to be explained

required

Returns: (?): An explanation object

Source code in template_vision/monitoring/model_explainer.py
def explain_instance(self, content: Image.Image, **kwargs) -> Any:
    '''Explains a prediction

    Args:
        content (Image.Image): Image to be explained
    Returns:
        (?): An explanation object
    '''
    raise NotImplementedError("'explain_instance' needs to be overridden")

explain_instance_as_html(content, **kwargs)

Explains a prediction - returns an HTML object

Parameters:

Name Type Description Default
content Image

Image to be explained

required

Returns: str: An HTML code with the explanation

Source code in template_vision/monitoring/model_explainer.py
def explain_instance_as_html(self, content: Image.Image, **kwargs) -> str:
    '''Explains a prediction - returns an HTML object

    Args:
        content (Image.Image): Image to be explained
    Returns:
        str: An HTML code with the explanation
    '''
    raise NotImplementedError("'explain_instance_as_html' needs to be overridden")

explain_instance_as_json(content, **kwargs)

Explains a prediction - returns an JSON serializable object

Parameters:

Name Type Description Default
content str

Text to be explained

required

Returns: str: A JSON serializable object with the explanation

Source code in template_vision/monitoring/model_explainer.py
def explain_instance_as_json(self, content: Image.Image, **kwargs) -> Union[dict, list]:
    '''Explains a prediction - returns an JSON serializable object

    Args:
        content (str): Text to be explained
    Returns:
        str: A JSON serializable object with the explanation
    '''
    raise NotImplementedError("'explain_instance_as_json' needs to be overridden")

LimeExplainer

Bases: Explainer

Lime Explainer wrapper class

Source code in template_vision/monitoring/model_explainer.py
class LimeExplainer(Explainer):
    '''Lime Explainer wrapper class'''

    def __init__(self, model: Type[ModelClassifierMixin], model_conf: dict) -> None:
        ''' Initialization

        Args:
            model: A model instance with predict & predict_proba functions, and list_classes attribute
            model_conf (dict): The model's configuration
        Raises:
            ValueError: If the provided model is not a classifier
            TypeError: If the provided model does not implement a `predict_proba` function
            TypeError: If the provided model does not have a `list_classes` attribute
        '''
        super().__init__()
        pred_proba_op = getattr(model, "predict_proba", None)

        # Check classifier
        if not model.model_type == 'classifier':
            raise ValueError("LimeExplainer only supported with classifier models")
        # Check needed methods
        if pred_proba_op is None or not callable(pred_proba_op):
            raise TypeError("The supplied model must implement a predict_proba() function")
        if getattr(model, "list_classes", None) is None:
            raise TypeError("The supplied model must have a list_classes attribute")

        self.model = model
        self.model_conf = model_conf
        self.class_names = self.model.list_classes
        # Our explainers will explain a prediction for a given class / label
        # These atributes are set on the fly
        self.current_class_index = 0
        # Create the explainer
        self.explainer = LimeImageExplainer()

    def classifier_fn(self, content_arrays: np.ndarray) -> np.ndarray:
        '''Function to get probabilities from a list of (not preprocessed) images

        Args:
            content_arrays (np.ndarray): images to be considered
        Returns:
            np.array: probabilities
        '''
        # Get preprocessor
        if 'preprocess_str' in self.model_conf.keys():
            preprocess_str = self.model_conf['preprocess_str']
        else:
            preprocess_str = "no_preprocess"
        preprocessor = preprocess.get_preprocessor(preprocess_str)
        # Preprocess images
        images = [Image.fromarray(img, 'RGB') for img in content_arrays]
        images_preprocessed = preprocessor(images)
        # Temporary folder
        with tempfile.TemporaryDirectory(dir=utils.get_data_path()) as tmp_folder:
            # Save images
            images_path = [os.path.join(tmp_folder, f'image_{i}.png') for i in range(len(images_preprocessed))]
            for i, img_preprocessed in enumerate(images_preprocessed):
                img_preprocessed.save(images_path[i], format='PNG')
            # Get predictions
            df = pd.DataFrame({'file_path': images_path})
            probas = self.model.predict_proba(df)
        # Return probas
        return probas

    def explain_instance(self, content: Image.Image, class_index: Union[int, None] = None,
                         num_samples: int = 100, batch_size: int = 100, hide_color=0, **kwargs):
        '''Explains a prediction

        This function calls the Lime module. It generates neighborhood data by randomly perturbing features from the instance.
        Then, it learns locally weighted linear models on this neighborhood data to explain each of the classes in an interpretable way.

        Args:
            img (Image.Image): Image to be explained
        Kwargs:
            class_index (int): for classification only. Class or label index to be considered.
            num_samples (int): size of the neighborhood to learn the linear model (cf. Lime documentation)
            batch_size (int): classifier_fn will be called on batches of this size (cf. Lime documentation)
            hide_color (?): TODO
        Returns:
            (?): An explanation object
        '''
        # Set index
        if class_index is not None:
            self.current_class_index = class_index
        else:
            self.current_class_index = 1  # Def to 1
        # Get explanations (images must be convert into rgb, then into np array)
        return self.explainer.explain_instance(np.array(content.convert('RGB')), self.classifier_fn,
                                               labels=(self.current_class_index,),
                                               num_samples=num_samples, batch_size=batch_size,
                                               hide_color=hide_color, top_labels=None)

    def explain_instance_as_html(self, content: Image.Image, **kwargs) -> str:
        '''Explains a prediction - returns an HTML object
        ** NOT IMPLEMENTED **
        '''
        raise NotImplementedError("'explain_instance_as_html' is not defined for LimeExplainer")

    def explain_instance_as_json(self, content: Image.Image, **kwargs) -> Union[dict, list]:
        '''Explains a prediction - returns an JSON serializable object
        ** NOT IMPLEMENTED **
        '''
        raise NotImplementedError("'explain_instance_as_json' is not defined for LimeExplainer")

__init__(model, model_conf)

Initialization

Parameters:

Name Type Description Default
model Type[ModelClassifierMixin]

A model instance with predict & predict_proba functions, and list_classes attribute

required
model_conf dict

The model's configuration

required

Raises: ValueError: If the provided model is not a classifier TypeError: If the provided model does not implement a predict_proba function TypeError: If the provided model does not have a list_classes attribute

Source code in template_vision/monitoring/model_explainer.py
def __init__(self, model: Type[ModelClassifierMixin], model_conf: dict) -> None:
    ''' Initialization

    Args:
        model: A model instance with predict & predict_proba functions, and list_classes attribute
        model_conf (dict): The model's configuration
    Raises:
        ValueError: If the provided model is not a classifier
        TypeError: If the provided model does not implement a `predict_proba` function
        TypeError: If the provided model does not have a `list_classes` attribute
    '''
    super().__init__()
    pred_proba_op = getattr(model, "predict_proba", None)

    # Check classifier
    if not model.model_type == 'classifier':
        raise ValueError("LimeExplainer only supported with classifier models")
    # Check needed methods
    if pred_proba_op is None or not callable(pred_proba_op):
        raise TypeError("The supplied model must implement a predict_proba() function")
    if getattr(model, "list_classes", None) is None:
        raise TypeError("The supplied model must have a list_classes attribute")

    self.model = model
    self.model_conf = model_conf
    self.class_names = self.model.list_classes
    # Our explainers will explain a prediction for a given class / label
    # These atributes are set on the fly
    self.current_class_index = 0
    # Create the explainer
    self.explainer = LimeImageExplainer()

classifier_fn(content_arrays)

Function to get probabilities from a list of (not preprocessed) images

Parameters:

Name Type Description Default
content_arrays ndarray

images to be considered

required

Returns: np.array: probabilities

Source code in template_vision/monitoring/model_explainer.py
def classifier_fn(self, content_arrays: np.ndarray) -> np.ndarray:
    '''Function to get probabilities from a list of (not preprocessed) images

    Args:
        content_arrays (np.ndarray): images to be considered
    Returns:
        np.array: probabilities
    '''
    # Get preprocessor
    if 'preprocess_str' in self.model_conf.keys():
        preprocess_str = self.model_conf['preprocess_str']
    else:
        preprocess_str = "no_preprocess"
    preprocessor = preprocess.get_preprocessor(preprocess_str)
    # Preprocess images
    images = [Image.fromarray(img, 'RGB') for img in content_arrays]
    images_preprocessed = preprocessor(images)
    # Temporary folder
    with tempfile.TemporaryDirectory(dir=utils.get_data_path()) as tmp_folder:
        # Save images
        images_path = [os.path.join(tmp_folder, f'image_{i}.png') for i in range(len(images_preprocessed))]
        for i, img_preprocessed in enumerate(images_preprocessed):
            img_preprocessed.save(images_path[i], format='PNG')
        # Get predictions
        df = pd.DataFrame({'file_path': images_path})
        probas = self.model.predict_proba(df)
    # Return probas
    return probas

explain_instance(content, class_index=None, num_samples=100, batch_size=100, hide_color=0, **kwargs)

Explains a prediction

This function calls the Lime module. It generates neighborhood data by randomly perturbing features from the instance. Then, it learns locally weighted linear models on this neighborhood data to explain each of the classes in an interpretable way.

Parameters:

Name Type Description Default
img Image

Image to be explained

required

Kwargs: class_index (int): for classification only. Class or label index to be considered. num_samples (int): size of the neighborhood to learn the linear model (cf. Lime documentation) batch_size (int): classifier_fn will be called on batches of this size (cf. Lime documentation) hide_color (?): TODO Returns: (?): An explanation object

Source code in template_vision/monitoring/model_explainer.py
def explain_instance(self, content: Image.Image, class_index: Union[int, None] = None,
                     num_samples: int = 100, batch_size: int = 100, hide_color=0, **kwargs):
    '''Explains a prediction

    This function calls the Lime module. It generates neighborhood data by randomly perturbing features from the instance.
    Then, it learns locally weighted linear models on this neighborhood data to explain each of the classes in an interpretable way.

    Args:
        img (Image.Image): Image to be explained
    Kwargs:
        class_index (int): for classification only. Class or label index to be considered.
        num_samples (int): size of the neighborhood to learn the linear model (cf. Lime documentation)
        batch_size (int): classifier_fn will be called on batches of this size (cf. Lime documentation)
        hide_color (?): TODO
    Returns:
        (?): An explanation object
    '''
    # Set index
    if class_index is not None:
        self.current_class_index = class_index
    else:
        self.current_class_index = 1  # Def to 1
    # Get explanations (images must be convert into rgb, then into np array)
    return self.explainer.explain_instance(np.array(content.convert('RGB')), self.classifier_fn,
                                           labels=(self.current_class_index,),
                                           num_samples=num_samples, batch_size=batch_size,
                                           hide_color=hide_color, top_labels=None)

explain_instance_as_html(content, **kwargs)

Explains a prediction - returns an HTML object ** NOT IMPLEMENTED **

Source code in template_vision/monitoring/model_explainer.py
def explain_instance_as_html(self, content: Image.Image, **kwargs) -> str:
    '''Explains a prediction - returns an HTML object
    ** NOT IMPLEMENTED **
    '''
    raise NotImplementedError("'explain_instance_as_html' is not defined for LimeExplainer")

explain_instance_as_json(content, **kwargs)

Explains a prediction - returns an JSON serializable object ** NOT IMPLEMENTED **

Source code in template_vision/monitoring/model_explainer.py
def explain_instance_as_json(self, content: Image.Image, **kwargs) -> Union[dict, list]:
    '''Explains a prediction - returns an JSON serializable object
    ** NOT IMPLEMENTED **
    '''
    raise NotImplementedError("'explain_instance_as_json' is not defined for LimeExplainer")