Exemple #1
0
    def create_analyzer_model(self):
        self._subanalyzer.create_analyzer_model()

        if self._subanalyzer._n_debug_output > 0:
            raise NotImplementedError(
                "No debug output at subanalyzer is supported.")

        model = self._subanalyzer._analyzer_model
        if None in model.input_shape[1:]:
            raise ValueError("The input shape for the model needs "
                             "to be fully specified (except the batch axis). "
                             f"Model input shape is: {model.input_shape}")

        inputs = model.inputs[:self._subanalyzer._n_data_input]
        extra_inputs = model.inputs[self._subanalyzer._n_data_input:]

        outputs = model.outputs[:self._subanalyzer._n_data_output]
        extra_outputs = model.outputs[self._subanalyzer._n_data_output:]

        if len(extra_outputs) > 0:
            raise Exception("No extra output is allowed " "with this wrapper.")

        new_inputs = iutils.to_list(self._augment(inputs))
        # print(type(new_inputs), type(extra_inputs))
        tmp = iutils.to_list(model(new_inputs + extra_inputs))
        new_outputs = iutils.to_list(self._reduce(tmp))
        new_constant_inputs = self._keras_get_constant_inputs()

        new_model = keras.models.Model(
            inputs=inputs + extra_inputs + new_constant_inputs,
            outputs=new_outputs + extra_outputs,
        )
        self._subanalyzer._analyzer_model = new_model
Exemple #2
0
    def apply(self, Xs, Ys, Rs, reverse_state):
        grad = ilayers.GradientWRT(len(Xs))
        to_low = keras.layers.Lambda(lambda x: x * 0 + self._low)
        to_high = keras.layers.Lambda(lambda x: x * 0 + self._high)

        low = [to_low(x) for x in Xs]
        high = [to_high(x) for x in Xs]

        # Get values for the division.
        A = kutils.apply(self._layer_wo_act, Xs)
        B = kutils.apply(self._layer_wo_act_positive, low)
        C = kutils.apply(self._layer_wo_act_negative, high)
        Zs = [
            keras.layers.Subtract()([a, keras.layers.Add()([b, c])])
            for a, b, c in zip(A, B, C)
        ]

        # Divide relevances with the value.
        tmp = [ilayers.SafeDivide()([a, b]) for a, b in zip(Rs, Zs)]
        # Distribute along the gradient.
        tmpA = iutils.to_list(grad(Xs + A + tmp))
        tmpB = iutils.to_list(grad(low + B + tmp))
        tmpC = iutils.to_list(grad(high + C + tmp))

        tmpA = [keras.layers.Multiply()([a, b]) for a, b in zip(Xs, tmpA)]
        tmpB = [keras.layers.Multiply()([a, b]) for a, b in zip(low, tmpB)]
        tmpC = [keras.layers.Multiply()([a, b]) for a, b in zip(high, tmpC)]

        tmp = [
            keras.layers.Subtract()([a, keras.layers.Add()([b, c])])
            for a, b, c in zip(tmpA, tmpB, tmpC)
        ]

        return tmp
 def _create_analysis(self, model, stop_analysis_at_tensors=[]):
     tensors_to_analyze = [x for x in iutils.to_list(model.inputs)
                           if x not in stop_analysis_at_tensors]
     gradients = iutils.to_list(ilayers.Gradient()(
         tensors_to_analyze+[model.outputs[0]]))
     return [keras.layers.Multiply()([i, g])
             for i, g in zip(tensors_to_analyze, gradients)]
 def f(layer1, layer2, X1, X2):
     # Get activations of full positive or negative part.
     Z1 = kutils.apply(layer1, X1)
     Z2 = kutils.apply(layer2, X2)
     Zs = [
         tensorflow.keras.layers.Add()([a, b]) for a, b in zip(Z1, Z2)
     ]
     # Divide incoming relevance by the activations.
     tmp = [ilayers.SafeDivide()([a, b]) for a, b in zip(Rs, Zs)]
     # Propagate the relevance to the input neurons
     # using the gradient
     tmp1 = iutils.to_list(grad(X1 + Z1 + tmp))
     tmp2 = iutils.to_list(grad(X2 + Z2 + tmp))
     # Re-weight relevance with the input values.
     tmp1 = [
         tensorflow.keras.layers.Multiply()([a, b])
         for a, b in zip(X1, tmp1)
     ]
     tmp2 = [
         tensorflow.keras.layers.Multiply()([a, b])
         for a, b in zip(X2, tmp2)
     ]
     #combine and return
     return [
         tensorflow.keras.layers.Add()([a, b])
         for a, b in zip(tmp1, tmp2)
     ]
