Skip to content

Quantities of Interest

A Quantity of Interest (QoI) is a function of the output that determines the network output behavior that the attributions describe.

The quantity of interest lets us specify what we want to explain. Often, this is the output of the network corresponding to a particular class, addressing, e.g., "Why did the model classify a given image as a car?" However, we could also consider various combinations of outputs, allowing us to ask more specific questions, such as, "Why did the model classify a given image as a sedan and not a convertible?" The former may highlight general “car features,” such as tires, while the latter (called a comparative explanation) might focus on the roof of the car, a “car feature” not shared by convertibles.

ClassQoI

Bases: QoI

Quantity of interest for attributing output towards a specified class.

Source code in trulens_explain/trulens/nn/quantities.py
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
class ClassQoI(QoI):
    """
    Quantity of interest for attributing output towards a specified class.
    """

    def __init__(self, cl: int):
        """
        Parameters:
            cl:
                The index of the class the QoI is for.
        """
        self.cl = cl

    def __str__(self):
        return render_object(self, ["cl"])

    def __call__(self, y: TensorLike) -> TensorLike:
        self._assert_cut_contains_only_one_tensor(y)

        return y[:, self.cl]

__init__(cl)

Parameters:

Name Type Description Default
cl int

The index of the class the QoI is for.

required
Source code in trulens_explain/trulens/nn/quantities.py
243
244
245
246
247
248
249
def __init__(self, cl: int):
    """
    Parameters:
        cl:
            The index of the class the QoI is for.
    """
    self.cl = cl

ClassSeqQoI

Bases: QoI

Quantity of interest for attributing output towards a sequence of classes for each input.

Source code in trulens_explain/trulens/nn/quantities.py
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
class ClassSeqQoI(QoI):
    """
    Quantity of interest for attributing output towards a sequence of classes 
    for each input.
    """

    def __init__(self, seq_labels: List[int]):
        """
        Parameters:
            seq_labels:
                A sequence of classes corresponding to each input.
        """
        self.seq_labels = seq_labels

    def __call__(self, y):

        self._assert_cut_contains_only_one_tensor(y)
        assert get_backend().shape(y)[0] == len(self.seq_labels)

        return y[:, self.seq_labels]

__init__(seq_labels)

Parameters:

Name Type Description Default
seq_labels List[int]

A sequence of classes corresponding to each input.

required
Source code in trulens_explain/trulens/nn/quantities.py
395
396
397
398
399
400
401
def __init__(self, seq_labels: List[int]):
    """
    Parameters:
        seq_labels:
            A sequence of classes corresponding to each input.
    """
    self.seq_labels = seq_labels

ComparativeQoI

Bases: QoI

Quantity of interest for attributing network output towards a given class, relative to another.

Source code in trulens_explain/trulens/nn/quantities.py
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
class ComparativeQoI(QoI):
    """
    Quantity of interest for attributing network output towards a given class, 
    relative to another.
    """

    def __init__(self, cl1: int, cl2: int):
        """
        Parameters:
            cl1:
                The index of the class the QoI is for.
            cl2:
                The index of the class to compare against.
        """
        self.cl1 = cl1
        self.cl2 = cl2

    def __str__(self):
        return render_object(self, ["cl1", "cl2"])

    def __call__(self, y: TensorLike) -> TensorLike:

        self._assert_cut_contains_only_one_tensor(y)

        return y[:, self.cl1] - y[:, self.cl2]

__init__(cl1, cl2)

Parameters:

Name Type Description Default
cl1 int

The index of the class the QoI is for.

required
cl2 int

The index of the class to compare against.

required
Source code in trulens_explain/trulens/nn/quantities.py
266
267
268
269
270
271
272
273
274
275
def __init__(self, cl1: int, cl2: int):
    """
    Parameters:
        cl1:
            The index of the class the QoI is for.
        cl2:
            The index of the class to compare against.
    """
    self.cl1 = cl1
    self.cl2 = cl2

InternalChannelQoI

Bases: QoI

Quantity of interest for attributing output towards the output of an internal convolutional layer channel, aggregating using a specified operation.

Also works for non-convolutional dense layers, where the given neuron's activation is returned.

