def __init__(self, model, patterns=None, pattern_type=None, **kwargs): super().__init__(model, **kwargs) # Add and run model checks self._add_model_softmax_check() self._add_model_check( lambda layer: not kchecks.only_relu_activation(layer), ("PatternNet is not well defined for networks with non-ReLU activations." ), check_type="warning", ) self._add_model_check( lambda layer: not kchecks.is_convnet_layer(layer), ("PatternNet is only well defined for convolutional neural networks." ), check_type="warning", ) self._add_model_check( lambda layer: not isinstance(layer, SUPPORTED_LAYER_PATTERNNET), ("PatternNet is only well defined for conv2d/max-pooling/dense layers." ), check_type="exception", ) self._do_model_checks() self._patterns = patterns if self._patterns is not None: # copy pattern references self._patterns = list(patterns) self._pattern_type = pattern_type # Pattern projections can lead to +-inf value with long networks. # We are only interested in the direction, therefore it is save to # Prevent this by projecting the values in bottleneck layers to +-1. if not kwargs.get("reverse_project_bottleneck_layers", True): warnings.warn( "The standard setting for 'reverse_project_bottleneck_layers'" "is overwritten.") else: kwargs["reverse_project_bottleneck_layers"] = True
def __init__(self, model, *args, **kwargs): rule = kwargs.pop("rule", None) input_layer_rule = kwargs.pop("input_layer_rule", None) self._add_model_softmax_check() self._add_model_check( lambda layer: not kchecks.is_convnet_layer(layer), "LRP is only tested for convolutional neural networks.", check_type="warning", ) # check if rule was given explicitly. # rule can be a string, a list (of strings) or a list of conditions [(Condition, Rule), ... ] for each layer. if rule is None: raise ValueError("Need LRP rule(s).") if isinstance(rule, list): # copy refrences self._rule = list(rule) else: self._rule = rule self._input_layer_rule = input_layer_rule if (isinstance(rule, six.string_types) or (inspect.isclass(rule) and issubclass(rule, kgraph.ReverseMappingBase) ) # NOTE: All LRP rules inherit from kgraph.ReverseMappingBase ): # the given rule is a single string or single rule implementing cla ss use_conditions = True rules = [(lambda a, b: True, rule)] elif not isinstance(rule[0], tuple): # rule list of rule strings or classes use_conditions = False rules = list(rule) else: # rule is list of conditioned rules use_conditions = True rules = rule # create a BoundedRule for input layer handling from given tuple if self._input_layer_rule is not None: input_layer_rule = self._input_layer_rule if isinstance(input_layer_rule, tuple): low, high = input_layer_rule class BoundedProxyRule(rrule.BoundedRule): def __init__(self, *args, **kwargs): super(BoundedProxyRule, self).__init__(*args, low=low, high=high, **kwargs) input_layer_rule = BoundedProxyRule if use_conditions is True: rules.insert(0, (lambda layer, foo: kchecks.is_input_layer(layer), input_layer_rule)) else: rules.insert(0, input_layer_rule) self._rules_use_conditions = use_conditions self._rules = rules # FINALIZED constructor. super(LRP, self).__init__(model, *args, **kwargs)
def __init__( self, model, *args, rule=None, input_layer_rule=None, until_layer_idx=None, until_layer_rule=None, bn_layer_rule=None, bn_layer_fuse_mode: str = "one_linear", **kwargs, ): super().__init__(model, *args, **kwargs) self._input_layer_rule = input_layer_rule self._until_layer_rule = until_layer_rule self._until_layer_idx = until_layer_idx self._bn_layer_rule = bn_layer_rule self._bn_layer_fuse_mode = bn_layer_fuse_mode # Add self._add_model_softmax_check() self._add_model_check( lambda layer: not kchecks.is_convnet_layer(layer), "LRP is only tested for convolutional neural networks.", check_type="warning", ) assert bn_layer_fuse_mode in ["one_linear", "two_linear"] # TODO: refactor rule type checking into separate function # check if rule was given explicitly. # rule can be a string, a list (of strings) or # a list of conditions [(Condition, Rule), ... ] for each layer. if rule is None: raise ValueError("Need LRP rule(s).") if isinstance(rule, list): self._rule = list(rule) else: self._rule = rule if isinstance(rule, str) or ( inspect.isclass(rule) and issubclass(rule, kgraph.ReverseMappingBase) ): # NOTE: All LRP rules inherit from kgraph.ReverseMappingBase # the given rule is a single string or single rule implementing cla ss use_conditions = True rules = [(lambda _: True, rule)] elif not isinstance(rule[0], tuple): # rule list of rule strings or classes use_conditions = False rules = list(rule) else: # rule is list of conditioned rules use_conditions = True rules = rule # apply rule to first self._until_layer_idx layers if self._until_layer_rule is not None and self._until_layer_idx is not None: for i in range(self._until_layer_idx + 1): is_at_idx: LayerCheck = lambda layer: kchecks.is_layer_at_idx(layer, i) rules.insert(0, (is_at_idx, self._until_layer_rule)) # create a BoundedRule for input layer handling from given tuple if self._input_layer_rule is not None: input_layer_rule = self._input_layer_rule if isinstance(input_layer_rule, tuple): low, high = input_layer_rule class BoundedProxyRule(rrule.BoundedRule): def __init__(self, *args, **kwargs): super().__init__(*args, low=low, high=high, **kwargs) input_layer_rule = BoundedProxyRule if use_conditions is True: is_input: LayerCheck = lambda layer: kchecks.is_input_layer(layer) rules.insert(0, (is_input, input_layer_rule)) else: rules.insert(0, input_layer_rule) self._rules_use_conditions = use_conditions self._rules = rules
def __init__(self, model, *args, **kwargs): rule = kwargs.pop("rule", None) input_layer_rule = kwargs.pop("input_layer_rule", None) self._model_checks = [ # TODO: Check for non-linear output in general. { "check": lambda layer: kchecks.contains_activation( layer, activation="softmax"), "type": "exception", "message": "Model should not contain a softmax.", }, { "check": lambda layer: not kchecks.is_convnet_layer(layer), "type": "warning", "message": ("LRP is only tested for " "convolutional neural networks."), }, ] # check if rule was given explicitly. # rule can be a string, a list (of strings) or a list of conditions [(Condition, Rule), ... ] for each layer. if rule is None: raise ValueError("Need LRP rule(s).") if isinstance(rule, list): # copy refrences self._rule = list(rule) else: self._rule = rule self._input_layer_rule = input_layer_rule if( isinstance(rule, six.string_types) or (inspect.isclass(rule) and issubclass(rule, kgraph.ReverseMappingBase)) # NOTE: All LRP rules inherit from kgraph.ReverseMappingBase ): # the given rule is a single string or single rule implementing cla ss use_conditions = True rules = [(lambda a, b: True, rule)] elif not isinstance(rule[0], tuple): # rule list of rule strings or classes use_conditions = False rules = list(rule) else: # rule is list of conditioned rules use_conditions = True rules = rule # create a BoundedRule for input layer handling from given tuple if self._input_layer_rule is not None: input_layer_rule = self._input_layer_rule if isinstance(input_layer_rule, tuple): low, high = input_layer_rule class BoundedProxyRule(rrule.BoundedRule): def __init__(self, *args, **kwargs): super(BoundedProxyRule, self).__init__( *args, low=low, high=high, **kwargs) input_layer_rule = BoundedProxyRule if use_conditions is True: rules.insert(0, (lambda layer, foo: kchecks.is_input_layer(layer), input_layer_rule)) else: rules.insert(0, input_layer_rule) #################################################################### ### Functionality responible for backwards rule selection below #### #################################################################### def select_rule(layer, reverse_state): ##print("in select_rule:", layer.__class__.__name__ , end='->') #debug if use_conditions is True: for condition, rule in rules: if condition(layer, reverse_state): ##print(str(rule)) #debug return rule raise Exception("No rule applies to layer: %s" % layer) else: ##print(str(rules[0]), '(via pop)') #debug return rules.pop() # default backward hook class ReverseLayer(kgraph.ReverseMappingBase): def __init__(self, layer, state): rule_class = select_rule(layer, state) #NOTE: this prevents refactoring. ##print("in ReverseLayer.init:",layer.__class__.__name__,"->" , rule_class if isinstance(rule_class, six.string_types) else rule_class.__name__) #debug if isinstance(rule_class, six.string_types): rule_class = LRP_RULES[rule_class] self._rule = rule_class(layer, state) def apply(self, Xs, Ys, Rs, reverse_state): ##print(" in ReverseLayer.apply:", reverse_state['layer'].__class__.__name__, '(nid: {})'.format(reverse_state['nid']) , '-> {}.apply'.format(self._rule.__class__.__name__)) return self._rule.apply(Xs, Ys, Rs, reverse_state) #specialized backward hooks. TODO: add ReverseLayer class handling layers Without kernel: Add and AvgPool class BatchNormalizationReverseLayer(kgraph.ReverseMappingBase): def __init__(self, layer, state): ##print("in BatchNormalizationReverseLayer.init:", layer.__class__.__name__,"-> Dedicated ReverseLayer class" ) #debug config = layer.get_config() self._center = config['center'] self._scale = config['scale'] self._axis = config['axis'] self._mean = layer.moving_mean self._std = layer.moving_variance if self._center: self._beta = layer.beta #TODO: implement rule support. for BatchNormalization -> [BNEpsilon, BNAlphaBeta, BNIgnore] #super(BatchNormalizationReverseLayer, self).__init__(layer, state) # how to do this: # super.__init__ calls select_rule and sets a self._rule class # check if isinstance(self_rule, EpsiloneRule), then reroute # to BatchNormEpsilonRule. Not pretty, but should work. def apply(self, Xs, Ys, Rs, reverse_state): ##print(" in BatchNormalizationReverseLayer.apply:", reverse_state['layer'].__class__.__name__, '(nid: {})'.format(reverse_state['nid'])) input_shape = [K.int_shape(x) for x in Xs] if len(input_shape) != 1: #extend below lambda layers towards multiple parameters. raise ValueError("BatchNormalizationReverseLayer expects Xs with len(Xs) = 1, but was len(Xs) = {}".format(len(Xs))) input_shape = input_shape[0] # prepare broadcasting shape for layer parameters broadcast_shape = [1] * len(input_shape) broadcast_shape[self._axis] = input_shape[self._axis] broadcast_shape[0] = -1 #reweight relevances as # x * (y - beta) R # Rin = ---------------- * ---- # x - mu y # batch norm can be considered as 3 distinct layers of subtraction, # multiplication and then addition. The multiplicative scaling layer # has no effect on LRP and functions as a linear activation layer minus_mu = keras.layers.Lambda(lambda x: x - K.reshape(self._mean, broadcast_shape)) minus_beta = keras.layers.Lambda(lambda x: x - K.reshape(self._beta, broadcast_shape)) prepare_div = keras.layers.Lambda(lambda x: x + (K.cast(K.greater_equal(x,0), K.floatx())*2-1)*K.epsilon()) x_minus_mu = kutils.apply(minus_mu, Xs) if self._center: y_minus_beta = kutils.apply(minus_beta, Ys) else: y_minus_beta = Ys numerator = [keras.layers.Multiply()([x, ymb, r]) for x, ymb, r in zip(Xs, y_minus_beta, Rs)] denominator = [keras.layers.Multiply()([xmm, y]) for xmm, y in zip(x_minus_mu, Ys)] return [ilayers.SafeDivide()([n, prepare_div(d)]) for n, d in zip(numerator, denominator)] class AddReverseLayer(kgraph.ReverseMappingBase): def __init__(self, layer, state): ##print("in AddReverseLayer.init:", layer.__class__.__name__,"-> Dedicated ReverseLayer class" ) #debug self._layer_wo_act = kgraph.copy_layer_wo_activation(layer, name_template="reversed_kernel_%s") #TODO: implement rule support. #super(AddReverseLayer, self).__init__(layer, state) 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)] class AveragePoolingRerseLayer(kgraph.ReverseMappingBase): def __init__(self, layer, state): ##print("in AveragePoolingRerseLayer.init:", layer.__class__.__name__,"-> Dedicated ReverseLayer class" ) #debug self._layer_wo_act = kgraph.copy_layer_wo_activation(layer, name_template="reversed_kernel_%s") #TODO: implement rule support. #super(AveragePoolingRerseLayer, self).__init__(layer, state) 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)] # conditional mappings layer_criterion -> ReverseLayer on how to handle backward passes through layers. self._conditional_mappings = [ (kchecks.contains_kernel, ReverseLayer), (kchecks.is_batch_normalization_layer, BatchNormalizationReverseLayer), (kchecks.is_average_pooling, AveragePoolingRerseLayer), (kchecks.is_add_layer, AddReverseLayer), ] # FINALIZED constructor. super(LRP, self).__init__(model, *args, **kwargs)