Exemple #5
0
    def run(self, inputs):
        """Runs the model given the inputs.

        :return: Tuple with model output and analyzer output.
        """
        return_list = True
        if not isinstance(inputs, list):
            return_list = False
            inputs = iutils.to_list(inputs)

        augmented = []
        for i, inp in enumerate(inputs):
            if len(inp.shape) == len(self._input_shapes[i]) - 1:
                # Augment by batch axis.
                augmented.append(i)
                inputs[i] = inp.reshape((1, ) + inp.shape)

        outputs = iutils.to_list(self._model.predict_on_batch(inputs))
        analysis = iutils.to_list(self._analyzer.analyze(inputs))

        for i in augmented:
            # Remove batch axis.
            outputs[i] = outputs[i][0]
            analysis[i] = analysis[i][0]

        if return_list:
            return outputs, analysis
        else:
            return outputs[0], analysis[0]
    def apply(self, Xs, Ys, Rs, reverse_state):
        grad = ilayers.GradientWRT(len(Xs))
        # Create dummy forward path to take the derivative below.
        Ys = kutils.apply(self._layer_wo_act_b, Xs)

        # Compute the sum of the weights.
        ones = ilayers.OnesLike()(Xs)
        Zs = iutils.to_list(self._layer_wo_act_b(ones))
        # Weight the incoming relevance.
        tmp = [ilayers.SafeDivide()([a, b]) for a, b in zip(Rs, Zs)]
        # Redistribute the relevances along the gradient.
        tmp = iutils.to_list(grad(Xs + Ys + tmp))
        return tmp
Exemple #7
0
 def compute_output_shape(self, input_shape: ShapeTuple) -> ShapeTuple:
     if self.axis is None:
         if self.keepdims is False:
             return (1, )
         else:
             return tuple(np.ones_like(input_shape))  # type: ignore
     else:
         axes = np.arange(len(input_shape))
         if self.keepdims is False:
             for i in iutils.to_list(self.axis):
                 axes = np.delete(axes, i, 0)
         else:
             for i in iutils.to_list(self.axis):
                 axes[i] = 1
         return tuple(
             [idx for i, idx in enumerate(input_shape) if i in axes])
Exemple #8
0
def broadcast_np_tensors_to_keras_tensors(
    keras_tensors: OptionalList[Tensor],
    np_tensors: Union[float, np.ndarray, List[np.ndarray]],
) -> List[np.ndarray]:
    """Broadcasts numpy tensors to the shape of Keras tensors.

    :param keras_tensors: The Keras tensors with the target shapes.
    :type keras_tensors: OptionalList[Tensor]
    :param np_tensors: Numpy tensors that should be broadcasted.
    :type np_tensors: Union[float, np.ndarray, List[np.ndarray]]
    :return: The broadcasted Numpy tensors.
    :rtype: List[np.ndarray]
    """
    def none_to_one(tmp):
        return [1 if x is None else x for x in tmp]

    keras_tensors = iutils.to_list(keras_tensors)

    if isinstance(np_tensors, list):
        return [
            np.broadcast_to(ri, none_to_one(int_shape(x)))
            for x, ri in zip(keras_tensors, np_tensors)
        ]
    return [
        np.broadcast_to(np_tensors, none_to_one(int_shape(x)))
        for x in keras_tensors
    ]