Source code in trulens_explain/trulens/nn/quantities.py
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
class InternalChannelQoI(QoI):
    """
    Quantity of interest for attributing output towards the output of an 
    internal convolutional layer channel, aggregating using a specified 
    operation.

    Also works for non-convolutional dense layers, where the given neuron's
    activation is returned.
    """

    @staticmethod
    def _batch_sum(x):
        """
        Sums batched 2D channels, leaving the batch dimension unchanged.
        """
        return get_backend().sum(x, axis=(1, 2))

    def __init__(
        self,
        channel: Union[int, List[int]],
        channel_axis: Optional[int] = None,
        agg_fn: Optional[Callable] = None
    ):
        """
        Parameters:
            channel:
                Channel to return. If a list is provided, then the quantity sums 
                over each of the channels in the list.

            channel_axis:
                Channel dimension index, if relevant, e.g., for 2D convolutional
                layers. If `channel_axis` is `None`, then the channel axis of 
                the relevant backend will be used. This argument is not used 
                when the channels are scalars, e.g., for dense layers.

            agg_fn:
                Function with which to aggregate the remaining dimensions 
                (except the batch dimension) in order to get a single scalar 
                value for each channel. If `agg_fn` is `None` then a sum over 
                each neuron in the channel will be taken. This argument is not 
                used when the channels are scalars, e.g., for dense layers.
        """
        if channel_axis is None:
            channel_axis = get_backend().channel_axis
        if agg_fn is None:
            agg_fn = InternalChannelQoI._batch_sum

        self._channel_ax = channel_axis
        self._agg_fn = agg_fn
        self._channels = channel if isinstance(channel, list) else [channel]

    def __call__(self, y: TensorLike) -> TensorLike:
        B = get_backend()
        self._assert_cut_contains_only_one_tensor(y)

        if len(B.int_shape(y)) == 2:
            return sum([y[:, ch] for ch in self._channels])

        elif len(B.int_shape(y)) == 3:
            return sum([self._agg_fn(y[:, :, ch]) for ch in self._channel])

        elif len(B.int_shape(y)) == 4:
            if self._channel_ax == 1:
                return sum([self._agg_fn(y[:, ch]) for ch in self._channels])

            elif self._channel_ax == 3:
                return sum(
                    [self._agg_fn(y[:, :, :, ch]) for ch in self._channels]
                )

            else:
                raise ValueError(
                    'Unsupported channel axis for convolutional layer: {}'.
                    format(self._channel_ax)
                )

        else:
            raise QoiCutSupportError(
                'Unsupported tensor rank for `InternalChannelQoI`: {}'.format(
                    len(B.int_shape(y))
                )
            )

__init__(channel, channel_axis=None, agg_fn=None)

Parameters:

Name Type Description Default
channel Union[int, List[int]]

Channel to return. If a list is provided, then the quantity sums over each of the channels in the list.

required
channel_axis Optional[int]

Channel dimension index, if relevant, e.g., for 2D convolutional layers. If channel_axis is None, then the channel axis of the relevant backend will be used. This argument is not used when the channels are scalars, e.g., for dense layers.

None
agg_fn Optional[Callable]

Function with which to aggregate the remaining dimensions (except the batch dimension) in order to get a single scalar value for each channel. If agg_fn is None then a sum over each neuron in the channel will be taken. This argument is not used when the channels are scalars, e.g., for dense layers.

None
Source code in trulens_explain/trulens/nn/quantities.py
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
def __init__(
    self,
    channel: Union[int, List[int]],
    channel_axis: Optional[int] = None,
    agg_fn: Optional[Callable] = None
):
    """
    Parameters:
        channel:
            Channel to return. If a list is provided, then the quantity sums 
            over each of the channels in the list.

        channel_axis:
            Channel dimension index, if relevant, e.g., for 2D convolutional
            layers. If `channel_axis` is `None`, then the channel axis of 
            the relevant backend will be used. This argument is not used 
            when the channels are scalars, e.g., for dense layers.

        agg_fn:
            Function with which to aggregate the remaining dimensions 
            (except the batch dimension) in order to get a single scalar 
            value for each channel. If `agg_fn` is `None` then a sum over 
            each neuron in the channel will be taken. This argument is not 
            used when the channels are scalars, e.g., for dense layers.
    """
    if channel_axis is None:
        channel_axis = get_backend().channel_axis
    if agg_fn is None:
        agg_fn = InternalChannelQoI._batch_sum

    self._channel_ax = channel_axis
    self._agg_fn = agg_fn
    self._channels = channel if isinstance(channel, list) else [channel]

