Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Changelog
=========

0.16.0 (2024-04-??)
-------------------

* support explain_prediction with Grad-CAM for Keras 3.x, TF 2.x image classifiers
(use versions prior to 0.14 for TF 1.x support)

0.15.0 (2025-04-06)
-------------------

Expand Down
6 changes: 6 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ classifiers and explain their predictions.
.. image:: https://raw.githubusercontent.com/eli5-org/eli5/refs/heads/master/docs/source/static/readme-show-prediction.png
:alt: explain_prediction for text data

.. image:: https://raw.githubusercontent.com/eli5-org/eli5/refs/heads/master/docs/source/static/gradcam-catdog.png
:alt: explain_prediction for image data

.. image:: https://raw.githubusercontent.com/eli5-org/eli5/refs/heads/master/docs/source/static/readme-show-weights.png
:alt: explain_weights for text data

Expand All @@ -39,6 +42,8 @@ It provides support for the following machine learning frameworks and packages:
It also allows to debug scikit-learn pipelines which contain
HashingVectorizer, by undoing hashing.

* Keras_ - explain predictions of image classifiers via Grad-CAM visualizations.

* xgboost_ - show feature importances and explain predictions of XGBClassifier,
XGBRegressor and xgboost.Booster.

Expand Down Expand Up @@ -80,6 +85,7 @@ and formatting on a client.
.. _xgboost: https://github.com/dmlc/xgboost
.. _LightGBM: https://github.com/Microsoft/LightGBM
.. _Catboost: https://github.com/catboost/catboost
.. _Keras: https://keras.io/
.. _Permutation importance: https://eli5.readthedocs.io/en/latest/blackbox/permutation_importance.html
.. _Inspecting Black-Box Estimators: https://eli5.readthedocs.io/en/latest/blackbox/index.html
.. _OpenAI: https://github.com/openai/openai-python
Expand Down
3 changes: 0 additions & 3 deletions constraints-test.txt

This file was deleted.

293 changes: 179 additions & 114 deletions docs/source/_notebooks/keras-image-classifiers.rst

Large diffs are not rendered by default.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Diff not rendered.
Diff not rendered.
4 changes: 1 addition & 3 deletions docs/source/libraries/keras.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@
Keras
=====

Keras_ is "a high-level neural networks API, written in Python and capable of running on top of TensorFlow, CNTK, or Theano".
Keras_ is a high-level neural networks API, written in Python and capable of running on top of TensorFlow and other backends.

Keras can be used for many Machine Learning tasks, and it has support for both popular
and experimental neural network architectures.

Note: only TensorFlow 1.x is supported, recommended Keras version is 2.3.1 or earlier, and eli5 version 0.13 or earlier, as you can't install TensorFlow 1.x on Python 3.9+ which is required for eli5 0.14+

.. _Keras: https://keras.io/


Expand Down
2 changes: 1 addition & 1 deletion docs/update-notebooks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ mv ../notebooks/keras-image-classifiers.rst \
rm -r source/_notebooks/keras-image-classifiers_files
mv ../notebooks/keras-image-classifiers_files/ \
source/_notebooks/
sed -i 's&.. image:: keras-image-classifiers_files/&.. image:: ../_notebooks/keras-image-classifiers_files/&g' \
sed -i '' 's/image:: keras-image-classifiers_files/image:: ..\/_notebooks\/keras-image-classifiers_files/g' \
source/_notebooks/keras-image-classifiers.rst


Expand Down
50 changes: 19 additions & 31 deletions eli5/keras/explain_prediction.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from typing import Union, Optional, Callable, Tuple, List, TYPE_CHECKING
from typing import Union, Optional, Callable, TYPE_CHECKING
if TYPE_CHECKING:
import PIL