Exemple #9
0
    def __init__(
        self,
        model,
        pattern_type="linear",
        # TODO: this options seems to be buggy,
        # if it sequential tensorflow still pushes all models to gpus
        compute_layers_in_parallel=True,
        gpus=None,
    ):
        self.model = model

        supported_layers = (
            innvestigate.analyzer.pattern_based.SUPPORTED_LAYER_PATTERNNET)
        for layer in self.model.layers:
            if not isinstance(layer, supported_layers):
                raise Exception("Model contains not supported layer: %s" %
                                layer)

        pattern_types = iutils.to_list(pattern_type)
        self.pattern_types = {k: get_pattern_class(k) for k in pattern_types}
        self.compute_layers_in_parallel = compute_layers_in_parallel
        self.gpus = gpus

        if self.compute_layers_in_parallel is False:
            raise NotImplementedError("Not supported.")
Exemple #10
0
    def __init__(self, model, analyzer, weights=None):
        """Helper class for retrieving output and analysis in test cases.


        :param model: A Keras layer object or a list of layer objects.
          In this case a sequntial model will be build. The first layer
          must have set input_shape or batch_input_shape.
          Alternatively a tuple with input and output tensors, in which
          case the keras modle api will be used.
        :param analyzer: Either an analyzer class or a function
          that takes a keras model and returns an analyzer.
        :param weights: After creating the model set the given weights.
        """

        if isinstance(model, keras.engine.topology.Layer):
            model = [model]

        if isinstance(model, list):
            self._model = keras.models.Sequential(model)
        else:
            self._model = keras.models.Model(*model)

        self._input_shapes = iutils.to_list(self._model.input_shape)

        if weights is not None:
            self._model.set_weights(weights)

        self._analyzer = analyzer(self._model)
Exemple #11
0
def apply(layer: Layer, inputs: OptionalList[Tensor]) -> List[Tensor]:
    """
    Apply a layer to input[s].

    A flexible apply that tries to fit input to layers expected input.
    This is useful when one doesn't know if a layer expects a single tensor
    or many.

    :param layer: A Keras layer instance.
    :type layer: Layer
    :param inputs: A list of input tensors or a single tensor.
    :type inputs: OptionalList[Tensor]
    :return: Output from applying the layer to the input.
    :rtype: List[Tensor]
    """

    if isinstance(inputs, list) and len(inputs) > 1:
        try:
            ret = layer(inputs)
        except (TypeError, AttributeError) as err:
            # layer expects a single tensor.
            if len(inputs) != 1:
                raise ValueError("Layer expects only a single input!") from err
            ret = layer(inputs[0])
    else:
        ret = layer(inputs[0])

    return iutils.to_list(ret)
Exemple #12
0
 def _create_analysis(self, model):
     gradients = iutils.to_list(ilayers.Gradient()(model.inputs + [
         model.outputs[0],
     ]))
     return [
         keras.layers.Multiply()([i, g])
         for i, g in zip(model.inputs, gradients)
     ]
    def _create_analysis(self, model, stop_analysis_at_tensors=None):
        if stop_analysis_at_tensors is None:
            stop_analysis_at_tensors = []

        tensors_to_analyze = [
            x for x in iutils.to_list(model.inputs)
            if x not in stop_analysis_at_tensors
        ]
        ret = iutils.to_list(ilayers.Gradient()(tensors_to_analyze +
                                                [model.outputs[0]]))

        if self._postprocess == "abs":
            ret = ilayers.Abs()(ret)
        elif self._postprocess == "square":
            ret = ilayers.Square()(ret)

        return iutils.to_list(ret)
