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:
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
| 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
|