LambdaQoI

Bases: QoI

Generic quantity of interest allowing the user to specify a function of the model's output as the QoI.

Source code in trulens_explain/trulens/nn/quantities.py
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
class LambdaQoI(QoI):
    """
    Generic quantity of interest allowing the user to specify a function of the
    model's output as the QoI.
    """

    def __init__(self, function: Callable):
        """
        Parameters:
            function:
                A callable that takes a single argument representing the model's 
                tensor output and returns a differentiable batched scalar tensor 
                representing the QoI.
        """
        if len(signature(function).parameters) != 1:
            raise ValueError(
                'QoI function must take exactly 1 argument, but provided '
                'function takes {} arguments'.format(
                    len(signature(function).parameters)
                )
            )

        self.function = function

    def __call__(self, y: TensorLike) -> TensorLike:
        return self.function(y)

__init__(function)

Parameters:

Name Type Description Default
function Callable

A callable that takes a single argument representing the model's tensor output and returns a differentiable batched scalar tensor representing the QoI.

required
Source code in trulens_explain/trulens/nn/quantities.py
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
def __init__(self, function: Callable):
    """
    Parameters:
        function:
            A callable that takes a single argument representing the model's 
            tensor output and returns a differentiable batched scalar tensor 
            representing the QoI.
    """
    if len(signature(function).parameters) != 1:
        raise ValueError(
            'QoI function must take exactly 1 argument, but provided '
            'function takes {} arguments'.format(
                len(signature(function).parameters)
            )
        )

    self.function = function

MaxClassQoI

Bases: QoI

Quantity of interest for attributing output towards the maximum-predicted class.

Source code in trulens_explain/trulens/nn/quantities.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
class MaxClassQoI(QoI):
    """
    Quantity of interest for attributing output towards the maximum-predicted 
    class.
    """

    def __init__(
        self, axis: int = 1, activation: Union[Callable, str, None] = None
    ):
        """
        Parameters:
            axis:
                Output dimension over which max operation is taken.

            activation:
                Activation function to be applied to the output before taking 
                the max. If `activation` is a string, use the corresponding 
                named activation function implemented by the backend. The 
                following strings are currently supported as shorthands for the
                respective standard activation functions:

                - `'sigmoid'` 
                - `'softmax'` 

                If `activation` is `None`, no activation function is applied to
                the input.
        """
        self._axis = axis
        self.activation = activation

    def __str__(self):
        return render_object(self, ["_axis", "activation"])

    def __call__(self, y: TensorLike) -> TensorLike:
        self._assert_cut_contains_only_one_tensor(y)

        if self.activation is not None:
            if isinstance(self.activation, str):
                self.activation = self.activation.lower()
                if self.activation in ['sigmoid', 'softmax']:
                    y = getattr(get_backend(), self.activation)(y)

                else:
                    raise NotImplementedError(
                        'This activation function is not currently supported '
                        'by the backend'
                    )
            else:
                y = self.activation(y)

        return get_backend().max(y, axis=self._axis)

__init__(axis=1, activation=None)

Parameters:

Name Type Description Default
axis int

Output dimension over which max operation is taken.

1
activation Union[Callable, str, None]

Activation function to be applied to the output before taking the max. If activation is a string, use the corresponding named activation function implemented by the backend. The following strings are currently supported as shorthands for the respective standard activation functions:

  • 'sigmoid'
  • 'softmax'

If activation is None, no activation function is applied to the input.

None
Source code in trulens_explain/trulens/nn/quantities.py
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
def __init__(
    self, axis: int = 1, activation: Union[Callable, str, None] = None
):
    """
    Parameters:
        axis:
            Output dimension over which max operation is taken.

        activation:
            Activation function to be applied to the output before taking 
            the max. If `activation` is a string, use the corresponding 
            named activation function implemented by the backend. The 
            following strings are currently supported as shorthands for the
            respective standard activation functions:

            - `'sigmoid'` 
            - `'softmax'` 

            If `activation` is `None`, no activation function is applied to
            the input.
    """
    self._axis = axis
    self.activation = activation