Exemple #14
0
    def create_analyzer_model(self) -> None:
        """
        Creates the analyze functionality. If not called beforehand
        it will be called by :func:`analyze`.
        """
        model_inputs = self._model.inputs
        model, analysis_inputs, stop_analysis_at_tensors = self._prepare_model(
            self._model
        )
        self._analysis_inputs = analysis_inputs
        self._prepared_model = model

        tmp = self._create_analysis(
            model, stop_analysis_at_tensors=stop_analysis_at_tensors
        )
        if isinstance(tmp, tuple):
            if len(tmp) == 3:
                analysis_outputs, debug_outputs, constant_inputs = tmp  # type: ignore
            elif len(tmp) == 2:
                analysis_outputs, debug_outputs = tmp  # type: ignore
                constant_inputs = []
            elif len(tmp) == 1:
                analysis_outputs = tmp[0]
                constant_inputs = []
                debug_outputs = []
            else:
                raise Exception("Unexpected output from _create_analysis.")
        else:
            analysis_outputs = tmp
            constant_inputs = []
            debug_outputs = []

        analysis_outputs = iutils.to_list(analysis_outputs)
        debug_outputs = iutils.to_list(debug_outputs)
        constant_inputs = iutils.to_list(constant_inputs)

        self._n_data_input = len(model_inputs)
        self._n_constant_input = len(constant_inputs)
        self._n_data_output = len(analysis_outputs)
        self._n_debug_output = len(debug_outputs)
        self._analyzer_model = keras.models.Model(
            inputs=model_inputs + analysis_inputs + constant_inputs,
            outputs=analysis_outputs + debug_outputs,
        )

        self._analyzer_model_done = True
    def _postprocess_analysis(self, X):
        ret = super()._postprocess_analysis(X)

        if self._postprocess == "abs":
            ret = ilayers.Abs()(ret)
        elif self._postprocess == "square":
            ret = ilayers.Square()(ret)

        return iutils.to_list(ret)
Exemple #16
0
    def _reduce(self, X):
        X_shape = [kbackend.int_shape(x) for x in iutils.to_list(X)]
        reshape = [
            ilayers.Reshape((-1, self._augment_by_n) + shape[1:])
            for shape in X_shape
        ]
        mean = ilayers.Mean(axis=1)

        return [mean(reshape_x(x)) for x, reshape_x in zip(X, reshape)]
Exemple #17
0
    def _create_analysis(self, model, stop_analysis_at_tensors=None):
        if stop_analysis_at_tensors is None:
            stop_analysis_at_tensors = []

        tensors_to_analyze = [
            x for x in iutils.to_list(model.inputs)
            if x not in stop_analysis_at_tensors
        ]
        return [ilayers.Identity()(x) for x in tensors_to_analyze]
Exemple #18
0
 def f(layer, X):
     Zs = kutils.apply(layer, X)
     # Divide incoming relevance by the activations.
     tmp = [ilayers.SafeDivide()([a, b]) for a, b in zip(Rs, Zs)]
     # Propagate the relevance to the input neurons
     # using the gradient
     tmp = iutils.to_list(grad(X + Zs + tmp))
     # Re-weight relevance with the input values.
     tmp = [keras.layers.Multiply()([a, b]) for a, b in zip(X, tmp)]
     return tmp
Exemple #19
0
def get_input_layers(layer: Layer) -> Set[Layer]:
    """Returns all layers that created this layer's inputs."""
    ret = set()

    for node_index in range(len(layer._inbound_nodes)):
        Xs = iutils.to_list(layer.get_input_at(node_index))
        for X in Xs:
            ret.add(X._keras_history[0])

    return ret
Exemple #20
0
    def _create_analysis(self, model, stop_analysis_at_tensors=None):
        if stop_analysis_at_tensors is None:
            stop_analysis_at_tensors = []

        noise = ilayers.TestPhaseGaussianNoise(stddev=self._stddev)
        tensors_to_analyze = [
            x for x in iutils.to_list(model.inputs)
            if x not in stop_analysis_at_tensors
        ]
        return [noise(x) for x in tensors_to_analyze]
Exemple #21
0
    def apply(self, Xs, Ys, Rs, reverse_state):
        grad = ilayers.GradientWRT(len(Xs))

        # Get activations.
        Zs = kutils.apply(self._layer_wo_act, Xs)
        # Divide incoming relevance by the activations.
        tmp = [ilayers.SafeDivide()([a, b]) for a, b in zip(Rs, Zs)]
        # Propagate the relevance to input neurons
        # using the gradient.
        tmp = iutils.to_list(grad(Xs + Zs + tmp))
        # Re-weight relevance with the input values.
        return [keras.layers.Multiply()([a, b]) for a, b in zip(Xs, tmp)]
    def _create_analysis(self, model, stop_analysis_at_tensors=None):
        if stop_analysis_at_tensors is None:
            stop_analysis_at_tensors = []

        tensors_to_analyze = [
            x for x in iutils.to_list(model.inputs)
            if x not in stop_analysis_at_tensors
        ]
        gradients = super()._create_analysis(
            model, stop_analysis_at_tensors=stop_analysis_at_tensors)
        return [
            keras.layers.Multiply()([i, g])
            for i, g in zip(tensors_to_analyze, gradients)
        ]