import numpy as np
import keras
import keras.backend as K
from keras.models import Model
from keras.layers import Layer
from keras.layers import (
Expand All @@ -30,13 +26,12 @@

# note that keras.models.Sequential subclasses keras.models.Model
@explain_prediction.register(Model)
def explain_prediction_keras(model, # type: Model
doc, # type: np.ndarray
targets=None, # type: Optional[list]
layer=None, # type: Optional[Union[int, str, Layer]]
def explain_prediction_keras(model: Model,
doc: np.ndarray,
targets: Optional[list] = None,
layer: Optional[Union[int, str, Layer]] = None,
image=None,
):
# type: (...) -> Explanation
) -> Explanation:
"""
Explain the prediction of a Keras classifier with the Grad-CAM technique.

Expand Down Expand Up @@ -133,7 +128,7 @@ def explain_prediction_keras_not_supported(model, doc):

def explain_prediction_keras_image(model,
doc,
image=None, # type: Optional['PIL.Image.Image']
image: Optional['PIL.Image.Image'] = None,
targets=None,
layer=None,
):
Expand Down Expand Up @@ -204,23 +199,20 @@ def explain_prediction_keras_image(model,
)


def _maybe_image(model, doc):
# type: (Model, np.ndarray) -> bool
def _maybe_image(model: Model, doc: np.ndarray) -> bool:
"""Decide whether we are dealing with a image-based explanation
based on heuristics on ``model`` and ``doc``."""
return _maybe_image_input(doc) and _maybe_image_model(model)


def _maybe_image_input(doc):
# type: (np.ndarray) -> bool
def _maybe_image_input(doc: np.ndarray) -> bool:
"""Decide whether ``doc`` represents an image input."""
rank = len(doc.shape)
# image with channels or without (spatial only)
return rank == 4 or rank == 3


def _maybe_image_model(model):
# type: (Model) -> bool
def _maybe_image_model(model: Model) -> bool:
"""Decide whether ``model`` is used for images."""
# FIXME: replace try-except with something else
try:
Expand All @@ -239,22 +231,19 @@ def _maybe_image_model(model):
)


def _is_possible_image_model_layer(model, layer):
# type: (Model, Layer) -> bool
def _is_possible_image_model_layer(model: Model, layer: Layer) -> bool:
"""Check that the given ``layer`` is usually used for images."""
return isinstance(layer, image_model_layers)


def _extract_image(doc):
# type: (np.ndarray) -> 'PIL.Image.Image'
def _extract_image(doc: np.ndarray) -> 'PIL.Image.Image':
"""Convert ``doc`` tensor to image."""
im_arr, = doc # rank 4 batch -> rank 3 single image
image = array_to_img(im_arr)
return image


def _validate_doc(model, doc):
# type: (Model, np.ndarray) -> None
def _validate_doc(model: Model, doc: np.ndarray) -> None:
"""
Check that the input ``doc`` is suitable for ``model``.
"""
Expand All @@ -277,8 +266,7 @@ def _validate_doc(model, doc):
'input: {}, doc: {}'.format(input_sh, doc_sh))


def _get_activation_layer(model, layer):
# type: (Model, Union[None, int, str, Layer]) -> Layer
def _get_activation_layer(model: Model, layer: Union[None, int, str, Layer]) -> Layer:
"""
Get an instance of the desired activation layer in ``model``,
as specified by ``layer``.
Expand Down Expand Up @@ -306,8 +294,7 @@ def _get_activation_layer(model, layer):
raise ValueError('Can not perform Grad-CAM on the retrieved activation layer')


def _search_layer_backwards(model, condition):
# type: (Model, Callable[[Model, Layer], bool]) -> Layer
def _search_layer_backwards(model: Model, condition: Callable[[Model, Layer], bool]) -> Layer:
"""
Search for a layer in ``model``, backwards (starting from the output layer),
checking if the layer is suitable with the callable ``condition``,
Expand All @@ -321,8 +308,7 @@ def _search_layer_backwards(model, condition):
raise ValueError('Could not find a suitable target layer automatically.')


def _is_suitable_activation_layer(model, layer):
# type: (Model, Layer) -> bool
def _is_suitable_activation_layer(model: Model, layer: Layer) -> bool:
"""
Check whether the layer ``layer`` matches what is required
by ``model`` to do Grad-CAM on ``layer``.
Expand All @@ -337,6 +323,8 @@ def _is_suitable_activation_layer(model, layer):
# check layer name

# a check that asks "can we resize this activation layer over the image?"
rank = len(layer.output_shape)
# Use the tensor shape of the layer's output
output_shape = layer.output.shape
rank = len(output_shape)
required_rank = len(model.input_shape)
return rank == required_rank
Loading