Module ethik
Installation
Via PyPI
>>> pip install ethik
Via GitHub for the latest development version
>>> pip install git+<https://github.com/XAI-ANITI/ethik.git>
>>> # Or through SSH:
>>> pip install git+ssh://git@github.com/XAI-ANITI/ethik.git
Development installation
>>> git clone <https://github.com/MaxHalford/ethik>
>>> cd ethik
>>> make install_dev
Expand source code
"""
## Installation
<div style="display: flex; align-items: center; margin-bottom: 20px;">
<i class="material-icons" style="margin-right: 10px; color: red;">warning</i> Python 3.6 or above is required
</div>
**Via [PyPI](https://pypi.org/project/ethik/)**
```shell
>>> pip install ethik
```
**Via GitHub for the latest development version**
```shell
>>> pip install git+https://github.com/XAI-ANITI/ethik.git
>>> # Or through SSH:
>>> pip install git+ssh://git@github.com/XAI-ANITI/ethik.git
```
**Development installation**
```shell
>>> git clone https://github.com/MaxHalford/ethik
>>> cd ethik
>>> make install_dev
```
"""
from . import datasets
from .__version__ import __version__
from .classification_explainer import ClassificationExplainer
from .regression_explainer import RegressionExplainer
from .image_classification_explainer import ImageClassificationExplainer
from .utils import extract_category
__all__ = [
"__version__",
"datasets",
"RegressionExplainer",
"ClassificationExplainer",
"ImageClassificationExplainer",
"extract_category",
]
Sub-modules
Functions
def extract_category(X, cat)
-
Expand source code
def extract_category(X, cat): return pd.get_dummies(X)[cat].rename(f"{X.name}={cat}")
Classes
class ClassificationExplainer (alpha=0.05, n_taus=41, n_samples=1, sample_frac=0.8, conf_level=0.05, max_iterations=15, tol=0.0001, n_jobs=1, memoize=False, verbose=True)
-
Explains the influence of features on model predictions and performance.
Parameters
alpha
:float
- A
float
between0
and0.5
which indicates by how close theCacheExplainer
should look at extreme values of a distribution. The closer to zero, the more so extreme values will be accounted for. The default is0.05
which means that all values beyond the 5th and 95th quantiles are ignored. n_taus
:int
- The number of τ values to consider. The results will be more fine-grained the
higher this value is. However the computation time increases linearly with
n_taus
. The default is41
and corresponds to each τ being separated by it's neighbors by0.05
. n_samples
:int
- The number of samples to use for the confidence interval.
If
1
, the default, no confidence interval is computed. sample_frac
:float
- The proportion of lines in the dataset sampled to
generate the samples for the confidence interval. If
n_samples
is1
, no confidence interval is computed and the whole dataset is used. Default is0.8
. conf_level
:float
- A
float
between0
and0.5
which indicates the quantile used for the confidence interval. Default is0.05
, which means that the confidence interval contains the data between the 5th and 95th quantiles. max_iterations
:int
- The maximum number of iterations used when applying the Newton step
of the optimization procedure. Default is
5
. tol
:float
- The bottom threshold for the gradient of the optimization
procedure. When reached, the procedure stops. Otherwise, a warning
is raised about the fact that the optimization did not converge.
Default is
1e-4
. n_jobs
:int
- The number of jobs to use for parallel computations. See
joblib.Parallel()
. Default is-1
. memoize
:bool
- Indicates whether or not memoization should be used or not. If
True
, then intermediate results will be stored in order to avoid recomputing results that can be reused by successively called methods. For example, if you callplot_influence
followed byplot_influence_ranking
andmemoize
isTrue
, then the intermediate results required byplot_influence
will be reused forplot_influence_ranking
. Memoization is turned off by default because it can lead to unexpected behavior depending on your usage. verbose
:bool
- Whether or not to show progress bars during
computations. Default is
True
.
Expand source code
class ClassificationExplainer(CacheExplainer): def plot_influence( self, X_test, y_pred, colors=None, yrange=None, size=None, constraints=None ): """Plot the influence for the features in `X_test`. See `ethik.cache_explainer.CacheExplainer.plot_influence()`. """ if yrange is None: yrange = [0, 1] X_test = pd.DataFrame(to_pandas(X_test)) y_pred = pd.DataFrame(to_pandas(y_pred)) if len(y_pred.columns) == 1: return super().plot_influence( X_test=X_test, y_pred=y_pred.iloc[:, 0], colors=colors, yrange=yrange, size=size, constraints=constraints, ) if colors is None: features = X_test.columns # Skip the lightest color as it is too light scale = cl.interp(cl.scales["10"]["qual"]["Paired"], len(features) + 1)[1:] colors = {feat: scale[i] for i, feat in enumerate(features)} labels = y_pred.columns plots = [] for label in labels: plots.append( super().plot_influence( X_test=X_test, y_pred=y_pred[label], colors=colors, yrange=yrange, constraints=constraints, ) ) fig = make_subplots(rows=len(labels), cols=1, shared_xaxes=True) for ilabel, (label, plot) in enumerate(zip(labels, plots)): fig.update_layout({f"yaxis{ilabel+1}": dict(title=f"Average {label}")}) for trace in plot["data"]: trace["showlegend"] = ilabel == 0 and trace["showlegend"] trace["legendgroup"] = trace["name"] fig.add_trace(trace, row=ilabel + 1, col=1) fig.update_xaxes( nticks=5, showline=True, showgrid=True, zeroline=False, linecolor="black", gridcolor="#eee", ) fig.update_yaxes( range=yrange, showline=True, showgrid=True, linecolor="black", gridcolor="#eee", ) fig.update_layout( { f"xaxis{len(labels)}": dict( title="tau" if len(X_test.columns) > 1 else f"Average {X_test.columns[0]}" ) } ) set_fig_size(fig, size) return fig def plot_influence_2d( self, X_test, y_pred, z_range=None, colorscale=None, size=None, constraints=None ): """Plot the combined influence for the features in `X_test`. See `ethik.cache_explainer.CacheExplainer.plot_influence_2d()`. """ if z_range is None: z_range = [0, 1] X_test = pd.DataFrame(to_pandas(X_test)) y_pred = pd.DataFrame(to_pandas(y_pred)) if len(y_pred.columns) == 1: return super().plot_influence_2d( X_test=X_test, y_pred=y_pred, z_range=z_range, colorscale=colorscale, size=size, constraints=constraints, ) labels = y_pred.columns plots = [] for label in labels: plots.append( super().plot_influence_2d( X_test=X_test, y_pred=y_pred[label], z_range=z_range, colorscale=colorscale, size=size, constraints=constraints, ) ) fig = make_subplots( rows=len(labels), cols=1, shared_xaxes=True, subplot_titles=[p["data"][0]["colorbar"]["title"].text for p in plots], ) for ilabel, (label, plot) in enumerate(zip(labels, plots)): fig.update_layout({f"yaxis{ilabel+1}": dict(title=plot.layout.yaxis.title)}) heatmap, ds_mean = plot["data"] heatmap["showscale"] = ilabel == 0 heatmap["colorbar"]["title"] = "" heatmap["hoverinfo"] = "x+y+z" fig.add_trace(heatmap, row=ilabel + 1, col=1) fig.add_trace(ds_mean, row=ilabel + 1, col=1) fig.update_xaxes( showline=True, showgrid=True, zeroline=False, mirror=True, linecolor="black", gridcolor="#eee", ) fig.update_yaxes( showline=True, showgrid=True, mirror=True, linecolor="black", gridcolor="#eee", ) fig.update_layout( { f"xaxis{len(labels)}": dict(title=plots[0].layout.xaxis.title), "title": plots[0].layout.title, } ) set_fig_size(fig, size) return fig def plot_distributions( self, feature_values, y_pred=None, bins=None, show_hist=False, show_curve=True, targets=None, colors=None, dataset_color="black", size=None, ): """Plot the stressed distribution of `feature_values` or `y_pred` if specified for each mean of `feature_values` in `targets`. See `ethik.base_explainer.BaseExplainer.plot_distributions()`. """ if y_pred is None: return super().plot_distributions( feature_values=feature_values, bins=bins, show_hist=show_hist, show_curve=show_curve, targets=targets, colors=colors, dataset_color=dataset_color, size=size, ) y_pred = pd.DataFrame(to_pandas(y_pred)) if len(y_pred.columns) == 1: return super().plot_distributions( feature_values=feature_values, y_pred=y_pred.iloc[:, 0], bins=bins, show_hist=show_hist, show_curve=show_curve, targets=targets, colors=colors, dataset_color=dataset_color, size=size, ) labels = y_pred.columns plots = [] for label in labels: plots.append( super().plot_distributions( feature_values=feature_values, y_pred=y_pred[label], bins=bins, show_hist=show_hist, show_curve=show_curve, targets=targets, colors=colors, dataset_color=dataset_color, ) ) fig = make_subplots(rows=len(labels), cols=1, shared_xaxes=True) for ilabel, (label, plot) in enumerate(zip(labels, plots)): fig.update_layout( { f"xaxis{ilabel+1}": dict(title=label), f"yaxis{ilabel+1}": dict(title=f"Probability density"), } ) for itrace, trace in enumerate(plot["data"]): trace["legendgroup"] = itrace fig.add_trace(trace, row=ilabel + 1, col=1) fig.update_xaxes( showline=True, showgrid=True, zeroline=False, linecolor="black", gridcolor="#eee", ) fig.update_yaxes( showline=True, showgrid=True, linecolor="black", gridcolor="#eee" ) set_fig_size(fig, size) return fig def plot_influence_comparison( self, X_test, y_pred, reference, compared, colors=None, yrange=None, size=None ): """Plot the influence of features in `X_test` on `y_pred` for the individual `compared` compared to `reference`. Basically, we look at how the model would behave if the average individual were `compared` and take the difference with what the output would be if the average were `reference`. See `ethik.base_explainer.BaseExplainer.plot_influence_comparison()`. """ if yrange is None: yrange = [-1, 1] X_test = pd.DataFrame(to_pandas(X_test)) y_pred = pd.DataFrame(to_pandas(y_pred)) features = X_test.columns labels = y_pred.columns if len(labels) == 1: return super().plot_influence_comparison( X_test, y_pred.iloc[:, 0], reference, compared, colors=colors, yrange=yrange, size=size, ) if colors is None: # Skip the lightest color as it is too light scale = cl.interp(cl.scales["10"]["qual"]["Paired"], len(features) + 1)[1:] colors = {feat: scale[i] for i, feat in enumerate(features)} plots = [] for label in labels: plots.append( super().plot_influence_comparison( X_test, y_pred[label], reference, compared, colors=colors ) ) fig = make_subplots(rows=len(labels), cols=1, shared_xaxes=True) title = None shapes = [] for ilabel, (label, plot) in enumerate(zip(labels, plots)): fig.update_layout({f"yaxis{ilabel+1}": dict(title=label)}) for trace in plot["data"]: shapes.append( go.layout.Shape( type="line", x0=0, y0=-0.5, x1=0, y1=len(features) - 0.5, yref=f"y{ilabel+1}", line=dict(color="black", width=1), ) ) fig.add_trace(trace, row=ilabel + 1, col=1) fig.update_xaxes( range=yrange, showline=True, linecolor="black", linewidth=1, zeroline=False, showgrid=True, gridcolor="#eee", side="top", fixedrange=True, showticklabels=True, ) fig.update_layout( showlegend=False, xaxis1=dict(title=plots[0].layout.xaxis.title), shapes=shapes, ) set_fig_size( fig, size, width=500, height=100 + 60 * len(features) + 30 * len(labels) ) return fig
Ancestors
Methods
def plot_distributions(self, feature_values, y_pred=None, bins=None, show_hist=False, show_curve=True, targets=None, colors=None, dataset_color='black', size=None)
-
Plot the stressed distribution of
feature_values
ory_pred
if specified for each mean offeature_values
intargets
.Expand source code
def plot_distributions( self, feature_values, y_pred=None, bins=None, show_hist=False, show_curve=True, targets=None, colors=None, dataset_color="black", size=None, ): """Plot the stressed distribution of `feature_values` or `y_pred` if specified for each mean of `feature_values` in `targets`. See `ethik.base_explainer.BaseExplainer.plot_distributions()`. """ if y_pred is None: return super().plot_distributions( feature_values=feature_values, bins=bins, show_hist=show_hist, show_curve=show_curve, targets=targets, colors=colors, dataset_color=dataset_color, size=size, ) y_pred = pd.DataFrame(to_pandas(y_pred)) if len(y_pred.columns) == 1: return super().plot_distributions( feature_values=feature_values, y_pred=y_pred.iloc[:, 0], bins=bins, show_hist=show_hist, show_curve=show_curve, targets=targets, colors=colors, dataset_color=dataset_color, size=size, ) labels = y_pred.columns plots = [] for label in labels: plots.append( super().plot_distributions( feature_values=feature_values, y_pred=y_pred[label], bins=bins, show_hist=show_hist, show_curve=show_curve, targets=targets, colors=colors, dataset_color=dataset_color, ) ) fig = make_subplots(rows=len(labels), cols=1, shared_xaxes=True) for ilabel, (label, plot) in enumerate(zip(labels, plots)): fig.update_layout( { f"xaxis{ilabel+1}": dict(title=label), f"yaxis{ilabel+1}": dict(title=f"Probability density"), } ) for itrace, trace in enumerate(plot["data"]): trace["legendgroup"] = itrace fig.add_trace(trace, row=ilabel + 1, col=1) fig.update_xaxes( showline=True, showgrid=True, zeroline=False, linecolor="black", gridcolor="#eee", ) fig.update_yaxes( showline=True, showgrid=True, linecolor="black", gridcolor="#eee" ) set_fig_size(fig, size) return fig
def plot_influence(self, X_test, y_pred, colors=None, yrange=None, size=None, constraints=None)
-
Plot the influence for the features in
X_test
.Expand source code
def plot_influence( self, X_test, y_pred, colors=None, yrange=None, size=None, constraints=None ): """Plot the influence for the features in `X_test`. See `ethik.cache_explainer.CacheExplainer.plot_influence()`. """ if yrange is None: yrange = [0, 1] X_test = pd.DataFrame(to_pandas(X_test)) y_pred = pd.DataFrame(to_pandas(y_pred)) if len(y_pred.columns) == 1: return super().plot_influence( X_test=X_test, y_pred=y_pred.iloc[:, 0], colors=colors, yrange=yrange, size=size, constraints=constraints, ) if colors is None: features = X_test.columns # Skip the lightest color as it is too light scale = cl.interp(cl.scales["10"]["qual"]["Paired"], len(features) + 1)[1:] colors = {feat: scale[i] for i, feat in enumerate(features)} labels = y_pred.columns plots = [] for label in labels: plots.append( super().plot_influence( X_test=X_test, y_pred=y_pred[label], colors=colors, yrange=yrange, constraints=constraints, ) ) fig = make_subplots(rows=len(labels), cols=1, shared_xaxes=True) for ilabel, (label, plot) in enumerate(zip(labels, plots)): fig.update_layout({f"yaxis{ilabel+1}": dict(title=f"Average {label}")}) for trace in plot["data"]: trace["showlegend"] = ilabel == 0 and trace["showlegend"] trace["legendgroup"] = trace["name"] fig.add_trace(trace, row=ilabel + 1, col=1) fig.update_xaxes( nticks=5, showline=True, showgrid=True, zeroline=False, linecolor="black", gridcolor="#eee", ) fig.update_yaxes( range=yrange, showline=True, showgrid=True, linecolor="black", gridcolor="#eee", ) fig.update_layout( { f"xaxis{len(labels)}": dict( title="tau" if len(X_test.columns) > 1 else f"Average {X_test.columns[0]}" ) } ) set_fig_size(fig, size) return fig
def plot_influence_2d(self, X_test, y_pred, z_range=None, colorscale=None, size=None, constraints=None)
-
Plot the combined influence for the features in
X_test
.Expand source code
def plot_influence_2d( self, X_test, y_pred, z_range=None, colorscale=None, size=None, constraints=None ): """Plot the combined influence for the features in `X_test`. See `ethik.cache_explainer.CacheExplainer.plot_influence_2d()`. """ if z_range is None: z_range = [0, 1] X_test = pd.DataFrame(to_pandas(X_test)) y_pred = pd.DataFrame(to_pandas(y_pred)) if len(y_pred.columns) == 1: return super().plot_influence_2d( X_test=X_test, y_pred=y_pred, z_range=z_range, colorscale=colorscale, size=size, constraints=constraints, ) labels = y_pred.columns plots = [] for label in labels: plots.append( super().plot_influence_2d( X_test=X_test, y_pred=y_pred[label], z_range=z_range, colorscale=colorscale, size=size, constraints=constraints, ) ) fig = make_subplots( rows=len(labels), cols=1, shared_xaxes=True, subplot_titles=[p["data"][0]["colorbar"]["title"].text for p in plots], ) for ilabel, (label, plot) in enumerate(zip(labels, plots)): fig.update_layout({f"yaxis{ilabel+1}": dict(title=plot.layout.yaxis.title)}) heatmap, ds_mean = plot["data"] heatmap["showscale"] = ilabel == 0 heatmap["colorbar"]["title"] = "" heatmap["hoverinfo"] = "x+y+z" fig.add_trace(heatmap, row=ilabel + 1, col=1) fig.add_trace(ds_mean, row=ilabel + 1, col=1) fig.update_xaxes( showline=True, showgrid=True, zeroline=False, mirror=True, linecolor="black", gridcolor="#eee", ) fig.update_yaxes( showline=True, showgrid=True, mirror=True, linecolor="black", gridcolor="#eee", ) fig.update_layout( { f"xaxis{len(labels)}": dict(title=plots[0].layout.xaxis.title), "title": plots[0].layout.title, } ) set_fig_size(fig, size) return fig
def plot_influence_comparison(self, X_test, y_pred, reference, compared, colors=None, yrange=None, size=None)
-
Plot the influence of features in
X_test
ony_pred
for the individualcompared
compared toreference
. Basically, we look at how the model would behave if the average individual werecompared
and take the difference with what the output would be if the average werereference
.Expand source code
def plot_influence_comparison( self, X_test, y_pred, reference, compared, colors=None, yrange=None, size=None ): """Plot the influence of features in `X_test` on `y_pred` for the individual `compared` compared to `reference`. Basically, we look at how the model would behave if the average individual were `compared` and take the difference with what the output would be if the average were `reference`. See `ethik.base_explainer.BaseExplainer.plot_influence_comparison()`. """ if yrange is None: yrange = [-1, 1] X_test = pd.DataFrame(to_pandas(X_test)) y_pred = pd.DataFrame(to_pandas(y_pred)) features = X_test.columns labels = y_pred.columns if len(labels) == 1: return super().plot_influence_comparison( X_test, y_pred.iloc[:, 0], reference, compared, colors=colors, yrange=yrange, size=size, ) if colors is None: # Skip the lightest color as it is too light scale = cl.interp(cl.scales["10"]["qual"]["Paired"], len(features) + 1)[1:] colors = {feat: scale[i] for i, feat in enumerate(features)} plots = [] for label in labels: plots.append( super().plot_influence_comparison( X_test, y_pred[label], reference, compared, colors=colors ) ) fig = make_subplots(rows=len(labels), cols=1, shared_xaxes=True) title = None shapes = [] for ilabel, (label, plot) in enumerate(zip(labels, plots)): fig.update_layout({f"yaxis{ilabel+1}": dict(title=label)}) for trace in plot["data"]: shapes.append( go.layout.Shape( type="line", x0=0, y0=-0.5, x1=0, y1=len(features) - 0.5, yref=f"y{ilabel+1}", line=dict(color="black", width=1), ) ) fig.add_trace(trace, row=ilabel + 1, col=1) fig.update_xaxes( range=yrange, showline=True, linecolor="black", linewidth=1, zeroline=False, showgrid=True, gridcolor="#eee", side="top", fixedrange=True, showticklabels=True, ) fig.update_layout( showlegend=False, xaxis1=dict(title=plots[0].layout.xaxis.title), shapes=shapes, ) set_fig_size( fig, size, width=500, height=100 + 60 * len(features) + 30 * len(labels) ) return fig
Inherited members
CacheExplainer
:CAT_COL_SEP
compare_influence
compare_performance
compute_distributions
compute_weights
explain_influence
explain_performance
get_metric_name
plot_cumulative_weights
plot_influence_ranking
plot_performance
plot_performance_2d
plot_performance_comparison
plot_performance_ranking
plot_weight_distribution
rank_by_influence
rank_by_performance
class ImageClassificationExplainer (alpha=0.05, max_iterations=10, tol=0.0001, n_jobs=-1, memoize=True, verbose=True)
-
An explainer specially suited for image classification.
This has exactly the same API as
Explainer
, but expects to be provided with an array of images instead of a tabular dataset.TODO: add a note about n_taus being 2
Parameters
alpha
:float
- A
float
between0
and0.5
which indicates by how close theExplainer
should look at extreme values of a distribution. The closer to zero, the more so extreme values will be accounted for. The default is0.05
which means that all values beyond the 5th and 95th quantiles are ignored. max_iterations
:int
- The number of iterations used when applying the Newton step
of the optimization procedure. Default is
5
. tol
:float
- The bottom threshold for the gradient of the optimization
procedure. When reached, the procedure stops. Otherwise, a warning
is raised about the fact that the optimization did not converge.
Default is
1e-4
. n_jobs
:int
- The number of jobs to use for parallel computations. See
joblib.Parallel()
. Default is-1
. memoize
:bool
- Indicates whether or not memoization should be used or not. If
True
, then intermediate results will be stored in order to avoid recomputing results that can be reused by successively called methods. For example, if you callplot_influence
followed byplot_influence_ranking
andmemoize
isTrue
, then the intermediate results required byplot_influence
will be reused forplot_influence_ranking
. Memoization is turned on by default because computations are time-consuming for images. verbose
:bool
- Whether or not to show progress bars during
computations. Default is
True
.
Expand source code
class ImageClassificationExplainer(CacheExplainer): """An explainer specially suited for image classification. This has exactly the same API as `Explainer`, but expects to be provided with an array of images instead of a tabular dataset. TODO: add a note about n_taus being 2 Parameters: alpha (float): A `float` between `0` and `0.5` which indicates by how close the `Explainer` should look at extreme values of a distribution. The closer to zero, the more so extreme values will be accounted for. The default is `0.05` which means that all values beyond the 5th and 95th quantiles are ignored. max_iterations (int): The number of iterations used when applying the Newton step of the optimization procedure. Default is `5`. tol (float): The bottom threshold for the gradient of the optimization procedure. When reached, the procedure stops. Otherwise, a warning is raised about the fact that the optimization did not converge. Default is `1e-4`. n_jobs (int): The number of jobs to use for parallel computations. See `joblib.Parallel()`. Default is `-1`. memoize (bool): Indicates whether or not memoization should be used or not. If `True`, then intermediate results will be stored in order to avoid recomputing results that can be reused by successively called methods. For example, if you call `plot_influence` followed by `plot_influence_ranking` and `memoize` is `True`, then the intermediate results required by `plot_influence` will be reused for `plot_influence_ranking`. Memoization is turned on by default because computations are time-consuming for images. verbose (bool): Whether or not to show progress bars during computations. Default is `True`. """ def __init__( self, alpha=0.05, max_iterations=10, tol=1e-4, n_jobs=-1, memoize=True, verbose=True, ): super().__init__( alpha=alpha, n_taus=2, max_iterations=max_iterations, tol=tol, n_jobs=n_jobs, memoize=memoize, verbose=verbose, ) def _set_image_shape(self, images): self.img_shape = images[0].shape if self.img_shape[-1] == 1: self.img_shape = self.img_shape[:-1] def explain_influence(self, X_test, y_pred): """Compute the influence of the model for the features in `X_test`. Args: X_test (np.array): An array of images, i.e. a 3d numpy array of dimension `(n_images, n_rows, n_cols)`. y_pred (pd.DataFrame or pd.Series): The model predictions for the samples in `X_test`. For binary classification and regression, `pd.Series` is expected. For multi-label classification, a pandas dataframe with one column per label is expected. The values can either be probabilities or `0/1` (for a one-hot-encoded output). Returns: pd.DataFrame: See `ethik.explainer.Explainer.explain_influence()`. """ self._set_image_shape(images=X_test) with warnings.catch_warnings(): warnings.simplefilter("ignore", category=ConstantWarning) return super().explain_influence( X_test=images_to_dataframe(X_test), y_pred=y_pred ) def explain_performance(self, X_test, y_test, y_pred, metric): """Compute the change in model's performance for the features in `X_test`. Args: X_test (np.array): An array of images, i.e. a 3d numpy array of dimension `(n_images, n_rows, n_cols)`. y_test (pd.DataFrame or pd.Series): The true values for the samples in `X_test`. For binary classification and regression, a `pd.Series` is expected. For multi-label classification, a pandas dataframe with one column per label is expected. The values can either be probabilities or `0/1` (for a one-hot-encoded output). y_pred (pd.DataFrame or pd.Series): The model predictions for the samples in `X_test`. The format is the same as `y_test`. metric (callable): A scikit-learn-like metric `f(y_true, y_pred, sample_weight=None)`. The metric must be able to handle the `y` data. For instance, for `sklearn.metrics.accuracy_score()`, "the set of labels predicted for a sample must exactly match the corresponding set of labels in `y_true`". Returns: pd.DataFrame: See `ethik.explainer.Explainer.explain_performance()`. """ self._set_image_shape(images=X_test) with warnings.catch_warnings(): warnings.simplefilter("ignore", category=ConstantWarning) return super().explain_performance( X_test=images_to_dataframe(X_test), y_test=y_test, y_pred=y_pred, metric=metric, ) def _get_fig_size(self, cell_width, n_rows, n_cols): if cell_width is None: cell_width = 800 / n_cols im_height, im_width = self.img_shape ratio = im_height / im_width cell_height = ratio * cell_width return n_cols * cell_width, n_rows * cell_height def plot_influence(self, X_test, y_pred, n_cols=3, cell_width=None): """Plot the influence of the model for the features in `X_test`. Args: X_test (pd.DataFrame or np.array): See `ImageClassificationExplainer.explain_influence()`. y_pred (pd.DataFrame or pd.Series): See `ImageClassificationExplainer.explain_influence()`. n_cols (int): The number of classes to render per row. Default is `3`. cell_width (int, optional): The width of each cell in pixels. Returns: plotly.graph_objs.Figure: A Plotly figure. It shows automatically in notebook cells but you can also call the `.show()` method to plot multiple charts in the same cell. """ influences = self.explain_influence(X_test=X_test, y_pred=y_pred) z_values = {} for label, group in influences.groupby("label"): diffs = ( group.query("tau == 1")["influence"] - group.query("tau == -1")["influence"].values ) diffs = diffs.to_numpy().reshape(self.img_shape) z_values[label] = diffs n_plots = len(z_values) labels = sorted(z_values) n_rows = n_plots // n_cols + 1 fig = make_subplots( rows=n_rows, cols=n_cols, subplot_titles=list(map(str, labels)), shared_xaxes="all", shared_yaxes="all", horizontal_spacing=0.2 / n_cols, vertical_spacing=0.2 / n_rows, ) # We want all the heatmaps to share the same scale zmin = min(np.min(z) for z in z_values.values()) zmax = max(np.max(z) for z in z_values.values()) # We want to make sure that 0 is at the center of the scale zmin, zmax = min(zmin, -zmax), max(zmax, -zmin) im_height, im_width = self.img_shape colorbar_width = 30 colorbar_ticks_width = 27 colorbar_x = 1.02 for i, label in enumerate(labels): fig.add_trace( go.Heatmap( z=z_values[label][::-1], x=list(range(im_width)), y=list(range(im_height)), zmin=zmin, zmax=zmax, colorscale="RdBu", zsmooth="best", showscale=(i == 0), name=label, hoverinfo="x+y+z", reversescale=True, colorbar=dict( thicknessmode="pixels", thickness=colorbar_width, xpad=0, x=colorbar_x, ), ), row=i // n_cols + 1, col=i % n_cols + 1, ) for i in range(n_plots): fig.update_layout( { f"xaxis{i+1}": dict(visible=False), f"yaxis{i+1}": dict(scaleanchor=f"x{i+1}", visible=False), } ) width, height = self._get_fig_size(cell_width, n_rows, n_cols) width += (colorbar_x - 1) * width + colorbar_width + colorbar_ticks_width fig.update_layout( margin=dict(t=20, l=20, b=20, r=20), width=width, height=height, autosize=False, ) return fig def plot_performance(self, X_test, y_test, y_pred, metric): """Plot the performance of the model for the features in `X_test`. Args: X_test (pd.DataFrame or np.array): See `ImageClassificationExplainer.explain_performance()`. y_test (pd.DataFrame or pd.Series): See `ImageClassificationExplainer.explain_performance()`. y_pred (pd.DataFrame or pd.Series): See `ImageClassificationExplainer.explain_performance()`. metric (callable): See `ImageClassificationExplainer.explain_performance()`. Returns: plotly.graph_objs.Figure: A Plotly figure. It shows automatically in notebook cells but you can also call the `.show()` method to plot multiple charts in the same cell. TODO: explain what is represented on the image. """ perf = self.explain_performance( X_test=X_test, y_test=y_test, y_pred=y_pred, metric=metric ) metric_name = self.get_metric_name(metric) mask = perf["label"] == perf["label"].iloc[0] diffs = ( perf[mask].query(f"tau == 1")[metric_name].to_numpy() - perf[mask].query(f"tau == -1")[metric_name].to_numpy() ) diffs = diffs.reshape(self.img_shape) # We want to make sure that 0 is at the center of the scale zmin, zmax = diffs.min(), diffs.max() zmin, zmax = min(zmin, -zmax), max(zmax, -zmin) height, width = self.img_shape fig = go.Figure() fig.add_trace( go.Heatmap( z=diffs[::-1], x=list(range(width)), y=list(range(height)), zmin=zmin, zmax=zmax, colorscale="RdBu", zsmooth="best", showscale=True, hoverinfo="x+y+z", reversescale=True, ) ) fig_width = 500 fig.update_layout( margin=dict(t=20, l=20, b=20), width=fig_width, height=fig_width * height / width, xaxis=dict(visible=False), yaxis=dict(visible=False, scaleanchor="x", scaleratio=height / width), ) return fig
Ancestors
Methods
def explain_influence(self, X_test, y_pred)
-
Compute the influence of the model for the features in
X_test
.Args
X_test
:np.array
- An array of images, i.e. a 3d numpy array of
dimension
(n_images, n_rows, n_cols)
. y_pred
:pd.DataFrame
orpd.Series
- The model predictions
for the samples in
X_test
. For binary classification and regression,pd.Series
is expected. For multi-label classification, a pandas dataframe with one column per label is expected. The values can either be probabilities or0/1
(for a one-hot-encoded output).
Returns
pd.DataFrame
:- See
ethik.explainer.Explainer.explain_influence()
.
Expand source code
def explain_influence(self, X_test, y_pred): """Compute the influence of the model for the features in `X_test`. Args: X_test (np.array): An array of images, i.e. a 3d numpy array of dimension `(n_images, n_rows, n_cols)`. y_pred (pd.DataFrame or pd.Series): The model predictions for the samples in `X_test`. For binary classification and regression, `pd.Series` is expected. For multi-label classification, a pandas dataframe with one column per label is expected. The values can either be probabilities or `0/1` (for a one-hot-encoded output). Returns: pd.DataFrame: See `ethik.explainer.Explainer.explain_influence()`. """ self._set_image_shape(images=X_test) with warnings.catch_warnings(): warnings.simplefilter("ignore", category=ConstantWarning) return super().explain_influence( X_test=images_to_dataframe(X_test), y_pred=y_pred )
def explain_performance(self, X_test, y_test, y_pred, metric)
-
Compute the change in model's performance for the features in
X_test
.Args
X_test
:np.array
- An array of images, i.e. a 3d numpy array of
dimension
(n_images, n_rows, n_cols)
. y_test
:pd.DataFrame
orpd.Series
- The true values
for the samples in
X_test
. For binary classification and regression, apd.Series
is expected. For multi-label classification, a pandas dataframe with one column per label is expected. The values can either be probabilities or0/1
(for a one-hot-encoded output). y_pred
:pd.DataFrame
orpd.Series
- The model predictions
for the samples in
X_test
. The format is the same asy_test
. metric
:callable
- A scikit-learn-like metric
f(y_true, y_pred, sample_weight=None)
. The metric must be able to handle they
data. For instance, forsklearn.metrics.accuracy_score()
, "the set of labels predicted for a sample must exactly match the corresponding set of labels iny_true
".
Returns
pd.DataFrame
:- See
ethik.explainer.Explainer.explain_performance()
.
Expand source code
def explain_performance(self, X_test, y_test, y_pred, metric): """Compute the change in model's performance for the features in `X_test`. Args: X_test (np.array): An array of images, i.e. a 3d numpy array of dimension `(n_images, n_rows, n_cols)`. y_test (pd.DataFrame or pd.Series): The true values for the samples in `X_test`. For binary classification and regression, a `pd.Series` is expected. For multi-label classification, a pandas dataframe with one column per label is expected. The values can either be probabilities or `0/1` (for a one-hot-encoded output). y_pred (pd.DataFrame or pd.Series): The model predictions for the samples in `X_test`. The format is the same as `y_test`. metric (callable): A scikit-learn-like metric `f(y_true, y_pred, sample_weight=None)`. The metric must be able to handle the `y` data. For instance, for `sklearn.metrics.accuracy_score()`, "the set of labels predicted for a sample must exactly match the corresponding set of labels in `y_true`". Returns: pd.DataFrame: See `ethik.explainer.Explainer.explain_performance()`. """ self._set_image_shape(images=X_test) with warnings.catch_warnings(): warnings.simplefilter("ignore", category=ConstantWarning) return super().explain_performance( X_test=images_to_dataframe(X_test), y_test=y_test, y_pred=y_pred, metric=metric, )
def plot_influence(self, X_test, y_pred, n_cols=3, cell_width=None)
-
Plot the influence of the model for the features in
X_test
.Args
X_test
:pd.DataFrame
ornp.array
- See
ImageClassificationExplainer.explain_influence()
. y_pred
:pd.DataFrame
orpd.Series
- See
ImageClassificationExplainer.explain_influence()
. n_cols
:int
- The number of classes to render per row. Default is
3
. cell_width
:int
, optional- The width of each cell in pixels.
Returns
plotly.graph_objs.Figure
:- A Plotly figure. It shows automatically in notebook cells but you
can also call the
.show()
method to plot multiple charts in the same cell.
Expand source code
def plot_influence(self, X_test, y_pred, n_cols=3, cell_width=None): """Plot the influence of the model for the features in `X_test`. Args: X_test (pd.DataFrame or np.array): See `ImageClassificationExplainer.explain_influence()`. y_pred (pd.DataFrame or pd.Series): See `ImageClassificationExplainer.explain_influence()`. n_cols (int): The number of classes to render per row. Default is `3`. cell_width (int, optional): The width of each cell in pixels. Returns: plotly.graph_objs.Figure: A Plotly figure. It shows automatically in notebook cells but you can also call the `.show()` method to plot multiple charts in the same cell. """ influences = self.explain_influence(X_test=X_test, y_pred=y_pred) z_values = {} for label, group in influences.groupby("label"): diffs = ( group.query("tau == 1")["influence"] - group.query("tau == -1")["influence"].values ) diffs = diffs.to_numpy().reshape(self.img_shape) z_values[label] = diffs n_plots = len(z_values) labels = sorted(z_values) n_rows = n_plots // n_cols + 1 fig = make_subplots( rows=n_rows, cols=n_cols, subplot_titles=list(map(str, labels)), shared_xaxes="all", shared_yaxes="all", horizontal_spacing=0.2 / n_cols, vertical_spacing=0.2 / n_rows, ) # We want all the heatmaps to share the same scale zmin = min(np.min(z) for z in z_values.values()) zmax = max(np.max(z) for z in z_values.values()) # We want to make sure that 0 is at the center of the scale zmin, zmax = min(zmin, -zmax), max(zmax, -zmin) im_height, im_width = self.img_shape colorbar_width = 30 colorbar_ticks_width = 27 colorbar_x = 1.02 for i, label in enumerate(labels): fig.add_trace( go.Heatmap( z=z_values[label][::-1], x=list(range(im_width)), y=list(range(im_height)), zmin=zmin, zmax=zmax, colorscale="RdBu", zsmooth="best", showscale=(i == 0), name=label, hoverinfo="x+y+z", reversescale=True, colorbar=dict( thicknessmode="pixels", thickness=colorbar_width, xpad=0, x=colorbar_x, ), ), row=i // n_cols + 1, col=i % n_cols + 1, ) for i in range(n_plots): fig.update_layout( { f"xaxis{i+1}": dict(visible=False), f"yaxis{i+1}": dict(scaleanchor=f"x{i+1}", visible=False), } ) width, height = self._get_fig_size(cell_width, n_rows, n_cols) width += (colorbar_x - 1) * width + colorbar_width + colorbar_ticks_width fig.update_layout( margin=dict(t=20, l=20, b=20, r=20), width=width, height=height, autosize=False, ) return fig
def plot_performance(self, X_test, y_test, y_pred, metric)
-
Plot the performance of the model for the features in
X_test
.Args
X_test
:pd.DataFrame
ornp.array
- See
ImageClassificationExplainer.explain_performance()
. y_test
:pd.DataFrame
orpd.Series
- See
ImageClassificationExplainer.explain_performance()
. y_pred
:pd.DataFrame
orpd.Series
- See
ImageClassificationExplainer.explain_performance()
. metric
:callable
- See
ImageClassificationExplainer.explain_performance()
.
Returns
plotly.graph_objs.Figure
:-
A Plotly figure. It shows automatically in notebook cells but you can also call the
.show()
method to plot multiple charts in the same cell.TODO: explain what is represented on the image.
Expand source code
def plot_performance(self, X_test, y_test, y_pred, metric): """Plot the performance of the model for the features in `X_test`. Args: X_test (pd.DataFrame or np.array): See `ImageClassificationExplainer.explain_performance()`. y_test (pd.DataFrame or pd.Series): See `ImageClassificationExplainer.explain_performance()`. y_pred (pd.DataFrame or pd.Series): See `ImageClassificationExplainer.explain_performance()`. metric (callable): See `ImageClassificationExplainer.explain_performance()`. Returns: plotly.graph_objs.Figure: A Plotly figure. It shows automatically in notebook cells but you can also call the `.show()` method to plot multiple charts in the same cell. TODO: explain what is represented on the image. """ perf = self.explain_performance( X_test=X_test, y_test=y_test, y_pred=y_pred, metric=metric ) metric_name = self.get_metric_name(metric) mask = perf["label"] == perf["label"].iloc[0] diffs = ( perf[mask].query(f"tau == 1")[metric_name].to_numpy() - perf[mask].query(f"tau == -1")[metric_name].to_numpy() ) diffs = diffs.reshape(self.img_shape) # We want to make sure that 0 is at the center of the scale zmin, zmax = diffs.min(), diffs.max() zmin, zmax = min(zmin, -zmax), max(zmax, -zmin) height, width = self.img_shape fig = go.Figure() fig.add_trace( go.Heatmap( z=diffs[::-1], x=list(range(width)), y=list(range(height)), zmin=zmin, zmax=zmax, colorscale="RdBu", zsmooth="best", showscale=True, hoverinfo="x+y+z", reversescale=True, ) ) fig_width = 500 fig.update_layout( margin=dict(t=20, l=20, b=20), width=fig_width, height=fig_width * height / width, xaxis=dict(visible=False), yaxis=dict(visible=False, scaleanchor="x", scaleratio=height / width), ) return fig
Inherited members
CacheExplainer
:CAT_COL_SEP
compare_influence
compare_performance
compute_distributions
compute_weights
get_metric_name
plot_cumulative_weights
plot_distributions
plot_influence_2d
plot_influence_comparison
plot_influence_ranking
plot_performance_2d
plot_performance_comparison
plot_performance_ranking
plot_weight_distribution
rank_by_influence
rank_by_performance
class RegressionExplainer (alpha=0.05, n_taus=41, n_samples=1, sample_frac=0.8, conf_level=0.05, max_iterations=15, tol=0.0001, n_jobs=1, memoize=False, verbose=True)
-
Explains the influence of features on model predictions and performance.
Parameters
alpha
:float
- A
float
between0
and0.5
which indicates by how close theCacheExplainer
should look at extreme values of a distribution. The closer to zero, the more so extreme values will be accounted for. The default is0.05
which means that all values beyond the 5th and 95th quantiles are ignored. n_taus
:int
- The number of τ values to consider. The results will be more fine-grained the
higher this value is. However the computation time increases linearly with
n_taus
. The default is41
and corresponds to each τ being separated by it's neighbors by0.05
. n_samples
:int
- The number of samples to use for the confidence interval.
If
1
, the default, no confidence interval is computed. sample_frac
:float
- The proportion of lines in the dataset sampled to
generate the samples for the confidence interval. If
n_samples
is1
, no confidence interval is computed and the whole dataset is used. Default is0.8
. conf_level
:float
- A
float
between0
and0.5
which indicates the quantile used for the confidence interval. Default is0.05
, which means that the confidence interval contains the data between the 5th and 95th quantiles. max_iterations
:int
- The maximum number of iterations used when applying the Newton step
of the optimization procedure. Default is
5
. tol
:float
- The bottom threshold for the gradient of the optimization
procedure. When reached, the procedure stops. Otherwise, a warning
is raised about the fact that the optimization did not converge.
Default is
1e-4
. n_jobs
:int
- The number of jobs to use for parallel computations. See
joblib.Parallel()
. Default is-1
. memoize
:bool
- Indicates whether or not memoization should be used or not. If
True
, then intermediate results will be stored in order to avoid recomputing results that can be reused by successively called methods. For example, if you callplot_influence
followed byplot_influence_ranking
andmemoize
isTrue
, then the intermediate results required byplot_influence
will be reused forplot_influence_ranking
. Memoization is turned off by default because it can lead to unexpected behavior depending on your usage. verbose
:bool
- Whether or not to show progress bars during
computations. Default is
True
.
Expand source code
class RegressionExplainer(CacheExplainer): pass
Ancestors
Inherited members
CacheExplainer
:CAT_COL_SEP
compare_influence
compare_performance
compute_distributions
compute_weights
explain_influence
explain_performance
get_metric_name
plot_cumulative_weights
plot_distributions
plot_influence
plot_influence_2d
plot_influence_comparison
plot_influence_ranking
plot_performance
plot_performance_2d
plot_performance_comparison
plot_performance_ranking
plot_weight_distribution
rank_by_influence
rank_by_performance