class_docstr: This decomposition rule handles the positive forward
  activations (x*w > 0) and negative forward activations
  (w * x < 0) independently, reducing the risk of zero
  divisions considerably. In fact, the only case where
  divisions by zero can happen is if there are either
  no positive or no negative parts to the activation
  at all.
  Corresponding parameterization of this rule implement
  methods such as Excitation Backpropagation with
  alpha=1, beta=0
  s.t.
  alpha - beta = 1 (after current param. scheme.)
  and
  alpha > 1
  beta > 0
class_docstr_tok: ["This","decomposition","rule","handles","the","positive","forward","activations","(","x","*","w",">","0",")","and","negative","forward","activations","(","w","*","x","<","0",")","independently",",","reducing","the","risk","of","zero","divisions","considerably",".","In","fact",",","the","only","case","where","divisions","by","zero","can","happen","is","if","there","are","either","no","positive","or","no","negative","parts","to","the","activation","at","all",".","Corresponding","parameterization","of","this","rule","implement","methods","such","as","Excitation","Backpropagation","with","alpha=1",",","beta=0","s.t",".","alpha","-","beta","=","1","(","after","current","param",".","scheme",".",")","and","alpha",">","1","beta",">","0"]
code: def apply(
      self,
      Xs: list[Tensor],
      _Ys: list[Tensor],
      Rs: list[Tensor],
      _reverse_state: dict,
  ):
      # this method is correct, but wasteful
      times_alpha = klayers.Lambda(lambda x: x * self._alpha)
      times_beta = klayers.Lambda(lambda x: x * self._beta)
      keep_positives = klayers.Lambda(
          lambda x: x
          * kbackend.cast(kbackend.greater(x, 0), kbackend.floatx())
      )
      keep_negatives = klayers.Lambda(
          lambda x: x
          * kbackend.cast(kbackend.less(x, 0), kbackend.floatx())
      )
  
      def fn_tmp(
          layer1: Layer,
          layer2: Layer,
          X1: OptionalList[Tensor],
          X2: OptionalList[Tensor],
      ):
          # Get activations of full positive or negative part.
          Z1 = ibackend.apply(layer1, X1)
          Z2 = ibackend.apply(layer2, X2)
          Zs = [klayers.Add()([a, b]) for a, b in zip(Z1, Z2)]
          # Divide incoming relevance by the activations.
          tmp = [ibackend.safe_divide(a, b) for a, b in zip(Rs, Zs)]
          # Propagate the relevance to the input neurons
          # using the gradient
          grads1 = ibackend.gradients(X1, Z1, tmp)
          grads2 = ibackend.gradients(X2, Z2, tmp)
          # Re-weight relevance with the input values.
          tmp1 = [
              klayers.Multiply()([a, b]) for a, b in zip(X1, grads1)
          ]
          tmp2 = [
              klayers.Multiply()([a, b]) for a, b in zip(X2, grads2)
          ]
          # combine and return
          return [klayers.Add()([a, b]) for a, b in zip(tmp1, tmp2)]
  
      # Distinguish postive and negative inputs.
      Xs_pos = ibackend.apply(keep_positives, Xs)
      Xs_neg = ibackend.apply(keep_negatives, Xs)
      # xpos*wpos + xneg*wneg
      activator_relevances = fn_tmp(
          self._layer_wo_act_positive,
          self._layer_wo_act_negative,
          Xs_pos,
          Xs_neg,
      )
  
      if (
          self._beta
      ):  # only compute beta-weighted contributions of beta is not zero
          # xpos*wneg + xneg*wpos
          inhibitor_relevances = fn_tmp(
              self._layer_wo_act_negative,
              self._layer_wo_act_positive,
              Xs_pos,
              Xs_neg,
          )
          return [
              klayers.Subtract()([times_alpha(a), times_beta(b)])
              for a, b in zip(
                  activator_relevances, inhibitor_relevances
              )
          ]
      return activator_relevances
  
imports: from __future__ import annotations
  import numpy
  import tensorflow.keras.backend
  import tensorflow.keras.layers
  import innvestigate.analyzer.relevance_based.utils
  import innvestigate.backend
  import innvestigate.backend.graph
  import innvestigate.layers
  from innvestigate.backend.types import Layer, OptionalList, Tensor
label_desc: Use image node_id 2 for calling the AlphaBetaRule obj's underlying member method code with example usage: obj.apply(Xs, _Ys, Rs, _reverse_state) and returns: activator_relevances, unknown, unknown