Example #1
0
    def explain(self, X: np.ndarray, Y: np.ndarray = None, verbose: bool = False) -> Explanation:
        """
        Explain instance and return PP or PN with metadata.

        Parameters
        ----------
        X
            Instances to attack
        Y
            Labels for X
        verbose
            Print intermediate results of optimization if True

        Returns
        -------
        explanation
            `Explanation` object containing the PP or PN with additional metadata as attributes.
        """
        if X.shape[0] != 1:
            logger.warning('Currently only single instance explanations supported (first dim = 1), '
                           'but first dim = %s', X.shape[0])

        if Y is None:
            if self.model:
                Y = self.sess.run(self.predict(tf.convert_to_tensor(X, dtype=tf.float32)))
            else:
                Y = self.predict(X)
            Y_ohe = np.zeros(Y.shape)
            Y_ohe[np.arange(Y.shape[0]), np.argmax(Y, axis=1)] = 1
            Y = Y_ohe.copy()

        # find best PP or PN
        self.best_attack = False
        best_attack, grads = self.attack(X, Y=Y, verbose=verbose)

        # output explanation dictionary
        data = copy.deepcopy(DEFAULT_DATA_CEM)
        data['X'] = X
        data['X_pred'] = np.argmax(Y, axis=1)[0]

        if not self.best_attack:
            logger.warning('No {} found!'.format(self.mode))

            # create explanation object
            explanation = Explanation(meta=copy.deepcopy(self.meta), data=data)
            return explanation

        data[self.mode] = best_attack
        if self.model:
            Y_pert = self.sess.run(self.predict(tf.convert_to_tensor(best_attack, dtype=tf.float32)))
        else:
            Y_pert = self.predict(best_attack)
        data[self.mode + '_pred'] = np.argmax(Y_pert, axis=1)[0]
        data['grads_graph'], data['grads_num'] = grads[0], grads[1]

        # create explanation object
        explanation = Explanation(meta=copy.deepcopy(self.meta), data=data)

        return explanation
Example #2
0
    def build_explanation(self, text: str, result: dict, predicted_label: int,
                          params: dict) -> Explanation:
        """ Uses the metadata returned by the anchor search algorithm together with
        the instance to be explained to build an explanation object.

        Parameters
        ----------
        text
            Instance to be explained.
        result
            Dictionary containing the search result and metadata.
        predicted_label
            Label of the instance to be explained. Inferred if not received.
        params
            Parameters passed to `explain`
        """

        result['instance'] = text
        result['prediction'] = predicted_label
        exp = AnchorExplanation('text', result)

        # output explanation dictionary
        data = copy.deepcopy(DEFAULT_DATA_ANCHOR)
        data.update(anchor=exp.names(),
                    precision=exp.precision(),
                    coverage=exp.coverage(),
                    raw=exp.exp_map)

        # create explanation object
        explanation = Explanation(meta=copy.deepcopy(self.meta), data=data)

        # params passed to explain
        explanation.meta['params'].update(params)
        return explanation
Example #3
0
    def build_explanation(self,
                          X: List[np.ndarray],
                          baselines: List[np.ndarray],
                          target: Optional[List[int]],
                          attributions: List[np.ndarray]) -> Explanation:
        data = copy.deepcopy(DEFAULT_DATA_INTGRAD)
        data.update(X=X,
                    baselines=baselines,
                    target=target,
                    attributions=attributions)

        # calculate predictions
        predictions = self.model(X).numpy()
        data.update(predictions=predictions)

        # calculate convergence deltas
        deltas = _compute_convergence_delta(self.model,
                                            self.input_dtypes,
                                            attributions,
                                            baselines,
                                            X,
                                            target,
                                            self._has_inputs)
        data.update(deltas=deltas)

        return Explanation(meta=copy.deepcopy(self.meta), data=data)
