def get_predictions_after_masking(self, explained_model, X, y, downsample_factors=(1,), batch_size=64,
                                      flatten=False):
        Validation.check_dataset(X, y)

        num_batches = int(np.ceil(len(X) / float(batch_size)))

        all_outputs = []
        for batch_idx in range(num_batches):
            x_batch = X[batch_idx*batch_size:(batch_idx+1)*batch_size]
            y_pred = MaskingUtil.get_prediction(explained_model, x_batch, flatten=flatten)

            x_imputed = []
            for x_i in x_batch:
                x_curr = []
                for j in range(len(x_i)):
                    x_i_imputed_j = np.concatenate([x_i[:j], x_i[j+1:]], axis=0)  # Drop entry at each index j.
                    x_curr.append(x_i_imputed_j)
                x_imputed.append(x_curr)

            all_y_pred_imputed = []
            for j, x_imputed_curr in enumerate(x_imputed):
                if len(x_imputed_curr) == 0:
                    y_pred_imputed = y_pred[j].reshape((1, -1))
                else:
                    y_pred_imputed = MaskingUtil.get_prediction(explained_model, x_imputed_curr, flatten=flatten)
                all_y_pred_imputed.append(y_pred_imputed)

            all_outputs.append((x_batch, y_pred, all_y_pred_imputed))

        all_outputs = [np.concatenate(list(map(partial(lambda x, dim: x[dim], dim=dim), all_outputs)))
                       for dim in range(len(all_outputs[0]))]

        return all_outputs
Beispiel #2
0
    def test_extract_downsample_factors_valid_length(self):
        ret_val = MaskingUtil.extract_downsample_factors((2, ), 2)
        self.assertEqual(ret_val, (2, 2))

        ret_val = MaskingUtil.extract_downsample_factors(1, 3)
        self.assertEqual(ret_val, (1, 1, 1))

        ret_val = MaskingUtil.extract_downsample_factors((1, 2, 3), 3)
        self.assertEqual(ret_val, (1, 2, 3))
Beispiel #3
0
    def test_get_ith_mask_invalid(self):
        with self.assertRaises(ValueError):
            MaskingUtil.get_ith_mask(i=0,
                                     image_shape=(1, ),
                                     downsample_factors=(1, ))

        with self.assertRaises(ValueError):
            MaskingUtil.get_ith_mask(i=0,
                                     image_shape=(1, ) * 5,
                                     downsample_factors=(1, ))
    def get_predictions_after_masking(self,
                                      explained_model,
                                      X,
                                      y,
                                      downsample_factors=(1, ),
                                      batch_size=64,
                                      flatten=False):
        Validation.check_dataset(X, y)

        num_batches = int(np.ceil(len(X) / float(batch_size)))

        all_outputs = []
        for batch_idx in range(num_batches):
            x = X[batch_idx * batch_size:(batch_idx + 1) * batch_size]
            y_pred = MaskingUtil.get_prediction(explained_model,
                                                x,
                                                flatten=flatten)
            x_imputed = MaskingUtil.get_x_imputed(x,
                                                  downsample_factors,
                                                  math_ops=NumpyInterface)

            all_y_pred_imputed = []
            for x_imputed_curr in x_imputed:
                y_pred_imputed = MaskingUtil.get_prediction(explained_model,
                                                            x_imputed_curr,
                                                            flatten=flatten)
                all_y_pred_imputed.append(y_pred_imputed)

            all_y_pred_imputed = np.stack(all_y_pred_imputed).swapaxes(0, 1)

            all_outputs.append((x, y_pred, all_y_pred_imputed))

        all_outputs = [
            np.concatenate(
                list(map(partial(lambda x, dim: x[dim], dim=dim),
                         all_outputs))) for dim in range(len(all_outputs[0]))
        ]
        return all_outputs
