autoLRP 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- autolrp-0.1.0/LICENSE +21 -0
- autolrp-0.1.0/PKG-INFO +207 -0
- autolrp-0.1.0/README.md +176 -0
- autolrp-0.1.0/autoLRP/__init__.py +55 -0
- autolrp-0.1.0/autoLRP/backward/__init__.py +5 -0
- autolrp-0.1.0/autoLRP/backward/analysis.py +423 -0
- autolrp-0.1.0/autoLRP/backward/engine.py +299 -0
- autolrp-0.1.0/autoLRP/backward/install.py +850 -0
- autolrp-0.1.0/autoLRP/backward/lrp_utils.py +353 -0
- autolrp-0.1.0/autoLRP/backward/resolve.py +51 -0
- autolrp-0.1.0/autoLRP/backward/rules.py +294 -0
- autolrp-0.1.0/autoLRP/backward/strategies.py +288 -0
- autolrp-0.1.0/autoLRP/compat.py +54 -0
- autolrp-0.1.0/autoLRP/config.py +284 -0
- autolrp-0.1.0/autoLRP/eval.py +357 -0
- autolrp-0.1.0/autoLRP/forward/__init__.py +3 -0
- autolrp-0.1.0/autoLRP/forward/intercept.py +511 -0
- autolrp-0.1.0/autoLRP/forward/ops.py +176 -0
- autolrp-0.1.0/autoLRP/recipes.py +150 -0
- autolrp-0.1.0/autoLRP/tensor.py +111 -0
- autolrp-0.1.0/autoLRP/utils.py +21 -0
- autolrp-0.1.0/autoLRP.egg-info/PKG-INFO +207 -0
- autolrp-0.1.0/autoLRP.egg-info/SOURCES.txt +40 -0
- autolrp-0.1.0/autoLRP.egg-info/dependency_links.txt +1 -0
- autolrp-0.1.0/autoLRP.egg-info/requires.txt +9 -0
- autolrp-0.1.0/autoLRP.egg-info/top_level.txt +1 -0
- autolrp-0.1.0/pyproject.toml +41 -0
- autolrp-0.1.0/setup.cfg +4 -0
- autolrp-0.1.0/tests/test_addressing.py +343 -0
- autolrp-0.1.0/tests/test_config.py +250 -0
- autolrp-0.1.0/tests/test_conservation.py +242 -0
- autolrp-0.1.0/tests/test_eval.py +258 -0
- autolrp-0.1.0/tests/test_explain.py +71 -0
- autolrp-0.1.0/tests/test_integration.py +214 -0
- autolrp-0.1.0/tests/test_loud_fallback.py +43 -0
- autolrp-0.1.0/tests/test_modes.py +109 -0
- autolrp-0.1.0/tests/test_ops.py +266 -0
- autolrp-0.1.0/tests/test_parity.py +183 -0
- autolrp-0.1.0/tests/test_recipes.py +192 -0
- autolrp-0.1.0/tests/test_registration_api.py +40 -0
- autolrp-0.1.0/tests/test_rules.py +520 -0
- autolrp-0.1.0/tests/test_strategies.py +228 -0
autolrp-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Waleed Alasad
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
autolrp-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: autoLRP
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Layer-wise relevance propagation on the PyTorch autograd graph
|
|
5
|
+
Author: Waleed Alasad
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Wa-lead/autoLRP
|
|
8
|
+
Project-URL: Repository, https://github.com/Wa-lead/autoLRP
|
|
9
|
+
Keywords: lrp,layer-wise-relevance-propagation,explainability,xai,interpretability,attribution,pytorch
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: torch>=2.1
|
|
24
|
+
Requires-Dist: numpy
|
|
25
|
+
Provides-Extra: test
|
|
26
|
+
Requires-Dist: pytest; extra == "test"
|
|
27
|
+
Requires-Dist: pytest-randomly; extra == "test"
|
|
28
|
+
Provides-Extra: parity
|
|
29
|
+
Requires-Dist: zennit; extra == "parity"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
<p align="center">
|
|
33
|
+
<picture>
|
|
34
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Wa-lead/autoLRP/main/assets/autolrp-lockup-dark.png">
|
|
35
|
+
<img src="https://raw.githubusercontent.com/Wa-lead/autoLRP/main/assets/autolrp-lockup-light.png" alt="autoLRP" width="360">
|
|
36
|
+
</picture>
|
|
37
|
+
</p>
|
|
38
|
+
|
|
39
|
+
# autoLRP
|
|
40
|
+
|
|
41
|
+
Layer-wise relevance propagation on the PyTorch autograd graph. No
|
|
42
|
+
model rewriting, no module names: wrap the input, run the model as it
|
|
43
|
+
is, pick the output scalar, call `.lrp()`.
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import torch.nn as nn
|
|
47
|
+
import autoLRP
|
|
48
|
+
from autoLRP import LRPConfig, BASE
|
|
49
|
+
|
|
50
|
+
x = autoLRP.tensor(image) # the input you want relevance for
|
|
51
|
+
out = model(x)
|
|
52
|
+
out[0, pred].lrp() # relevance of class `pred`
|
|
53
|
+
heatmap = x.relevance # same shape as `image`
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## How it works
|
|
57
|
+
|
|
58
|
+
`autoLRP.tensor` returns a tensor subclass. While the model runs, a
|
|
59
|
+
few ops are replaced by our own (`add`, `sub`, `mean`, `sum`, `cumsum`,
|
|
60
|
+
`softmax`, fused attention) so that the backward graph keeps the values
|
|
61
|
+
the rules need; their gradients are the native ones, so the graph is
|
|
62
|
+
otherwise unchanged. `.lrp()` walks the autograd graph, attaches a
|
|
63
|
+
label ("fact") to some nodes (for example, which operand of a product
|
|
64
|
+
is the softmax weights), installs one hook per node that turns the
|
|
65
|
+
arriving gradient into relevance, and runs `backward`. Whatever reaches
|
|
66
|
+
the wrapped input is its relevance.
|
|
67
|
+
|
|
68
|
+
## Configuration
|
|
69
|
+
|
|
70
|
+
Every rule-bearing node is addressed by its autograd name without the
|
|
71
|
+
version digit, or by a fact an analyzer attached to it. `BASE` is the
|
|
72
|
+
starting table:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
>>> print(BASE)
|
|
76
|
+
{'AddmmBackward': 'epsilon', 'MmBackward': 'epsilon', 'ConvolutionBackward': 'epsilon',
|
|
77
|
+
'BmmBackward': 'epsilon', 'MulBackward': 'proportional', 'DivBackward': 'proportional',
|
|
78
|
+
'AddBackward': 'proportional', 'SubBackward': 'proportional',
|
|
79
|
+
'statistic_operand': ('detach', {'by': 'statistic_operand'})}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Override entries on it, or use a preset:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
LRPConfig(rule={**BASE, 'AddmmBackward': 'zplus'})
|
|
86
|
+
LRPConfig(rule={**BASE, 'ConvolutionBackward': ('gamma', {'gamma': 0.25})})
|
|
87
|
+
LRPConfig.composite() # z+ on conv, epsilon elsewhere
|
|
88
|
+
LRPConfig(attn='attnlrp') # epsilon products, Jacobian softmax
|
|
89
|
+
LRPConfig(attn='cplrp') # attention weights treated as constants
|
|
90
|
+
LRPConfig(attn='uniform')
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The config says exactly what runs. A key that is not a node name or a
|
|
94
|
+
registered fact, a rule the key's family cannot run, and a node that
|
|
95
|
+
no entry addresses are errors:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
LRPConfig(rule={**BASE, 'linear': 'zplus'})
|
|
99
|
+
ValueError: unknown rule key 'linear': not a node name [...]
|
|
100
|
+
LRPConfig(rule={**BASE, 'MulBackward': 'zbox'})
|
|
101
|
+
ValueError: rule entry 'MulBackward'='zbox': 'zbox' is not a choice here. Choices: [...]
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Rule tables, by family:
|
|
105
|
+
|
|
106
|
+
| family | node names | rules |
|
|
107
|
+
| --- | --- | --- |
|
|
108
|
+
| linear | `AddmmBackward`, `MmBackward`, `ConvolutionBackward` | `epsilon`, `zplus`, `gamma`, `gamma_montavon`, `alpha_beta`, `zbox` |
|
|
109
|
+
| bilinear | `BmmBackward` | `epsilon`, `uniform`, `detach_lhs`, `detach_rhs` |
|
|
110
|
+
| product | `MulBackward`, `DivBackward` | `proportional`, `detach_lhs`, `detach_rhs` |
|
|
111
|
+
| sum | `AddBackward`, `SubBackward` | `proportional`, `equal`, `fixed`, `detach_lhs`, `detach_rhs` |
|
|
112
|
+
|
|
113
|
+
Names are positional: `detach_lhs` zeros the operand written on the
|
|
114
|
+
left of that op, always. The one virtual name `'detach'` takes
|
|
115
|
+
`by=<fact>` and picks the side per node from the fact's value.
|
|
116
|
+
|
|
117
|
+
Which family a product node belongs to is decided by which of its
|
|
118
|
+
operands come from the wrapped input, not by its name. One operand:
|
|
119
|
+
the op is a linear layer whose weight is the other operand (a constant,
|
|
120
|
+
a parameter, or any tensor you did not wrap). Two: the op is bilinear.
|
|
121
|
+
Frozen models (`requires_grad=False`) work like trainable ones.
|
|
122
|
+
|
|
123
|
+
Softmax, layer norm and activation nodes are set by their own fields:
|
|
124
|
+
`softmax=` (`passthrough`, `jacobian`, `detach`), `layernorm=`
|
|
125
|
+
(`identity`, `passthrough`, `yx`, `detach_std`), `activation=`
|
|
126
|
+
(`passthrough`, `yx`).
|
|
127
|
+
|
|
128
|
+
## Reading back what ran
|
|
129
|
+
|
|
130
|
+
`explain` installs the hooks, records the config entry and the rule
|
|
131
|
+
function at every node, removes the hooks again, and runs no backward.
|
|
132
|
+
`explain_summary` prints one line per distinct combination:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from autoLRP import explain, explain_summary
|
|
136
|
+
rows = explain(model(autoLRP.tensor(x))[0, pred], LRPConfig(attn='cplrp'))
|
|
137
|
+
print(explain_summary([r for r in rows if r[2] != 'native gradient']))
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
count node key what
|
|
142
|
+
4 AddmmBackward0 AddmmBackward epsilon
|
|
143
|
+
2 AddBackward AddBackward residual_proportional
|
|
144
|
+
2 NativeLayerNormBackward0 None layernorm=identity
|
|
145
|
+
1 BmmBackward0 weights_operand detach_lhs_bmm
|
|
146
|
+
1 BmmBackward0 BmmBackward epsilon_bmm
|
|
147
|
+
1 DivBackward0 None passthrough (constant operand)
|
|
148
|
+
1 SoftmaxBackward None softmax=passthrough
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`key` is the entry that addressed the node, `what` the function it
|
|
152
|
+
picked; `native gradient` rows are shape ops, where the gradient is
|
|
153
|
+
already the routing.
|
|
154
|
+
|
|
155
|
+
## Facts and your own analyzers
|
|
156
|
+
|
|
157
|
+
Built-in facts: `statistic_operand` (the normalization statistic in a
|
|
158
|
+
mul, div or sub, detached by `BASE`), `weights_operand` (the softmax
|
|
159
|
+
weights in a bmm), `input_conv` (the first convolution). An analyzer is
|
|
160
|
+
a function over all nodes that returns the nodes carrying its fact; its
|
|
161
|
+
registered name is the config key. The value is the fact's value:
|
|
162
|
+
`True` for a plain tag, a slot number (0 or 1) for a side that a
|
|
163
|
+
`('detach', {'by': ...})` entry can read:
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
from autoLRP import register_analyzer
|
|
167
|
+
|
|
168
|
+
@register_analyzer('first_linear')
|
|
169
|
+
def first_linear(nodes):
|
|
170
|
+
hits = [n for n in nodes if 'AddmmBackward' in n.name()]
|
|
171
|
+
return {hits[-1]: True} if hits else {}
|
|
172
|
+
|
|
173
|
+
LRPConfig(rule={**BASE, 'first_linear': ('zbox', {'low': -3.0, 'high': 3.0})})
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Conventions worth knowing
|
|
177
|
+
|
|
178
|
+
- A constant that multiplies, divides or negates passes relevance
|
|
179
|
+
through unchanged. A constant that is added is a bias, and a bias
|
|
180
|
+
absorbs its share (`apply_bias_split`), so a layer with a bias emits
|
|
181
|
+
less than it receives.
|
|
182
|
+
- Fused `scaled_dot_product_attention` is decomposed into
|
|
183
|
+
matmul, softmax, matmul by default; `set_decompose_attention(False)`
|
|
184
|
+
keeps the fused node, which is handled by its own installer and gives
|
|
185
|
+
the same relevance to 1e-13.
|
|
186
|
+
- An op with no installer runs its native gradient and warns once,
|
|
187
|
+
if it lies on the path to the wrapped input.
|
|
188
|
+
- `BASE` with the default `eps=1e-11` is LRP-0; on deep networks it
|
|
189
|
+
can be numerically unstable, and the recipes (`composite`, gamma,
|
|
190
|
+
z+) are what to use there.
|
|
191
|
+
|
|
192
|
+
## Recipes and evaluation
|
|
193
|
+
|
|
194
|
+
`autoLRP.bilrp(model, x_a, x_b)` (second-order, similarity models),
|
|
195
|
+
`autoLRP.clrp(...)` (contrastive), and `autoLRP.eval` with
|
|
196
|
+
`perturbation_curve`, `aopc`, `sanity_check_cascade`,
|
|
197
|
+
`sensitivity_correlation`.
|
|
198
|
+
|
|
199
|
+
## Tests
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
python -m pytest -q
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
352 tests; two modules skip without `zennit` and the examples file.
|
|
206
|
+
The notebooks under `examples/showcase` are the showcases; the four in
|
|
207
|
+
`extras/` run without downloads.
|
autolrp-0.1.0/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Wa-lead/autoLRP/main/assets/autolrp-lockup-dark.png">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/Wa-lead/autoLRP/main/assets/autolrp-lockup-light.png" alt="autoLRP" width="360">
|
|
5
|
+
</picture>
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
# autoLRP
|
|
9
|
+
|
|
10
|
+
Layer-wise relevance propagation on the PyTorch autograd graph. No
|
|
11
|
+
model rewriting, no module names: wrap the input, run the model as it
|
|
12
|
+
is, pick the output scalar, call `.lrp()`.
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
import torch.nn as nn
|
|
16
|
+
import autoLRP
|
|
17
|
+
from autoLRP import LRPConfig, BASE
|
|
18
|
+
|
|
19
|
+
x = autoLRP.tensor(image) # the input you want relevance for
|
|
20
|
+
out = model(x)
|
|
21
|
+
out[0, pred].lrp() # relevance of class `pred`
|
|
22
|
+
heatmap = x.relevance # same shape as `image`
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## How it works
|
|
26
|
+
|
|
27
|
+
`autoLRP.tensor` returns a tensor subclass. While the model runs, a
|
|
28
|
+
few ops are replaced by our own (`add`, `sub`, `mean`, `sum`, `cumsum`,
|
|
29
|
+
`softmax`, fused attention) so that the backward graph keeps the values
|
|
30
|
+
the rules need; their gradients are the native ones, so the graph is
|
|
31
|
+
otherwise unchanged. `.lrp()` walks the autograd graph, attaches a
|
|
32
|
+
label ("fact") to some nodes (for example, which operand of a product
|
|
33
|
+
is the softmax weights), installs one hook per node that turns the
|
|
34
|
+
arriving gradient into relevance, and runs `backward`. Whatever reaches
|
|
35
|
+
the wrapped input is its relevance.
|
|
36
|
+
|
|
37
|
+
## Configuration
|
|
38
|
+
|
|
39
|
+
Every rule-bearing node is addressed by its autograd name without the
|
|
40
|
+
version digit, or by a fact an analyzer attached to it. `BASE` is the
|
|
41
|
+
starting table:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
>>> print(BASE)
|
|
45
|
+
{'AddmmBackward': 'epsilon', 'MmBackward': 'epsilon', 'ConvolutionBackward': 'epsilon',
|
|
46
|
+
'BmmBackward': 'epsilon', 'MulBackward': 'proportional', 'DivBackward': 'proportional',
|
|
47
|
+
'AddBackward': 'proportional', 'SubBackward': 'proportional',
|
|
48
|
+
'statistic_operand': ('detach', {'by': 'statistic_operand'})}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Override entries on it, or use a preset:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
LRPConfig(rule={**BASE, 'AddmmBackward': 'zplus'})
|
|
55
|
+
LRPConfig(rule={**BASE, 'ConvolutionBackward': ('gamma', {'gamma': 0.25})})
|
|
56
|
+
LRPConfig.composite() # z+ on conv, epsilon elsewhere
|
|
57
|
+
LRPConfig(attn='attnlrp') # epsilon products, Jacobian softmax
|
|
58
|
+
LRPConfig(attn='cplrp') # attention weights treated as constants
|
|
59
|
+
LRPConfig(attn='uniform')
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The config says exactly what runs. A key that is not a node name or a
|
|
63
|
+
registered fact, a rule the key's family cannot run, and a node that
|
|
64
|
+
no entry addresses are errors:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
LRPConfig(rule={**BASE, 'linear': 'zplus'})
|
|
68
|
+
ValueError: unknown rule key 'linear': not a node name [...]
|
|
69
|
+
LRPConfig(rule={**BASE, 'MulBackward': 'zbox'})
|
|
70
|
+
ValueError: rule entry 'MulBackward'='zbox': 'zbox' is not a choice here. Choices: [...]
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Rule tables, by family:
|
|
74
|
+
|
|
75
|
+
| family | node names | rules |
|
|
76
|
+
| --- | --- | --- |
|
|
77
|
+
| linear | `AddmmBackward`, `MmBackward`, `ConvolutionBackward` | `epsilon`, `zplus`, `gamma`, `gamma_montavon`, `alpha_beta`, `zbox` |
|
|
78
|
+
| bilinear | `BmmBackward` | `epsilon`, `uniform`, `detach_lhs`, `detach_rhs` |
|
|
79
|
+
| product | `MulBackward`, `DivBackward` | `proportional`, `detach_lhs`, `detach_rhs` |
|
|
80
|
+
| sum | `AddBackward`, `SubBackward` | `proportional`, `equal`, `fixed`, `detach_lhs`, `detach_rhs` |
|
|
81
|
+
|
|
82
|
+
Names are positional: `detach_lhs` zeros the operand written on the
|
|
83
|
+
left of that op, always. The one virtual name `'detach'` takes
|
|
84
|
+
`by=<fact>` and picks the side per node from the fact's value.
|
|
85
|
+
|
|
86
|
+
Which family a product node belongs to is decided by which of its
|
|
87
|
+
operands come from the wrapped input, not by its name. One operand:
|
|
88
|
+
the op is a linear layer whose weight is the other operand (a constant,
|
|
89
|
+
a parameter, or any tensor you did not wrap). Two: the op is bilinear.
|
|
90
|
+
Frozen models (`requires_grad=False`) work like trainable ones.
|
|
91
|
+
|
|
92
|
+
Softmax, layer norm and activation nodes are set by their own fields:
|
|
93
|
+
`softmax=` (`passthrough`, `jacobian`, `detach`), `layernorm=`
|
|
94
|
+
(`identity`, `passthrough`, `yx`, `detach_std`), `activation=`
|
|
95
|
+
(`passthrough`, `yx`).
|
|
96
|
+
|
|
97
|
+
## Reading back what ran
|
|
98
|
+
|
|
99
|
+
`explain` installs the hooks, records the config entry and the rule
|
|
100
|
+
function at every node, removes the hooks again, and runs no backward.
|
|
101
|
+
`explain_summary` prints one line per distinct combination:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from autoLRP import explain, explain_summary
|
|
105
|
+
rows = explain(model(autoLRP.tensor(x))[0, pred], LRPConfig(attn='cplrp'))
|
|
106
|
+
print(explain_summary([r for r in rows if r[2] != 'native gradient']))
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
count node key what
|
|
111
|
+
4 AddmmBackward0 AddmmBackward epsilon
|
|
112
|
+
2 AddBackward AddBackward residual_proportional
|
|
113
|
+
2 NativeLayerNormBackward0 None layernorm=identity
|
|
114
|
+
1 BmmBackward0 weights_operand detach_lhs_bmm
|
|
115
|
+
1 BmmBackward0 BmmBackward epsilon_bmm
|
|
116
|
+
1 DivBackward0 None passthrough (constant operand)
|
|
117
|
+
1 SoftmaxBackward None softmax=passthrough
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`key` is the entry that addressed the node, `what` the function it
|
|
121
|
+
picked; `native gradient` rows are shape ops, where the gradient is
|
|
122
|
+
already the routing.
|
|
123
|
+
|
|
124
|
+
## Facts and your own analyzers
|
|
125
|
+
|
|
126
|
+
Built-in facts: `statistic_operand` (the normalization statistic in a
|
|
127
|
+
mul, div or sub, detached by `BASE`), `weights_operand` (the softmax
|
|
128
|
+
weights in a bmm), `input_conv` (the first convolution). An analyzer is
|
|
129
|
+
a function over all nodes that returns the nodes carrying its fact; its
|
|
130
|
+
registered name is the config key. The value is the fact's value:
|
|
131
|
+
`True` for a plain tag, a slot number (0 or 1) for a side that a
|
|
132
|
+
`('detach', {'by': ...})` entry can read:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from autoLRP import register_analyzer
|
|
136
|
+
|
|
137
|
+
@register_analyzer('first_linear')
|
|
138
|
+
def first_linear(nodes):
|
|
139
|
+
hits = [n for n in nodes if 'AddmmBackward' in n.name()]
|
|
140
|
+
return {hits[-1]: True} if hits else {}
|
|
141
|
+
|
|
142
|
+
LRPConfig(rule={**BASE, 'first_linear': ('zbox', {'low': -3.0, 'high': 3.0})})
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Conventions worth knowing
|
|
146
|
+
|
|
147
|
+
- A constant that multiplies, divides or negates passes relevance
|
|
148
|
+
through unchanged. A constant that is added is a bias, and a bias
|
|
149
|
+
absorbs its share (`apply_bias_split`), so a layer with a bias emits
|
|
150
|
+
less than it receives.
|
|
151
|
+
- Fused `scaled_dot_product_attention` is decomposed into
|
|
152
|
+
matmul, softmax, matmul by default; `set_decompose_attention(False)`
|
|
153
|
+
keeps the fused node, which is handled by its own installer and gives
|
|
154
|
+
the same relevance to 1e-13.
|
|
155
|
+
- An op with no installer runs its native gradient and warns once,
|
|
156
|
+
if it lies on the path to the wrapped input.
|
|
157
|
+
- `BASE` with the default `eps=1e-11` is LRP-0; on deep networks it
|
|
158
|
+
can be numerically unstable, and the recipes (`composite`, gamma,
|
|
159
|
+
z+) are what to use there.
|
|
160
|
+
|
|
161
|
+
## Recipes and evaluation
|
|
162
|
+
|
|
163
|
+
`autoLRP.bilrp(model, x_a, x_b)` (second-order, similarity models),
|
|
164
|
+
`autoLRP.clrp(...)` (contrastive), and `autoLRP.eval` with
|
|
165
|
+
`perturbation_curve`, `aopc`, `sanity_check_cascade`,
|
|
166
|
+
`sensitivity_correlation`.
|
|
167
|
+
|
|
168
|
+
## Tests
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
python -m pytest -q
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
352 tests; two modules skip without `zennit` and the examples file.
|
|
175
|
+
The notebooks under `examples/showcase` are the showcases; the four in
|
|
176
|
+
`extras/` run without downloads.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
r"""autoLRP: layer-wise relevance propagation on the autograd graph.
|
|
2
|
+
|
|
3
|
+
Wrap the input, run the model, pick the output scalar, call ``.lrp()``::
|
|
4
|
+
|
|
5
|
+
import autoLRP
|
|
6
|
+
from autoLRP import LRPConfig, BASE
|
|
7
|
+
|
|
8
|
+
x = autoLRP.tensor(image)
|
|
9
|
+
out = model(x)
|
|
10
|
+
out[0, pred].lrp() # BASE: epsilon on linear
|
|
11
|
+
heatmap = x.relevance # families, proportional elsewhere
|
|
12
|
+
|
|
13
|
+
Every rule-bearing node in the graph is addressed by its name without
|
|
14
|
+
the version digit, or by a fact an analyzer attached to it. ``BASE`` is
|
|
15
|
+
the printed starting table; override entries on it::
|
|
16
|
+
|
|
17
|
+
out[0, pred].lrp(config=LRPConfig(rule={**BASE, 'BmmBackward': 'uniform'}))
|
|
18
|
+
out[0, pred].lrp(config=LRPConfig(attn='attnlrp'))
|
|
19
|
+
|
|
20
|
+
Anything that is not a node name or a registered fact, a rule the
|
|
21
|
+
node's family cannot run, or a node no entry addresses, is an error.
|
|
22
|
+
"""
|
|
23
|
+
__version__ = '0.1.0'
|
|
24
|
+
|
|
25
|
+
from .compat import run_selfcheck
|
|
26
|
+
run_selfcheck()
|
|
27
|
+
|
|
28
|
+
from .config import LRPConfig, BASE
|
|
29
|
+
from .tensor import LRPTensor, tensor
|
|
30
|
+
from .forward.intercept import (
|
|
31
|
+
register_rewrite, REWRITES,
|
|
32
|
+
set_decompose_attention, get_decompose_attention, decompose_attention,
|
|
33
|
+
)
|
|
34
|
+
from .backward.strategies import (
|
|
35
|
+
register_installer, installer, merge, match_installer, is_shape_node,
|
|
36
|
+
EXPLICIT_STRATEGY, INSTALLERS,
|
|
37
|
+
)
|
|
38
|
+
from .backward.analysis import (
|
|
39
|
+
register_analyzer, ANALYZERS, node_facts,
|
|
40
|
+
)
|
|
41
|
+
from .backward.engine import graph_lrp, walk, plan_report, explain, explain_summary
|
|
42
|
+
from .recipes import bilrp, clrp
|
|
43
|
+
from . import eval
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
'LRPConfig', 'BASE', 'LRPTensor', 'tensor',
|
|
47
|
+
'register_rewrite', 'REWRITES',
|
|
48
|
+
'set_decompose_attention', 'get_decompose_attention',
|
|
49
|
+
'decompose_attention',
|
|
50
|
+
'register_installer', 'installer', 'merge', 'match_installer',
|
|
51
|
+
'is_shape_node', 'EXPLICIT_STRATEGY', 'INSTALLERS',
|
|
52
|
+
'register_analyzer', 'ANALYZERS', 'node_facts',
|
|
53
|
+
'graph_lrp', 'walk', 'plan_report', 'explain', 'explain_summary',
|
|
54
|
+
'bilrp', 'clrp', 'eval',
|
|
55
|
+
]
|