QoI

Bases: AbstractBaseClass

Interface for quantities of interest. The Quantity of Interest (QoI) is a function of the output specified by the slice that determines the network output behavior that the attributions describe.

Source code in trulens_explain/trulens/nn/quantities.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
class QoI(AbstractBaseClass):
    """
    Interface for quantities of interest. The *Quantity of Interest* (QoI) is a
    function of the output specified by the slice that determines the network 
    output behavior that the attributions describe.
    """

    def __str__(self):
        return render_object(self, [])

    # TODO: Need to give a seperate value of y at target instance here since
    # these are values are interventions. Cannot presently define a QoI that says:
    # logits of the predicted class for each instance.
    # Issue GH-72 . Task MLNN-415 .

    def _wrap_public_call(self, y: Outputs[Tensor]) -> Outputs[Tensor]:
        """
        Wrap a public call that may result in one or more tensors. Signature of
        this class is not specific while public calls are flexible.
        """

        return many_of_om(self.__call__(om_of_many(y)))

    @abstractmethod
    def __call__(self, y: OM[Outputs, Tensor]) -> OM[Outputs, Tensor]:
        """
        Computes the distribution of interest from an initial point.

        Parameters:
            y:
                Output point from which the quantity is derived. Must be a
                differentiable tensor.

        Returns:
            A differentiable batched scalar tensor representing the QoI.
        """
        raise NotImplementedError

    def _assert_cut_contains_only_one_tensor(self, x):
        if isinstance(x, DATA_CONTAINER_TYPE):
            raise QoiCutSupportError(
                'Cut provided to quantity of interest was comprised of '
                'multiple tensors, but `{}` is only defined for cuts comprised '
                'of a single tensor (received a list of {} tensors).\n'
                '\n'
                'Either (1) select a slice where the `to_cut` corresponds to a '
                'single tensor, or (2) implement/use a `QoI` object that '
                'supports lists of tensors, i.e., where the parameter, `x`, to '
                '`__call__` is expected/allowed to be a list of {} tensors.'.
                format(self.__class__.__name__, len(x), len(x))
            )

        elif not get_backend().is_tensor(x):
            raise ValueError(
                '`{}` expected to receive an instance of `Tensor`, but '
                'received an instance of {}'.format(
                    self.__class__.__name__, type(x)
                )
            )

__call__(y) abstractmethod

Computes the distribution of interest from an initial point.

Parameters:

Name Type Description Default
y OM[Outputs, Tensor]

Output point from which the quantity is derived. Must be a differentiable tensor.

required

Returns:

Type Description
OM[Outputs, Tensor]

A differentiable batched scalar tensor representing the QoI.

Source code in trulens_explain/trulens/nn/quantities.py
63
64
65
66
67
68
69
70
71
72
73
74
75
76
@abstractmethod
def __call__(self, y: OM[Outputs, Tensor]) -> OM[Outputs, Tensor]:
    """
    Computes the distribution of interest from an initial point.

    Parameters:
        y:
            Output point from which the quantity is derived. Must be a
            differentiable tensor.

    Returns:
        A differentiable batched scalar tensor representing the QoI.
    """
    raise NotImplementedError

QoiCutSupportError

Bases: ValueError

Exception raised if the quantity of interest is called on a cut whose output is not supported by the quantity of interest.

Source code in trulens_explain/trulens/nn/quantities.py
32
33
34
35
36
37
class QoiCutSupportError(ValueError):
    """
    Exception raised if the quantity of interest is called on a cut whose output
    is not supported by the quantity of interest.
    """
    pass

ThresholdQoI

Bases: QoI

Quantity of interest for attributing network output toward the difference between two regions seperated by a given threshold. I.e., the quantity of interest is the "high" elements minus the "low" elements, where the high elements have activations above the threshold and the low elements have activations below the threshold.

Use case: bianry segmentation.