Example #4
0
    def build_explanation(self,
                          ale_values: List[np.ndarray],
                          ale0: List[np.ndarray],
                          constant_value: float,
                          feature_values: List[np.ndarray],
                          feature_deciles: List[np.ndarray],
                          feature_names: np.ndarray) -> Explanation:
        """
        Helper method to build the Explanation object.
        """
        # TODO decide on the format for these lists of arrays
        # Currently each list element relates to a feature and each column relates to an output dimension,
        # this is different from e.g. SHAP but arguably more convenient for ALE.

        data = copy.deepcopy(DEFAULT_DATA_ALE)
        data.update(
            ale_values=ale_values,
            ale0=ale0,
            constant_value=constant_value,
            feature_values=feature_values,
            feature_names=feature_names,
            target_names=self.target_names,
            feature_deciles=feature_deciles
        )

        return Explanation(meta=copy.deepcopy(self.meta), data=data)
Example #5
0
    def explain(self, X: np.ndarray) -> Explanation:
        """
        Explain an instance and return the counterfactual with metadata.

        Parameters
        ----------
        X
            Instance to be explained

        Returns
        -------
        `Explanation` object containing the counterfactual with additional metadata as attributes.

        """
        # TODO change init parameters on the fly

        if X.shape[0] != 1:
            logger.warning(
                'Currently only single instance explanations supported (first dim = 1), '
                'but first dim = %s', X.shape[0])

        # make a prediction
        Y = self.predict_fn(X)

        pred_class = Y.argmax(axis=1).item()
        pred_prob = Y.max(axis=1).item()
        self.return_dict['orig_class'] = pred_class
        self.return_dict['orig_proba'] = pred_prob

        logger.debug('Initial prediction: %s with p=%s', pred_class, pred_prob)

        # define the class-specific prediction function
        self.predict_class_fn, t_class = _define_func(self.predict_fn,
                                                      pred_class,
                                                      self.target_class)

        # initialize with an instance
        X_init = self._initialize(X)

        # minimize loss iteratively
        self._minimize_loss(X, X_init, Y)

        return_dict = self.return_dict.copy()
        self.instance_dict = dict.fromkeys(
            ['X', 'distance', 'lambda', 'index', 'class', 'proba', 'loss'])
        self.return_dict = {
            'cf': None,
            'all': {i: []
                    for i in range(self.max_lam_steps)},
            'orig_class': None,
            'orig_proba': None
        }

        # create explanation object
        explanation = Explanation(meta=copy.deepcopy(self.meta),
                                  data=return_dict)

        return explanation
Example #6
0
def test_explanation():
    exp = Explanation(meta=valid_meta, data=valid_data)
    assert exp.meta == valid_meta
    assert exp.data == valid_data
    assert isinstance(exp, Explanation)

    # test that a warning is raised if accessing attributes as dict keys
    with pytest.warns(None) as record:
        _ = exp['anchor']
    assert len(record) == 1
    def build_explanation(self, X: Union[np.ndarray, pd.DataFrame,
                                         sparse.spmatrix],
                          shap_values: List[np.ndarray],
                          expected_value: List) -> Explanation:
        """
        Create an explanation object.

        Parameters
        ----------
        X
            Array of instances to be explained.
        shap_values
            Each entry is a n_instances x n_features array, and the length of the list equals the dimensionality
            of the predictor output. The rows of each array correspond to the shap values for the instances with
            the corresponding row index in X
        expected_value
            A list containing the expected value of the prediction for each class.

        Returns
        -------
            An explanation containing a meta field with basic classifier metadata
            # TODO: Plotting default should be same space as the explanation? How do we figure out what space they
            #  explain in?
        """

        # TODO: DEFINE COMPLETE SCHEMA FOR THE METADATA (ONGOING)

        raw_predictions = self._explainer.linkfv(self.predictor(X))
        argmax_pred = np.argmax(np.atleast_2d(raw_predictions), axis=1)
        importances = self.rank_by_importance(shap_values)

        if isinstance(X, sparse.spmatrix):
            X = X.toarray()
        else:
            X = np.array(X)

        # output explanation dictionary
        data = copy.deepcopy(DEFAULT_DATA_SHAP)
        data.update(shap_values=shap_values,
                    expected_value=expected_value,
                    link=self.link,
                    categorical_names=self.categorical_names,
                    feature_names=self.feature_names)
        data['raw'].update(raw_prediction=raw_predictions,
                           prediction=argmax_pred,
                           instances=X,
                           importances=importances)

        return Explanation(meta=copy.deepcopy(self.meta), data=data)