Beispiel #5
0
    def test_get_input_constants(self):
        test_cases_is = [(1, ), (2, ), (2, ), (1, 1), (2, 2), (2, 2), (4, 4),
                         (4, 4), (4, 2), (2, 2, 2), (2, 2, 2), (3, 1, 3)]
        test_cases_df = [(1, ), (1, ), (2, ), (1, 1), (1, 1), (2, 2), (1, 4),
                         (4, 1), (4, 2), (1, 1, 1), (2, 2, 2), (1, 1, 1)]
        expected_steps = [(1, ), (2, ), (1, ), (1, 1), (2, 2), (1, 1), (4, 1),
                          (1, 4), (1, 1), (2, 2, 2), (1, 1, 1), (3, 1, 3)]

        for shape, factors, expected in zip(test_cases_is, test_cases_df,
                                            expected_steps):
            shape = shape + (1, )  # Add channel dim.
            num_indices, num_channels, steps, downsampling_factor = \
                MaskingUtil.get_input_constants(input_dim=shape,
                                                downsample_factors=factors)

            self.assertEqual(
                (num_indices, num_channels, downsampling_factor),
                (int(np.prod(expected)), 1, int(np.prod(factors))))

            self.assertTrue(np.array_equal(np.array(expected), steps))
Beispiel #6
0
    def build_explanation_model(self,
                                input_dim,
                                output_dim=None,
                                loss=None,
                                downsample_factors=(1, )):
        num_indices, num_channels, steps, downsampling_factor =\
            MaskingUtil.get_input_constants(input_dim, downsample_factors)

        if self.n_feature_groups > 0:
            num_indices = self.n_feature_groups

        if downsampling_factor != 1 and num_indices is None:
            raise ValueError(
                "Attribution downsampling is not supported for variable length inputs. "
                "Please pad your data samples to the same size to use downsampling."
            )

        input_shape = (input_dim, ) if not isinstance(
            input_dim, collections.Sequence) else input_dim
        input_layer = Input(shape=input_shape)
        last_layer = self.build(input_layer)

        if num_indices is None:
            last_layer = Dense(1, activation="linear")(last_layer)
            last_layer = Flatten()(last_layer)  # None * None outputs
            last_layer = Lambda(
                K.softmax, output_shape=K.int_shape(last_layer))(last_layer)
        else:
            last_layer = Flatten()(last_layer)
            last_layer = Dense(num_indices, activation="softmax")(last_layer)

        # Prepare extra inputs for causal loss.
        # all_auxiliary_outputs = Input(shape=(output_dim,), name="all")
        # all_but_one_auxiliary_outputs_input = Input(shape=(num_indices, output_dim), name="all_but_one")
        # omega = Input(shape=(num_indices,), name="omega")

        # if num_indices is not None:
        # all_but_one_auxiliary_outputs = Lambda(lambda x: tf.unstack(x, axis=1))(all_but_one_auxiliary_outputs_input)
        # if K.int_shape(all_but_one_auxiliary_outputs_input)[1] == 1:
        # all_but_one_auxiliary_outputs = [all_but_one_auxiliary_outputs]
        # else:
        # all_but_one_auxiliary_outputs = all_but_one_auxiliary_outputs_input

        # causal_loss_fun = partial(causal_loss_precalculated,
        #                           attention_weights=last_layer,
        #                           omega=omega)
        #                           # auxiliary_outputs=all_auxiliary_outputs,
        #                           # all_but_one_auxiliary_outputs=all_but_one_auxiliary_outputs,
        #                           # loss_function=loss)
        # causal_loss_fun.__name__ = "causal_loss_precalculated"

        # We must connect all inputs to the output to bypass a bug in model saving in tf < 1.15.0rc0.
        # For easier handling when calling .fit(), we transform all outputs to be the same shape.
        # See https://github.com/tensorflow/tensorflow/pull/30244
        # all_but_one_same_shape_output_layer = Lambda(lambda x: x[:, 0, :])(all_but_one_auxiliary_outputs_input)

        model = Model(inputs=[input_layer], outputs=[last_layer])
        model = self.compile_model(model,
                                   main_losses=[causal_loss_precalculated],
                                   loss_weights=[1.0],
                                   learning_rate=self.learning_rate,
                                   optimizer=self.optimizer)

        prediction_model = Model(input_layer, last_layer)
        return model, prediction_model
 def predict(self, x):
     x = self.map_from_dummy(x)
     y = MaskingUtil.predict_proxy(model, x)
     if len(y.shape) == 1:
         y = np.expand_dims(y, axis=-1)
     return y
Beispiel #8
0
 def test_extract_downsample_factors_invalid_length(self):
     with self.assertRaises(ValueError):
         MaskingUtil.extract_downsample_factors((2, 2), 3)
     with self.assertRaises(ValueError):
         MaskingUtil.extract_downsample_factors((1, 1, 1, 1), 3)