Exemple #23
0
    def apply(self, Xs, Ys, Rs, reverse_state):
        grad = ilayers.GradientWRT(len(Xs))

        #TODO: assert all inputs are positive, instead of only keeping the positives.
        #keep_positives = keras.layers.Lambda(lambda x: x * K.cast(K.greater(x,0), K.floatx()))
        #Xs = kutils.apply(keep_positives, Xs)

        # Get activations.
        Zs = kutils.apply(self._layer_wo_act_b_positive, Xs)
        # Divide incoming relevance by the activations.
        tmp = [ilayers.SafeDivide()([a, b]) for a, b in zip(Rs, Zs)]
        # Propagate the relevance to input neurons
        # using the gradient.
        tmp = iutils.to_list(grad(Xs + Zs + tmp))
        # Re-weight relevance with the input values.
        return [keras.layers.Multiply()([a, b]) for a, b in zip(Xs, tmp)]
Exemple #24
0
    def apply(self, Xs, Ys, Rs, reverse_state):
        grad = ilayers.GradientWRT(len(Xs))
        # The epsilon rule aligns epsilon with the (extended) sign: 0 is considered to be positive
        prepare_div = keras.layers.Lambda(lambda x: x + (K.cast(
            K.greater_equal(x, 0), K.floatx()) * 2 - 1) * self._epsilon)

        # Get activations.
        Zs = kutils.apply(self._layer_wo_act, Xs)

        # Divide incoming relevance by the activations.
        tmp = [ilayers.Divide()([a, prepare_div(b)]) for a, b in zip(Rs, Zs)]
        # Propagate the relevance to input neurons
        # using the gradient.
        tmp = iutils.to_list(grad(Xs + Zs + tmp))
        # Re-weight relevance with the input values.
        return [keras.layers.Multiply()([a, b]) for a, b in zip(Xs, tmp)]
Exemple #25
0
    def apply(self, Xs, Ys, Rs, reverse_state):
        # the outputs of the pooling operation at each location is the sum of its inputs.
        # the forward message must be known in this case, and are the inputs for each pooling thing.
        # the gradient is 1 for each output-to-input connection, which corresponds to the "weights"
        # of the layer. It should thus be sufficient to reweight the relevances and and do a gradient_wrt
        grad = ilayers.GradientWRT(len(Xs))
        # Get activations.
        Zs = kutils.apply(self._layer_wo_act, Xs)
        # Divide incoming relevance by the activations.
        tmp = [ilayers.SafeDivide()([a, b]) for a, b in zip(Rs, Zs)]

        # Propagate the relevance to input neurons
        # using the gradient.
        tmp = iutils.to_list(grad(Xs + Zs + tmp))
        # Re-weight relevance with the input values.
        return [keras.layers.Multiply()([a, b]) for a, b in zip(Xs, tmp)]