Example #8
0
    def build_explanation(self, X: np.ndarray, baselines: np.ndarray,
                          target: list,
                          attributions: np.ndarray) -> Explanation:
        data = copy.deepcopy(DEFAULT_DATA_INTGRAD)
        data.update(X=X,
                    baselines=baselines,
                    target=target,
                    attributions=attributions)

        # calculate predictions
        predictions = self.model(X).numpy()
        data.update(predictions=predictions)

        # calculate convergence deltas
        deltas = _compute_convergence_delta(self.model, attributions,
                                            baselines, X, target)
        data.update(deltas=deltas)

        return Explanation(meta=copy.deepcopy(self.meta), data=data)
Example #9
0
    def build_explanation(self, image: np.ndarray, result: dict, predicted_label: int, params: dict) -> Explanation:
        """
        Uses the metadata returned by the anchor search algorithm together with
        the instance to be explained to build an explanation object.

        Parameters
        ----------
        image
            Instance to be explained.
        result
            Dictionary containing the search anchor and metadata.
        predicted_label
            Label of the instance to be explained.
        params
            Parameters passed to `explain`
        """

        result['instance'] = image
        result['instances'] = np.expand_dims(image, 0)
        result['prediction'] = np.array([predicted_label])

        # overlay image with anchor mask
        anchor = self.overlay_mask(image, self.segments, result['feature'])
        exp = AnchorExplanation('image', result)

        # output explanation dictionary
        data = copy.deepcopy(DEFAULT_DATA_ANCHOR_IMG)
        data.update(
            anchor=anchor,
            segments=self.segments,
            precision=exp.precision(),
            coverage=exp.coverage(),
            raw=exp.exp_map
        )

        # create explanation object
        explanation = Explanation(meta=copy.deepcopy(self.meta), data=data)

        # params passed to explain
        explanation.meta['params'].update(params)
        return explanation
Example #10
0
    def build_explanation(self, X: np.ndarray, result: dict,
                          predicted_label: int, params: dict) -> Explanation:
        """
        Preprocess search output and return an explanation object containing metdata

        Parameters
        ----------
        X:
            Instance to be explained.
        result:
            Dictionary with explanation search output and metadata.
        predicted_label:
            Label of the instance to be explained (inferred if not given).
        params
            Parameters passed to `explain`

        Return
        ------
             `Explanation` object containing human readable explanation, metadata, and precision/coverage
             info as attributes.
        """

        self.add_names_to_exp(result)
        result['prediction'] = np.array([predicted_label])
        result['instance'] = X
        result['instances'] = np.atleast_2d(X)
        exp = AnchorExplanation('tabular', result)

        # output explanation dictionary
        data = copy.deepcopy(DEFAULT_DATA_ANCHOR)
        data.update(anchor=exp.names(),
                    precision=exp.precision(),
                    coverage=exp.coverage(),
                    raw=exp.exp_map)

        # create explanation object
        explanation = Explanation(meta=copy.deepcopy(self.meta), data=data)

        # params passed to explain
        explanation.meta['params'].update(params)
        return explanation
Example #11
0
def test_serialize_deserialize_explanation():
    exp = Explanation(meta=valid_meta, data=valid_data)
    jrep = exp.to_json()
    exp2 = Explanation.from_json(jrep)
    assert exp == exp2