Source code in trulens_explain/trulens/nn/quantities.py
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
class ThresholdQoI(QoI):
    """
    Quantity of interest for attributing network output toward the difference 
    between two regions seperated by a given threshold. I.e., the quantity of
    interest is the "high" elements minus the "low" elements, where the high
    elements have activations above the threshold and the low elements have 
    activations below the threshold.

    Use case: bianry segmentation.
    """

    def __init__(
        self,
        threshold: float,
        low_minus_high: bool = False,
        activation: Union[Callable, str, None] = None
    ):
        """
        Parameters:
            threshold:
                A threshold to determine the element-wise sign of the input 
                tensor. The elements with activations higher than the threshold 
                will retain their sign, while the elements with activations 
                lower than the threshold will have their sign flipped (or vice 
                versa if `low_minus_high` is set to `True`).
            low_minus_high:
                If `True`, substract the output with activations above the 
                threshold from the output with activations below the threshold. 
                If `False`, substract the output with activations below the 
                threshold from the output with activations above the threshold.
            activation: str or function, optional
                Activation function to be applied to the quantity before taking
                the threshold. If `activation` is a string, use the 
                corresponding activation function implemented by the backend 
                (currently supported: `'sigmoid'` and `'softmax'`). Otherwise, 
                if `activation` is not `None`, it will be treated as a callable.
                If `activation` is `None`, do not apply an activation function 
                to the quantity.
        """
        # TODO(klas):should this support an aggregation function? By default
        #   this is a sum, but it could, for example, subtract the greatest
        #   positive element from the least negative element.
        self.threshold = threshold
        self.low_minus_high = low_minus_high
        self.activation = activation

    def __call__(self, x: TensorLike) -> TensorLike:
        B = get_backend()
        self._assert_cut_contains_only_one_tensor(x)

        if self.activation is not None:
            if isinstance(self.activation, str):
                self.activation = self.activation.lower()
                if self.activation in ['sigmoid', 'softmax']:
                    x = getattr(B, self.activation)(x)
                else:
                    raise NotImplementedError(
                        'This activation function is not currently supported '
                        'by the backend'
                    )
            else:
                x = self.activation(x)

        # TODO(klas): is the `clone` necessary here? Not sure why it was
        #   included.
        mask = B.sign(B.clone(x) - self.threshold)
        if self.low_minus_high:
            mask = -mask

        non_batch_dimensions = tuple(range(len(B.int_shape(x)))[1:])

        return B.sum(mask * x, axis=non_batch_dimensions)

__init__(threshold, low_minus_high=False, activation=None)

Parameters:

Name Type Description Default
threshold float

A threshold to determine the element-wise sign of the input tensor. The elements with activations higher than the threshold will retain their sign, while the elements with activations lower than the threshold will have their sign flipped (or vice versa if low_minus_high is set to True).

required
low_minus_high bool

If True, substract the output with activations above the threshold from the output with activations below the threshold. If False, substract the output with activations below the threshold from the output with activations above the threshold.

False
activation Union[Callable, str, None]

str or function, optional Activation function to be applied to the quantity before taking the threshold. If activation is a string, use the corresponding activation function implemented by the backend (currently supported: 'sigmoid' and 'softmax'). Otherwise, if activation is not None, it will be treated as a callable. If activation is None, do not apply an activation function to the quantity.

None
Source code in trulens_explain/trulens/nn/quantities.py
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
def __init__(
    self,
    threshold: float,
    low_minus_high: bool = False,
    activation: Union[Callable, str, None] = None
):
    """
    Parameters:
        threshold:
            A threshold to determine the element-wise sign of the input 
            tensor. The elements with activations higher than the threshold 
            will retain their sign, while the elements with activations 
            lower than the threshold will have their sign flipped (or vice 
            versa if `low_minus_high` is set to `True`).
        low_minus_high:
            If `True`, substract the output with activations above the 
            threshold from the output with activations below the threshold. 
            If `False`, substract the output with activations below the 
            threshold from the output with activations above the threshold.
        activation: str or function, optional
            Activation function to be applied to the quantity before taking
            the threshold. If `activation` is a string, use the 
            corresponding activation function implemented by the backend 
            (currently supported: `'sigmoid'` and `'softmax'`). Otherwise, 
            if `activation` is not `None`, it will be treated as a callable.
            If `activation` is `None`, do not apply an activation function 
            to the quantity.
    """
    # TODO(klas):should this support an aggregation function? By default
    #   this is a sum, but it could, for example, subtract the greatest
    #   positive element from the least negative element.
    self.threshold = threshold
    self.low_minus_high = low_minus_high
    self.activation = activation