Beispiel #9
0
    def build_explanation_model(self,
                                input_dim,
                                output_dim,
                                loss,
                                downsample_factors=(1, )):
        num_indices, num_channels, steps, downsampling_factor =\
            MaskingUtil.get_input_constants(input_dim, downsample_factors)

        if downsampling_factor != 1 and num_indices is None:
            raise ValueError(
                "Attribution downsampling is not supported for variable length inputs. "
                "Please pad your data samples to the same size to use downsampling."
            )

        input_shape = (input_dim, ) if not isinstance(
            input_dim, collections.Sequence) else input_dim
        input_layer = Input(shape=input_shape)
        last_layer = self.build(input_layer)

        if num_indices is None:
            last_layer = Dense(1, activation="linear")(last_layer)
            last_layer = Flatten()(last_layer)  # None * None outputs
            last_layer = Lambda(
                K.softmax, output_shape=K.int_shape(last_layer))(last_layer)
        else:
            last_layer = Flatten()(last_layer)
            last_layer = Dense(num_indices, activation="softmax")(last_layer)

        # Prepare extra inputs for causal loss.
        all_auxiliary_outputs = Input(shape=(output_dim, ), name="all")
        all_but_one_auxiliary_outputs_input = Input(shape=(num_indices,
                                                           output_dim),
                                                    name="all_but_one")

        if num_indices is not None:
            all_but_one_auxiliary_outputs = Lambda(lambda x: tf.unstack(
                x, axis=1))(all_but_one_auxiliary_outputs_input)
            if K.int_shape(all_but_one_auxiliary_outputs_input)[1] == 1:
                all_but_one_auxiliary_outputs = [all_but_one_auxiliary_outputs]
        else:
            all_but_one_auxiliary_outputs = all_but_one_auxiliary_outputs_input

        all_but_one_auxiliary_outputs = Concatenate()(
            all_but_one_auxiliary_outputs)

        causal_loss_fun = CausalLoss(num_indices=num_indices,
                                     loss_function=loss)

        if downsampling_factor != 1:
            last_layer = Reshape(tuple(steps) + (1, ))(last_layer)

            if len(steps) == 1:
                # Add a dummy dimension to enable usage of __resize_images__.
                last_layer = Reshape(tuple(steps) + (1, 1))(last_layer)
                last_layer = Lambda(lambda x: resize_images(
                    x,
                    height_factor=downsample_factors[0],
                    width_factor=1,
                    data_format="channels_last"))(last_layer)
            elif len(steps) == 2:
                last_layer = Lambda(lambda x: resize_images(
                    x,
                    height_factor=downsample_factors[0],
                    width_factor=downsample_factors[1],
                    data_format="channels_last"))(last_layer)
            elif len(steps) == 3:
                last_layer = Lambda(lambda x: resize_volumes(
                    x,
                    depth_factor=downsample_factors[0],
                    height_factor=downsample_factors[1],
                    width_factor=downsample_factors[2],
                    data_format="channels_last"))(last_layer)
            else:
                raise ValueError(
                    "Attribution maps of larger dimensionality than 3D data are not currently supported. "
                    "Requested output dim was: {}.".format(len(steps)))

            attribution_shape = Validation.get_attribution_shape_from_input_shape(
                num_samples=1, input_dim=input_dim)[1:]
            collapsed_attribution_shape = (int(np.prod(attribution_shape)), )
            last_layer = Reshape(collapsed_attribution_shape)(last_layer)

            # Re-normalise to sum = 1 after resizing (sum = __downsampling_factor__ after resizing).
            last_layer = Lambda(lambda x: x / float(downsampling_factor))(
                last_layer)

        final_layer = Concatenate()(
            [last_layer, all_but_one_auxiliary_outputs, all_auxiliary_outputs])

        model = Model(inputs=[
            input_layer, all_auxiliary_outputs,
            all_but_one_auxiliary_outputs_input
        ],
                      outputs=final_layer)

        model = self.compile_model(model,
                                   main_losses=causal_loss_fun,
                                   learning_rate=self.learning_rate,
                                   optimizer=self.optimizer)

        prediction_model = Model(input_layer, last_layer)
        return model, prediction_model