Exemple #26
0
    def analyze(
        self,
        X: OptionalList[np.ndarray],
        neuron_selection: Optional[int] = None,
    ) -> OptionalList[np.ndarray]:
        """
        Same interface as :class:`Analyzer` besides

        :param neuron_selection: If neuron_selection_mode is 'index' this
        should be an integer with the index for the chosen neuron.
        """
        # TODO: what does should mean in docstring?

        if self._analyzer_model_done is False:
            self.create_analyzer_model()

        if neuron_selection is not None and self._neuron_selection_mode != "index":
            raise ValueError(
                f"neuron_selection_mode {self._neuron_selection_mode} doesn't support ",
                "'neuron_selection' parameter.",
            )

        if neuron_selection is None and self._neuron_selection_mode == "index":
            raise ValueError(
                "neuron_selection_mode 'index' expects 'neuron_selection' parameter."
            )

        X = iutils.to_list(X)

        ret: OptionalList[np.ndarray]
        if self._neuron_selection_mode == "index":
            if neuron_selection is not None:
                # TODO: document how this works
                selection = self._get_neuron_selection_array(
                    X, neuron_selection)
                ret = self._analyzer_model.predict_on_batch(X + [selection])
            else:
                raise RuntimeError(
                    'neuron_selection_mode "index" requires neuron_selection.')
        else:
            ret = self._analyzer_model.predict_on_batch(X)

        if self._n_debug_output > 0:
            self._handle_debug_output(ret[-self._n_debug_output:])
            ret = ret[:-self._n_debug_output]

        return iutils.unpack_singleton(ret)
Exemple #27
0
    def _get_active_node_indices(self):
        """
        A layer can be applied in several models.
        This functions returns a list with all nodes of the given
        layer that are active/used in the current model.

        If no model_tensors are passed to the pattern,
        it is assumed all nodes are active.
        """
        n_nodes = kgraph.get_layer_inbound_count(self.layer)
        if self.model_tensors is None:
            return list(range(n_nodes))
        else:
            ret = []
            for i in range(n_nodes):
                output_tensors = iutils.to_list(self.layer.get_output_at(i))
                # Check if output is used in the model.
                if all([tmp in self.model_tensors for tmp in output_tensors]):
                    ret.append(i)
            return ret
Exemple #28
0
    def apply(self, Xs, _Ys, reversed_Ys, _reverse_state: Dict):
        # Reapply the prepared layers.
        act_Xs = kutils.apply(self._filter_layer, Xs)
        act_Ys = kutils.apply(self._act_layer, act_Xs)
        pattern_Ys = kutils.apply(self._pattern_layer, Xs)

        # Layers that apply the backward pass.
        grad_act = ilayers.GradientWRT(len(act_Xs))
        grad_pattern = ilayers.GradientWRT(len(Xs))

        # First step: propagate through the activation layer.
        # Workaround for linear activations.
        linear_activations = [None, keras.activations.get("linear")]
        if self._act_layer.activation in linear_activations:
            tmp = reversed_Ys
        else:
            # if linear activation this behaves strange
            tmp = iutils.to_list(grad_act(act_Xs + act_Ys + reversed_Ys))

        # Second step: propagate through the pattern layer.
        return grad_pattern(Xs + pattern_Ys + tmp)
Exemple #29
0
def pre_softmax_tensors(Xs: Tensor,
                        should_find_softmax: bool = True) -> List[Tensor]:
    """Finds the tensors that were preceeding a potential softmax."""
    softmax_found = False

    Xs = iutils.to_list(Xs)
    ret = []
    for x in Xs:
        layer, node_index, _tensor_index = x._keras_history
        if kchecks.contains_activation(layer, activation="softmax"):
            softmax_found = True
            if isinstance(layer, keras.layers.Activation):
                ret.append(layer.get_input_at(node_index))
            else:
                layer_wo_act = copy_layer_wo_activation(layer)
                ret.append(layer_wo_act(layer.get_input_at(node_index)))

    if should_find_softmax and not softmax_found:
        raise Exception("No softmax found.")

    return ret
Exemple #30
0
def model_contains(
    model: Model,
    layer_condition: OptionalList[LayerCheck],
) -> List[List[Layer]]:
    """
    Collect layers in model which satisfy `layer_condition`.
    If multiple conditions are given in `layer_condition`,
    the collected layers are returned for each condition.

    :param model: A Keras model.
    :type model: Model
    :param layer_condition: A boolean function or list of functions that
        check Keras layers.
    :type layer_condition: Union[LayerCheck, List[LayerCheck]]
    :return: List, which for each condition in layer_condition
        contains a list of layers which satisfy that condition.
    :rtype: List[List[Layer]]
    """
    conditions = iutils.to_list(layer_condition)
    layers = get_model_layers(model)

    # return layers for which condition c holds true
    return [[l for l in layers if c(l)] for c in conditions]