dl-backtrace 0.0.3__py3-none-any.whl → 0.0.7__py3-none-any.whl
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.
Potentially problematic release.
This version of dl-backtrace might be problematic. Click here for more details.
- dl_backtrace/__init__.py +0 -1
- dl_backtrace/pytorch_backtrace/__init__.py +1 -2
- dl_backtrace/pytorch_backtrace/backtrace/__init__.py +4 -1
- dl_backtrace/pytorch_backtrace/backtrace/backtrace.py +639 -639
- dl_backtrace/pytorch_backtrace/backtrace/config.py +41 -41
- dl_backtrace/pytorch_backtrace/backtrace/utils/__init__.py +2 -0
- dl_backtrace/pytorch_backtrace/backtrace/utils/contrast.py +840 -840
- dl_backtrace/pytorch_backtrace/backtrace/utils/prop.py +746 -746
- dl_backtrace/tf_backtrace/__init__.py +1 -2
- dl_backtrace/tf_backtrace/backtrace/__init__.py +4 -1
- dl_backtrace/tf_backtrace/backtrace/backtrace.py +527 -527
- dl_backtrace/tf_backtrace/backtrace/config.py +41 -41
- dl_backtrace/tf_backtrace/backtrace/utils/__init__.py +2 -0
- dl_backtrace/tf_backtrace/backtrace/utils/contrast.py +834 -834
- dl_backtrace/tf_backtrace/backtrace/utils/prop.py +725 -725
- dl_backtrace/version.py +16 -16
- {dl_backtrace-0.0.3.dist-info → dl_backtrace-0.0.7.dist-info}/LICENSE +21 -21
- {dl_backtrace-0.0.3.dist-info → dl_backtrace-0.0.7.dist-info}/METADATA +52 -52
- dl_backtrace-0.0.7.dist-info/RECORD +21 -0
- dl_backtrace-0.0.3.dist-info/RECORD +0 -21
- {dl_backtrace-0.0.3.dist-info → dl_backtrace-0.0.7.dist-info}/WHEEL +0 -0
- {dl_backtrace-0.0.3.dist-info → dl_backtrace-0.0.7.dist-info}/top_level.txt +0 -0
|
@@ -1,527 +1,527 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
from tensorflow.keras import Model
|
|
3
|
-
|
|
4
|
-
from dl_backtrace.tf_backtrace.backtrace.config import activation_master
|
|
5
|
-
from dl_backtrace.tf_backtrace.backtrace.utils import contrast as UC
|
|
6
|
-
from dl_backtrace.tf_backtrace.backtrace.utils import prop as UP
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class Backtrace(object):
|
|
10
|
-
"""
|
|
11
|
-
This is the constructor method for the Backtrace class. It initializes an instance of the class.
|
|
12
|
-
It takes two optional parameters: model (a neural network model) and activation_dict (a dictionary that maps layer names to activation functions).
|
|
13
|
-
"""
|
|
14
|
-
def __init__(self, model=None, activation_dict={}):
|
|
15
|
-
|
|
16
|
-
#create a tree-like structure that represents the layers of the neural network model
|
|
17
|
-
self.create_tree(model.layers)
|
|
18
|
-
|
|
19
|
-
#create a new model (an instance of tf.keras.Model) that produces the output of each layer in the neural network.
|
|
20
|
-
self.create_model_output(model)
|
|
21
|
-
|
|
22
|
-
#create a layer stack that defines the order in which layers should be processed during backpropagation.
|
|
23
|
-
self.create_layer_stack()
|
|
24
|
-
|
|
25
|
-
#checks if the model is sequential or not. If it's sequential, it adds the input layer to the layer stack.
|
|
26
|
-
if (
|
|
27
|
-
len(self.model_resource[3]) == 0
|
|
28
|
-
or model.__module__.split(".")[-1] == "sequential"
|
|
29
|
-
):
|
|
30
|
-
inp_name = model.input.name
|
|
31
|
-
self.layer_stack.append(inp_name)
|
|
32
|
-
self.model_resource[1][inp_name] = {}
|
|
33
|
-
self.model_resource[1][inp_name]["name"] = inp_name
|
|
34
|
-
self.model_resource[1][inp_name]["type"] = "input"
|
|
35
|
-
self.model_resource[1][inp_name]["parent"] = []
|
|
36
|
-
self.model_resource[1][inp_name]["child"] = None
|
|
37
|
-
self.model_resource[3].append(inp_name)
|
|
38
|
-
self.sequential = True
|
|
39
|
-
else:
|
|
40
|
-
self.sequential = False
|
|
41
|
-
try:
|
|
42
|
-
|
|
43
|
-
#calls the build_activation_dict method to build a dictionary that maps layer names to activation functions.
|
|
44
|
-
#If that fails, it creates a temporary dictionary with default activation functions.
|
|
45
|
-
if len(activation_dict) == 0:
|
|
46
|
-
self.build_activation_dict(model)
|
|
47
|
-
else:
|
|
48
|
-
self.activation_dict = activation_dict
|
|
49
|
-
|
|
50
|
-
except Exception as e:
|
|
51
|
-
print(e)
|
|
52
|
-
temp_dict = {}
|
|
53
|
-
for l in model.layers:
|
|
54
|
-
temp_dict[l.name] = activation_master["None"]
|
|
55
|
-
self.activation_dict = temp_dict
|
|
56
|
-
|
|
57
|
-
def build_activation_dict(self, model):
|
|
58
|
-
# Builds an activation dictionary by inspecting the activation functions of the layers in the model.
|
|
59
|
-
activation_dict = {}
|
|
60
|
-
for l in model.layers:
|
|
61
|
-
if not hasattr(l, "activation"):
|
|
62
|
-
activation_dict[l.name] = activation_master["None"]
|
|
63
|
-
continue
|
|
64
|
-
a1 = l.activation
|
|
65
|
-
func_name = str(a1).split(" ")
|
|
66
|
-
if func_name[0] == "<function":
|
|
67
|
-
activation_dict[l.name] = activation_master.get(
|
|
68
|
-
func_name[1], activation_master["None"]
|
|
69
|
-
)
|
|
70
|
-
else:
|
|
71
|
-
a2 = a1.activation
|
|
72
|
-
func_name = str(a2).split(" ")
|
|
73
|
-
if func_name[0] == "<function":
|
|
74
|
-
activation_dict[l.name] = activation_master.get(
|
|
75
|
-
func_name[1], activation_master["None"]
|
|
76
|
-
)
|
|
77
|
-
self.activation_dict = activation_dict
|
|
78
|
-
|
|
79
|
-
def create_tree(self, layers):
|
|
80
|
-
#Creates a tree structure representing the layers of the model.
|
|
81
|
-
#categorizes layers as input, output, or intermediate layers and establishes parent-child relationships between layers.
|
|
82
|
-
ltree = {}
|
|
83
|
-
layer_tree = {}
|
|
84
|
-
inputs = []
|
|
85
|
-
outputs = []
|
|
86
|
-
intermediates = []
|
|
87
|
-
for l in layers:
|
|
88
|
-
ltree[l.output.name] = {}
|
|
89
|
-
layer_tree[l.output.name] = l
|
|
90
|
-
ltree[l.output.name]["name"] = l.name.split("/")[0]
|
|
91
|
-
ltree[l.output.name]["class"] = type(l).__name__
|
|
92
|
-
if not isinstance(l.input, list):
|
|
93
|
-
if l.input.name == l.output.name:
|
|
94
|
-
ltree[l.output.name]["type"] = "input"
|
|
95
|
-
ltree[l.output.name]["parent"] = []
|
|
96
|
-
ltree[l.output.name]["child"] = None
|
|
97
|
-
inputs.append(l.output.name)
|
|
98
|
-
else:
|
|
99
|
-
ltree[l.output.name]["type"] = "output"
|
|
100
|
-
ltree[l.output.name]["parent"] = []
|
|
101
|
-
ltree[l.output.name]["child"] = [l.input.name]
|
|
102
|
-
outputs.append(l.output.name)
|
|
103
|
-
if l.input.name in ltree:
|
|
104
|
-
if ltree[l.input.name]["type"] != "input":
|
|
105
|
-
ltree[l.input.name]["type"] = "intermediate"
|
|
106
|
-
intermediates.append(l.input.name)
|
|
107
|
-
if l.input.name != l.output.name:
|
|
108
|
-
ltree[l.input.name]["parent"].append(l.output.name)
|
|
109
|
-
else:
|
|
110
|
-
ltree[l.output.name]["type"] = "output"
|
|
111
|
-
ltree[l.output.name]["child"] = [i.name for i in l.input]
|
|
112
|
-
ltree[l.output.name]["parent"] = []
|
|
113
|
-
outputs.append(l.output.name)
|
|
114
|
-
for i in l.input:
|
|
115
|
-
if i.name in ltree:
|
|
116
|
-
if i.name != l.output.name:
|
|
117
|
-
ltree[i.name]["parent"].append(l.output.name)
|
|
118
|
-
if ltree[i.name]["type"] != "input":
|
|
119
|
-
ltree[i.name]["type"] = "intermediate"
|
|
120
|
-
intermediates.append(i.name)
|
|
121
|
-
|
|
122
|
-
outputs = list(set(outputs) - set(intermediates))
|
|
123
|
-
self.model_resource = (layer_tree, ltree, outputs, inputs)
|
|
124
|
-
|
|
125
|
-
def create_layer_stack(self):
|
|
126
|
-
#Creates a layer stack that defines the order in which layers should be processed during backpropagation.
|
|
127
|
-
model_resource = self.model_resource
|
|
128
|
-
start_layer = model_resource[2][0]
|
|
129
|
-
layer_stack = [start_layer]
|
|
130
|
-
temp_stack = [start_layer]
|
|
131
|
-
while len(layer_stack) < len(model_resource[0]):
|
|
132
|
-
start_layer = temp_stack.pop(0)
|
|
133
|
-
if model_resource[1][start_layer]["child"]:
|
|
134
|
-
child_nodes = model_resource[1][start_layer]["child"]
|
|
135
|
-
for ch in child_nodes:
|
|
136
|
-
node_check = True
|
|
137
|
-
for pa in model_resource[1][ch]["parent"]:
|
|
138
|
-
if pa not in layer_stack:
|
|
139
|
-
node_check = False
|
|
140
|
-
break
|
|
141
|
-
if node_check:
|
|
142
|
-
if ch not in layer_stack:
|
|
143
|
-
layer_stack.append(ch)
|
|
144
|
-
temp_stack.append(ch)
|
|
145
|
-
self.layer_stack = layer_stack
|
|
146
|
-
|
|
147
|
-
def create_model_output(self, model):
|
|
148
|
-
#Creates a new model that produces the output of each layer in the neural network.
|
|
149
|
-
self.layers = [[], []]
|
|
150
|
-
for l in self.model_resource[0]:
|
|
151
|
-
# if l not in model_resource[3]:
|
|
152
|
-
self.layers[0].append(l)
|
|
153
|
-
self.layers[1].append(self.model_resource[0][l])
|
|
154
|
-
|
|
155
|
-
self.all_out_model = Model(
|
|
156
|
-
inputs=model.input, outputs=[layer.output for layer in self.layers[1]]
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
def predict(self, inputs):
|
|
160
|
-
#takes input data inputs and performs a forward pass through the neural network model to compute the output of each layer.
|
|
161
|
-
#returns a dictionary that maps layer names to their corresponding outputs.
|
|
162
|
-
all_out = self.all_out_model(inputs, training=False)
|
|
163
|
-
temp_out = {}
|
|
164
|
-
for i, v in enumerate(self.layers[0]):
|
|
165
|
-
temp_out[v] = all_out[i].numpy()
|
|
166
|
-
if self.sequential:
|
|
167
|
-
temp_out[self.layer_stack[-1]] = inputs
|
|
168
|
-
return temp_out
|
|
169
|
-
|
|
170
|
-
def eval(
|
|
171
|
-
self,
|
|
172
|
-
all_out,
|
|
173
|
-
mode,
|
|
174
|
-
start_wt=[],
|
|
175
|
-
multiplier=100.0,
|
|
176
|
-
scaler=0,
|
|
177
|
-
max_unit=0,
|
|
178
|
-
):
|
|
179
|
-
#This method is used for evaluating layer-wise relevance based on different modes.
|
|
180
|
-
if mode == "default":
|
|
181
|
-
output = self.proportional_eval(
|
|
182
|
-
all_out=all_out,
|
|
183
|
-
start_wt=start_wt,
|
|
184
|
-
multiplier=multiplier,
|
|
185
|
-
scaler=0,
|
|
186
|
-
max_unit=0,
|
|
187
|
-
)
|
|
188
|
-
return output
|
|
189
|
-
elif mode == "contrast":
|
|
190
|
-
temp_output = self.contrast_eval(all_out=all_out, multiplier=multiplier)
|
|
191
|
-
output = {}
|
|
192
|
-
for k in temp_output[0].keys():
|
|
193
|
-
output[k] = {}
|
|
194
|
-
output[k]["Positive"] = temp_output[0][k]
|
|
195
|
-
output[k]["Negative"] = temp_output[1][k]
|
|
196
|
-
return output
|
|
197
|
-
|
|
198
|
-
def proportional_eval(
|
|
199
|
-
self, all_out, start_wt=[], multiplier=100.0, scaler=0, max_unit=0
|
|
200
|
-
):
|
|
201
|
-
#This method computes layer-wise relevance in the "default" mode.
|
|
202
|
-
#iteratively calculates relevance for each layer based on the layer's type (e.g., Dense, Conv2D, LSTM) and activation function.
|
|
203
|
-
#returns a dictionary mapping layer names to their relevance scores.
|
|
204
|
-
model_resource = self.model_resource
|
|
205
|
-
activation_dict = self.activation_dict
|
|
206
|
-
inputcheck = False
|
|
207
|
-
out_layer = model_resource[2][0]
|
|
208
|
-
all_wt = {}
|
|
209
|
-
if len(start_wt) == 0:
|
|
210
|
-
start_wt = UP.calculate_start_wt(all_out[out_layer])
|
|
211
|
-
all_wt[out_layer] = start_wt * multiplier
|
|
212
|
-
layer_stack = self.layer_stack
|
|
213
|
-
|
|
214
|
-
for start_layer in layer_stack:
|
|
215
|
-
# print("===========================================")
|
|
216
|
-
# print(layer_stack)
|
|
217
|
-
if model_resource[1][start_layer]["child"]:
|
|
218
|
-
child_nodes = model_resource[1][start_layer]["child"]
|
|
219
|
-
for ch in child_nodes:
|
|
220
|
-
if ch not in all_wt:
|
|
221
|
-
all_wt[ch] = np.zeros_like(all_out[ch][0])
|
|
222
|
-
# print(start_layer, child_nodes,all_out[start_layer].shape)
|
|
223
|
-
if model_resource[1][start_layer]["class"] == "Dense":
|
|
224
|
-
print('dense')
|
|
225
|
-
l1 = model_resource[0][start_layer]
|
|
226
|
-
w1 = l1.weights[0]
|
|
227
|
-
b1 = l1.weights[1]
|
|
228
|
-
temp_wt = UP.calculate_wt_fc(
|
|
229
|
-
all_wt[start_layer],
|
|
230
|
-
all_out[child_nodes[0]][0],
|
|
231
|
-
w1,
|
|
232
|
-
b1,
|
|
233
|
-
activation_dict[model_resource[1][start_layer]["name"]],
|
|
234
|
-
)
|
|
235
|
-
all_wt[child_nodes[0]] += temp_wt
|
|
236
|
-
|
|
237
|
-
elif model_resource[1][start_layer]["class"] == "Conv2D":
|
|
238
|
-
l1 = model_resource[0][start_layer]
|
|
239
|
-
w1 = l1.weights[0]
|
|
240
|
-
b1 = l1.weights[1]
|
|
241
|
-
temp_wt = UP.calculate_wt_conv(
|
|
242
|
-
all_wt[start_layer],
|
|
243
|
-
all_out[child_nodes[0]][0],
|
|
244
|
-
w1,
|
|
245
|
-
b1,
|
|
246
|
-
activation_dict[model_resource[1][start_layer]["name"]],
|
|
247
|
-
)
|
|
248
|
-
all_wt[child_nodes[0]] += temp_wt
|
|
249
|
-
elif model_resource[1][start_layer]["class"] == "Reshape":
|
|
250
|
-
temp_wt = UP.calculate_wt_rshp(
|
|
251
|
-
all_wt[start_layer], all_out[child_nodes[0]][0]
|
|
252
|
-
)
|
|
253
|
-
all_wt[child_nodes[0]] += temp_wt
|
|
254
|
-
elif model_resource[1][start_layer]["class"] == "Flatten":
|
|
255
|
-
temp_wt = UP.calculate_wt_rshp(
|
|
256
|
-
all_wt[start_layer], all_out[child_nodes[0]][0]
|
|
257
|
-
)
|
|
258
|
-
all_wt[child_nodes[0]] += temp_wt
|
|
259
|
-
elif (
|
|
260
|
-
model_resource[1][start_layer]["class"] == "GlobalAveragePooling2D"
|
|
261
|
-
):
|
|
262
|
-
temp_wt = UP.calculate_wt_gavgpool(
|
|
263
|
-
all_wt[start_layer], all_out[child_nodes[0]][0]
|
|
264
|
-
)
|
|
265
|
-
all_wt[child_nodes[0]] += temp_wt
|
|
266
|
-
elif model_resource[1][start_layer]["class"] == "MaxPooling2D":
|
|
267
|
-
l1 = model_resource[0][start_layer]
|
|
268
|
-
temp_wt = UP.calculate_wt_maxpool(
|
|
269
|
-
all_wt[start_layer], all_out[child_nodes[0]][0], l1.pool_size
|
|
270
|
-
)
|
|
271
|
-
all_wt[child_nodes[0]] += temp_wt
|
|
272
|
-
elif model_resource[1][start_layer]["class"] == "AveragePooling2D":
|
|
273
|
-
l1 = model_resource[0][start_layer]
|
|
274
|
-
temp_wt = UP.calculate_wt_avgpool(
|
|
275
|
-
all_wt[start_layer], all_out[child_nodes[0]][0], l1.pool_size
|
|
276
|
-
)
|
|
277
|
-
all_wt[child_nodes[0]] += temp_wt
|
|
278
|
-
elif model_resource[1][start_layer]["class"] == "Concatenate":
|
|
279
|
-
temp_wt = UP.calculate_wt_concat(
|
|
280
|
-
all_wt[start_layer],
|
|
281
|
-
[all_out[ch] for ch in child_nodes],
|
|
282
|
-
model_resource[0][start_layer].axis,
|
|
283
|
-
)
|
|
284
|
-
for ind, ch in enumerate(child_nodes):
|
|
285
|
-
all_wt[ch] += temp_wt[ind]
|
|
286
|
-
elif model_resource[1][start_layer]["class"] == "Add":
|
|
287
|
-
temp_wt = UP.calculate_wt_add(
|
|
288
|
-
all_wt[start_layer], [all_out[ch] for ch in child_nodes]
|
|
289
|
-
)
|
|
290
|
-
for ind, ch in enumerate(child_nodes):
|
|
291
|
-
all_wt[ch] += temp_wt[ind]
|
|
292
|
-
elif model_resource[1][start_layer]["class"] == "LSTM":
|
|
293
|
-
print('lstm')
|
|
294
|
-
l1 = model_resource[0][start_layer]
|
|
295
|
-
return_sequence = l1.return_sequences
|
|
296
|
-
units = l1.units
|
|
297
|
-
num_of_cells = l1.input_shape[1]
|
|
298
|
-
lstm_obj_f = UP.LSTM_forward(
|
|
299
|
-
num_of_cells, units, l1.weights, return_sequence, False
|
|
300
|
-
)
|
|
301
|
-
lstm_obj_b = UP.LSTM_backtrace(
|
|
302
|
-
num_of_cells,
|
|
303
|
-
units,
|
|
304
|
-
[i.numpy() for i in l1.weights],
|
|
305
|
-
return_sequence,
|
|
306
|
-
False,
|
|
307
|
-
)
|
|
308
|
-
temp_out_f = lstm_obj_f.calculate_lstm_wt(
|
|
309
|
-
all_out[child_nodes[0]][0]
|
|
310
|
-
)
|
|
311
|
-
temp_wt = lstm_obj_b.calculate_lstm_wt(
|
|
312
|
-
all_wt[start_layer], lstm_obj_f.compute_log
|
|
313
|
-
)
|
|
314
|
-
all_wt[child_nodes[0]] += temp_wt
|
|
315
|
-
|
|
316
|
-
elif model_resource[1][start_layer]["class"] == "Embedding":
|
|
317
|
-
print('embedding')
|
|
318
|
-
temp_wt = all_wt[start_layer]
|
|
319
|
-
temp_wt = np.mean(temp_wt,axis=1)
|
|
320
|
-
|
|
321
|
-
all_wt[child_nodes[0]] = all_wt[child_nodes[0]] + temp_wt
|
|
322
|
-
|
|
323
|
-
else:
|
|
324
|
-
print('else')
|
|
325
|
-
temp_wt = all_wt[start_layer]
|
|
326
|
-
all_wt[child_nodes[0]] += temp_wt.astype('float64')
|
|
327
|
-
|
|
328
|
-
if max_unit > 0 and scaler == 0:
|
|
329
|
-
temp_dict = {}
|
|
330
|
-
for k in all_wt.keys():
|
|
331
|
-
temp_dict[k] = UC.weight_normalize(all_wt[k], max_val=max_unit)
|
|
332
|
-
all_wt = temp_dict
|
|
333
|
-
elif scaler > 0:
|
|
334
|
-
temp_dict = {}
|
|
335
|
-
for k in all_wt.keys():
|
|
336
|
-
temp_dict[k] = UC.weight_scaler(all_wt[k], scaler=scaler)
|
|
337
|
-
all_wt = temp_dict
|
|
338
|
-
return all_wt
|
|
339
|
-
|
|
340
|
-
def contrast_eval(self, all_out, multiplier=100.0):
|
|
341
|
-
#This method computes layer-wise relevance in the "contrast" mode.
|
|
342
|
-
#calculates positive and negative relevance scores for each layer
|
|
343
|
-
#returns a dictionary that maps layer names to dictionaries containing positive and negative relevance values.
|
|
344
|
-
model_resource = self.model_resource
|
|
345
|
-
print(model_resource)
|
|
346
|
-
activation_dict = self.activation_dict
|
|
347
|
-
inputcheck = False
|
|
348
|
-
out_layer = model_resource[2][0]
|
|
349
|
-
all_wt_pos = {}
|
|
350
|
-
all_wt_neg = {}
|
|
351
|
-
start_wt_pos, start_wt_neg = UC.calculate_start_wt(all_out[out_layer])
|
|
352
|
-
all_wt_pos[out_layer] = start_wt_pos * multiplier
|
|
353
|
-
all_wt_neg[out_layer] = start_wt_neg * multiplier
|
|
354
|
-
layer_stack = [out_layer]
|
|
355
|
-
while len(layer_stack) > 0:
|
|
356
|
-
start_layer = layer_stack.pop(0)
|
|
357
|
-
if model_resource[1][start_layer]["child"]:
|
|
358
|
-
child_nodes = model_resource[1][start_layer]["child"]
|
|
359
|
-
for ch in child_nodes:
|
|
360
|
-
if ch not in all_wt_pos:
|
|
361
|
-
all_wt_pos[ch] = np.zeros_like(all_out[ch][0])
|
|
362
|
-
all_wt_neg[ch] = np.zeros_like(all_out[ch][0])
|
|
363
|
-
if model_resource[1][start_layer]["class"] == "Dense":
|
|
364
|
-
print('dense')
|
|
365
|
-
l1 = model_resource[0][start_layer]
|
|
366
|
-
w1 = l1.weights[0]
|
|
367
|
-
b1 = l1.weights[1]
|
|
368
|
-
temp_wt_pos, temp_wt_neg = UC.calculate_wt_fc(
|
|
369
|
-
all_wt_pos[start_layer],
|
|
370
|
-
all_wt_neg[start_layer],
|
|
371
|
-
all_out[child_nodes[0]][0],
|
|
372
|
-
w1,
|
|
373
|
-
b1,
|
|
374
|
-
activation_dict[model_resource[1][start_layer]["name"]],
|
|
375
|
-
)
|
|
376
|
-
all_wt_pos[child_nodes[0]] += temp_wt_pos
|
|
377
|
-
all_wt_neg[child_nodes[0]] += temp_wt_neg
|
|
378
|
-
elif model_resource[1][start_layer]["class"] == "Conv2D":
|
|
379
|
-
l1 = model_resource[0][start_layer]
|
|
380
|
-
w1 = l1.weights[0]
|
|
381
|
-
b1 = l1.weights[1]
|
|
382
|
-
temp_wt_pos, temp_wt_neg = UC.calculate_wt_conv(
|
|
383
|
-
all_wt_pos[start_layer],
|
|
384
|
-
all_wt_neg[start_layer],
|
|
385
|
-
all_out[child_nodes[0]][0],
|
|
386
|
-
w1,
|
|
387
|
-
b1,
|
|
388
|
-
activation_dict[model_resource[1][start_layer]["name"]],
|
|
389
|
-
)
|
|
390
|
-
all_wt_pos[child_nodes[0]] += temp_wt_pos
|
|
391
|
-
all_wt_neg[child_nodes[0]] += temp_wt_neg
|
|
392
|
-
elif model_resource[1][start_layer]["class"] == "Reshape":
|
|
393
|
-
temp_wt_pos = UC.calculate_wt_rshp(
|
|
394
|
-
all_wt_pos[start_layer], all_out[child_nodes[0]][0]
|
|
395
|
-
)
|
|
396
|
-
temp_wt_neg = UC.calculate_wt_rshp(
|
|
397
|
-
all_wt_neg[start_layer], all_out[child_nodes[0]][0]
|
|
398
|
-
)
|
|
399
|
-
all_wt_pos[child_nodes[0]] += temp_wt_pos
|
|
400
|
-
all_wt_neg[child_nodes[0]] += temp_wt_neg
|
|
401
|
-
elif (
|
|
402
|
-
model_resource[1][start_layer]["class"] == "GlobalAveragePooling2D"
|
|
403
|
-
):
|
|
404
|
-
temp_wt_pos, temp_wt_neg = UC.calculate_wt_gavgpool(
|
|
405
|
-
all_wt_pos[start_layer],
|
|
406
|
-
all_wt_neg[start_layer],
|
|
407
|
-
all_out[child_nodes[0]][0],
|
|
408
|
-
)
|
|
409
|
-
all_wt_pos[child_nodes[0]] += temp_wt_pos
|
|
410
|
-
all_wt_neg[child_nodes[0]] += temp_wt_neg
|
|
411
|
-
elif model_resource[1][start_layer]["class"] == "Flatten":
|
|
412
|
-
temp_wt = UC.calculate_wt_rshp(
|
|
413
|
-
all_wt_pos[start_layer], all_out[child_nodes[0]][0]
|
|
414
|
-
)
|
|
415
|
-
all_wt_pos[child_nodes[0]] += temp_wt
|
|
416
|
-
temp_wt = UC.calculate_wt_rshp(
|
|
417
|
-
all_wt_neg[start_layer], all_out[child_nodes[0]][0]
|
|
418
|
-
)
|
|
419
|
-
all_wt_neg[child_nodes[0]] += temp_wt
|
|
420
|
-
elif (
|
|
421
|
-
model_resource[1][start_layer]["class"] == "GlobalAveragePooling2D"
|
|
422
|
-
):
|
|
423
|
-
temp_wt_pos, temp_wt_neg = UC.calculate_wt_gavgpool(
|
|
424
|
-
all_wt_pos[start_layer],
|
|
425
|
-
all_wt_neg[start_layer],
|
|
426
|
-
all_out[child_nodes[0]][0],
|
|
427
|
-
)
|
|
428
|
-
all_wt_pos[child_nodes[0]] += temp_wt_pos
|
|
429
|
-
all_wt_neg[child_nodes[0]] += temp_wt_neg
|
|
430
|
-
elif model_resource[1][start_layer]["class"] == "MaxPooling2D":
|
|
431
|
-
l1 = model_resource[0][start_layer]
|
|
432
|
-
temp_wt = UC.calculate_wt_maxpool(
|
|
433
|
-
all_wt_pos[start_layer],
|
|
434
|
-
all_out[child_nodes[0]][0],
|
|
435
|
-
l1.pool_size,
|
|
436
|
-
)
|
|
437
|
-
all_wt_pos[child_nodes[0]] += temp_wt
|
|
438
|
-
temp_wt = UC.calculate_wt_maxpool(
|
|
439
|
-
all_wt_neg[start_layer],
|
|
440
|
-
all_out[child_nodes[0]][0],
|
|
441
|
-
l1.pool_size,
|
|
442
|
-
)
|
|
443
|
-
all_wt_neg[child_nodes[0]] += temp_wt
|
|
444
|
-
elif model_resource[1][start_layer]["class"] == "AveragePooling2D":
|
|
445
|
-
l1 = model_resource[0][start_layer]
|
|
446
|
-
temp_wt_pos, temp_wt_neg = UC.calculate_wt_avgpool(
|
|
447
|
-
all_wt_pos[start_layer],
|
|
448
|
-
all_wt_neg[start_layer],
|
|
449
|
-
all_out[child_nodes[0]][0],
|
|
450
|
-
l1.pool_size,
|
|
451
|
-
)
|
|
452
|
-
all_wt_pos[child_nodes[0]] += temp_wt_pos
|
|
453
|
-
all_wt_neg[child_nodes[0]] += temp_wt_neg
|
|
454
|
-
elif model_resource[1][start_layer]["class"] == "Concatenate":
|
|
455
|
-
temp_wt = UC.calculate_wt_concat(
|
|
456
|
-
all_wt_pos[start_layer],
|
|
457
|
-
[all_out[ch] for ch in child_nodes],
|
|
458
|
-
model_resource[0][start_layer].axis,
|
|
459
|
-
)
|
|
460
|
-
for ind, ch in enumerate(child_nodes):
|
|
461
|
-
all_wt_pos[ch] += temp_wt[ind]
|
|
462
|
-
temp_wt = UC.calculate_wt_concat(
|
|
463
|
-
all_wt_neg[start_layer],
|
|
464
|
-
[all_out[ch] for ch in child_nodes],
|
|
465
|
-
model_resource[0][start_layer].axis,
|
|
466
|
-
)
|
|
467
|
-
for ind, ch in enumerate(child_nodes):
|
|
468
|
-
all_wt_neg[ch] += temp_wt[ind]
|
|
469
|
-
|
|
470
|
-
elif model_resource[1][start_layer]["class"] == "Add":
|
|
471
|
-
temp_wt = UC.calculate_wt_add(
|
|
472
|
-
all_wt_pos[start_layer],
|
|
473
|
-
all_wt_neg[start_layer],
|
|
474
|
-
[all_out[ch] for ch in child_nodes],
|
|
475
|
-
)
|
|
476
|
-
for ind, ch in enumerate(child_nodes):
|
|
477
|
-
all_wt_pos[ch] += temp_wt[ind][0]
|
|
478
|
-
all_wt_neg[ch] += temp_wt[ind][1]
|
|
479
|
-
elif model_resource[1][start_layer]["class"] == "LSTM":
|
|
480
|
-
print('lstm')
|
|
481
|
-
l1 = model_resource[0][start_layer]
|
|
482
|
-
return_sequence = l1.return_sequences
|
|
483
|
-
units = l1.units
|
|
484
|
-
num_of_cells = l1.input_shape[1]
|
|
485
|
-
lstm_obj_f = UC.LSTM_forward(
|
|
486
|
-
num_of_cells, units, l1.weights, return_sequence, False
|
|
487
|
-
)
|
|
488
|
-
lstm_obj_b = UC.LSTM_backtrace(
|
|
489
|
-
num_of_cells,
|
|
490
|
-
units,
|
|
491
|
-
[i.numpy() for i in l1.weights],
|
|
492
|
-
return_sequence,
|
|
493
|
-
False,
|
|
494
|
-
)
|
|
495
|
-
temp_out_f = lstm_obj_f.calculate_lstm_wt(
|
|
496
|
-
all_out[child_nodes[0]][0]
|
|
497
|
-
)
|
|
498
|
-
temp_wt_pos, temp_wt_neg = lstm_obj_b.calculate_lstm_wt(
|
|
499
|
-
all_wt_pos[start_layer],
|
|
500
|
-
all_wt_neg[start_layer],
|
|
501
|
-
lstm_obj_f.compute_log,
|
|
502
|
-
)
|
|
503
|
-
all_wt_pos[child_nodes[0]] = temp_wt_pos
|
|
504
|
-
all_wt_neg[child_nodes[0]] = temp_wt_neg
|
|
505
|
-
|
|
506
|
-
elif model_resource[1][start_layer]["class"] == "Embedding":
|
|
507
|
-
print('embedding layer')
|
|
508
|
-
temp_wt_pos = all_wt_pos[start_layer]
|
|
509
|
-
temp_wt_neg = all_wt_neg[start_layer]
|
|
510
|
-
|
|
511
|
-
temp_wt_pos = np.mean(temp_wt_pos,axis=1)
|
|
512
|
-
temp_wt_neg = np.mean(temp_wt_neg,axis=1)
|
|
513
|
-
|
|
514
|
-
all_wt_pos[child_nodes[0]] = all_wt_pos[child_nodes[0]] + temp_wt_pos
|
|
515
|
-
all_wt_neg[child_nodes[0]] = all_wt_neg[child_nodes[0]] + temp_wt_neg
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
else:
|
|
519
|
-
print('else')
|
|
520
|
-
temp_wt_pos = all_wt_pos[start_layer]
|
|
521
|
-
temp_wt_neg = all_wt_neg[start_layer]
|
|
522
|
-
all_wt_pos[child_nodes[0]] += temp_wt_pos
|
|
523
|
-
all_wt_neg[child_nodes[0]] += temp_wt_neg
|
|
524
|
-
for ch in child_nodes:
|
|
525
|
-
if not (ch in layer_stack):
|
|
526
|
-
layer_stack.append(ch)
|
|
527
|
-
return all_wt_pos, all_wt_neg
|
|
1
|
+
import numpy as np
|
|
2
|
+
from tensorflow.keras import Model
|
|
3
|
+
|
|
4
|
+
from dl_backtrace.tf_backtrace.backtrace.config import activation_master
|
|
5
|
+
from dl_backtrace.tf_backtrace.backtrace.utils import contrast as UC
|
|
6
|
+
from dl_backtrace.tf_backtrace.backtrace.utils import prop as UP
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Backtrace(object):
|
|
10
|
+
"""
|
|
11
|
+
This is the constructor method for the Backtrace class. It initializes an instance of the class.
|
|
12
|
+
It takes two optional parameters: model (a neural network model) and activation_dict (a dictionary that maps layer names to activation functions).
|
|
13
|
+
"""
|
|
14
|
+
def __init__(self, model=None, activation_dict={}):
|
|
15
|
+
|
|
16
|
+
#create a tree-like structure that represents the layers of the neural network model
|
|
17
|
+
self.create_tree(model.layers)
|
|
18
|
+
|
|
19
|
+
#create a new model (an instance of tf.keras.Model) that produces the output of each layer in the neural network.
|
|
20
|
+
self.create_model_output(model)
|
|
21
|
+
|
|
22
|
+
#create a layer stack that defines the order in which layers should be processed during backpropagation.
|
|
23
|
+
self.create_layer_stack()
|
|
24
|
+
|
|
25
|
+
#checks if the model is sequential or not. If it's sequential, it adds the input layer to the layer stack.
|
|
26
|
+
if (
|
|
27
|
+
len(self.model_resource[3]) == 0
|
|
28
|
+
or model.__module__.split(".")[-1] == "sequential"
|
|
29
|
+
):
|
|
30
|
+
inp_name = model.input.name
|
|
31
|
+
self.layer_stack.append(inp_name)
|
|
32
|
+
self.model_resource[1][inp_name] = {}
|
|
33
|
+
self.model_resource[1][inp_name]["name"] = inp_name
|
|
34
|
+
self.model_resource[1][inp_name]["type"] = "input"
|
|
35
|
+
self.model_resource[1][inp_name]["parent"] = []
|
|
36
|
+
self.model_resource[1][inp_name]["child"] = None
|
|
37
|
+
self.model_resource[3].append(inp_name)
|
|
38
|
+
self.sequential = True
|
|
39
|
+
else:
|
|
40
|
+
self.sequential = False
|
|
41
|
+
try:
|
|
42
|
+
|
|
43
|
+
#calls the build_activation_dict method to build a dictionary that maps layer names to activation functions.
|
|
44
|
+
#If that fails, it creates a temporary dictionary with default activation functions.
|
|
45
|
+
if len(activation_dict) == 0:
|
|
46
|
+
self.build_activation_dict(model)
|
|
47
|
+
else:
|
|
48
|
+
self.activation_dict = activation_dict
|
|
49
|
+
|
|
50
|
+
except Exception as e:
|
|
51
|
+
print(e)
|
|
52
|
+
temp_dict = {}
|
|
53
|
+
for l in model.layers:
|
|
54
|
+
temp_dict[l.name] = activation_master["None"]
|
|
55
|
+
self.activation_dict = temp_dict
|
|
56
|
+
|
|
57
|
+
def build_activation_dict(self, model):
|
|
58
|
+
# Builds an activation dictionary by inspecting the activation functions of the layers in the model.
|
|
59
|
+
activation_dict = {}
|
|
60
|
+
for l in model.layers:
|
|
61
|
+
if not hasattr(l, "activation"):
|
|
62
|
+
activation_dict[l.name] = activation_master["None"]
|
|
63
|
+
continue
|
|
64
|
+
a1 = l.activation
|
|
65
|
+
func_name = str(a1).split(" ")
|
|
66
|
+
if func_name[0] == "<function":
|
|
67
|
+
activation_dict[l.name] = activation_master.get(
|
|
68
|
+
func_name[1], activation_master["None"]
|
|
69
|
+
)
|
|
70
|
+
else:
|
|
71
|
+
a2 = a1.activation
|
|
72
|
+
func_name = str(a2).split(" ")
|
|
73
|
+
if func_name[0] == "<function":
|
|
74
|
+
activation_dict[l.name] = activation_master.get(
|
|
75
|
+
func_name[1], activation_master["None"]
|
|
76
|
+
)
|
|
77
|
+
self.activation_dict = activation_dict
|
|
78
|
+
|
|
79
|
+
def create_tree(self, layers):
|
|
80
|
+
#Creates a tree structure representing the layers of the model.
|
|
81
|
+
#categorizes layers as input, output, or intermediate layers and establishes parent-child relationships between layers.
|
|
82
|
+
ltree = {}
|
|
83
|
+
layer_tree = {}
|
|
84
|
+
inputs = []
|
|
85
|
+
outputs = []
|
|
86
|
+
intermediates = []
|
|
87
|
+
for l in layers:
|
|
88
|
+
ltree[l.output.name] = {}
|
|
89
|
+
layer_tree[l.output.name] = l
|
|
90
|
+
ltree[l.output.name]["name"] = l.name.split("/")[0]
|
|
91
|
+
ltree[l.output.name]["class"] = type(l).__name__
|
|
92
|
+
if not isinstance(l.input, list):
|
|
93
|
+
if l.input.name == l.output.name:
|
|
94
|
+
ltree[l.output.name]["type"] = "input"
|
|
95
|
+
ltree[l.output.name]["parent"] = []
|
|
96
|
+
ltree[l.output.name]["child"] = None
|
|
97
|
+
inputs.append(l.output.name)
|
|
98
|
+
else:
|
|
99
|
+
ltree[l.output.name]["type"] = "output"
|
|
100
|
+
ltree[l.output.name]["parent"] = []
|
|
101
|
+
ltree[l.output.name]["child"] = [l.input.name]
|
|
102
|
+
outputs.append(l.output.name)
|
|
103
|
+
if l.input.name in ltree:
|
|
104
|
+
if ltree[l.input.name]["type"] != "input":
|
|
105
|
+
ltree[l.input.name]["type"] = "intermediate"
|
|
106
|
+
intermediates.append(l.input.name)
|
|
107
|
+
if l.input.name != l.output.name:
|
|
108
|
+
ltree[l.input.name]["parent"].append(l.output.name)
|
|
109
|
+
else:
|
|
110
|
+
ltree[l.output.name]["type"] = "output"
|
|
111
|
+
ltree[l.output.name]["child"] = [i.name for i in l.input]
|
|
112
|
+
ltree[l.output.name]["parent"] = []
|
|
113
|
+
outputs.append(l.output.name)
|
|
114
|
+
for i in l.input:
|
|
115
|
+
if i.name in ltree:
|
|
116
|
+
if i.name != l.output.name:
|
|
117
|
+
ltree[i.name]["parent"].append(l.output.name)
|
|
118
|
+
if ltree[i.name]["type"] != "input":
|
|
119
|
+
ltree[i.name]["type"] = "intermediate"
|
|
120
|
+
intermediates.append(i.name)
|
|
121
|
+
|
|
122
|
+
outputs = list(set(outputs) - set(intermediates))
|
|
123
|
+
self.model_resource = (layer_tree, ltree, outputs, inputs)
|
|
124
|
+
|
|
125
|
+
def create_layer_stack(self):
|
|
126
|
+
#Creates a layer stack that defines the order in which layers should be processed during backpropagation.
|
|
127
|
+
model_resource = self.model_resource
|
|
128
|
+
start_layer = model_resource[2][0]
|
|
129
|
+
layer_stack = [start_layer]
|
|
130
|
+
temp_stack = [start_layer]
|
|
131
|
+
while len(layer_stack) < len(model_resource[0]):
|
|
132
|
+
start_layer = temp_stack.pop(0)
|
|
133
|
+
if model_resource[1][start_layer]["child"]:
|
|
134
|
+
child_nodes = model_resource[1][start_layer]["child"]
|
|
135
|
+
for ch in child_nodes:
|
|
136
|
+
node_check = True
|
|
137
|
+
for pa in model_resource[1][ch]["parent"]:
|
|
138
|
+
if pa not in layer_stack:
|
|
139
|
+
node_check = False
|
|
140
|
+
break
|
|
141
|
+
if node_check:
|
|
142
|
+
if ch not in layer_stack:
|
|
143
|
+
layer_stack.append(ch)
|
|
144
|
+
temp_stack.append(ch)
|
|
145
|
+
self.layer_stack = layer_stack
|
|
146
|
+
|
|
147
|
+
def create_model_output(self, model):
|
|
148
|
+
#Creates a new model that produces the output of each layer in the neural network.
|
|
149
|
+
self.layers = [[], []]
|
|
150
|
+
for l in self.model_resource[0]:
|
|
151
|
+
# if l not in model_resource[3]:
|
|
152
|
+
self.layers[0].append(l)
|
|
153
|
+
self.layers[1].append(self.model_resource[0][l])
|
|
154
|
+
|
|
155
|
+
self.all_out_model = Model(
|
|
156
|
+
inputs=model.input, outputs=[layer.output for layer in self.layers[1]]
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
def predict(self, inputs):
|
|
160
|
+
#takes input data inputs and performs a forward pass through the neural network model to compute the output of each layer.
|
|
161
|
+
#returns a dictionary that maps layer names to their corresponding outputs.
|
|
162
|
+
all_out = self.all_out_model(inputs, training=False)
|
|
163
|
+
temp_out = {}
|
|
164
|
+
for i, v in enumerate(self.layers[0]):
|
|
165
|
+
temp_out[v] = all_out[i].numpy()
|
|
166
|
+
if self.sequential:
|
|
167
|
+
temp_out[self.layer_stack[-1]] = inputs
|
|
168
|
+
return temp_out
|
|
169
|
+
|
|
170
|
+
def eval(
|
|
171
|
+
self,
|
|
172
|
+
all_out,
|
|
173
|
+
mode,
|
|
174
|
+
start_wt=[],
|
|
175
|
+
multiplier=100.0,
|
|
176
|
+
scaler=0,
|
|
177
|
+
max_unit=0,
|
|
178
|
+
):
|
|
179
|
+
#This method is used for evaluating layer-wise relevance based on different modes.
|
|
180
|
+
if mode == "default":
|
|
181
|
+
output = self.proportional_eval(
|
|
182
|
+
all_out=all_out,
|
|
183
|
+
start_wt=start_wt,
|
|
184
|
+
multiplier=multiplier,
|
|
185
|
+
scaler=0,
|
|
186
|
+
max_unit=0,
|
|
187
|
+
)
|
|
188
|
+
return output
|
|
189
|
+
elif mode == "contrast":
|
|
190
|
+
temp_output = self.contrast_eval(all_out=all_out, multiplier=multiplier)
|
|
191
|
+
output = {}
|
|
192
|
+
for k in temp_output[0].keys():
|
|
193
|
+
output[k] = {}
|
|
194
|
+
output[k]["Positive"] = temp_output[0][k]
|
|
195
|
+
output[k]["Negative"] = temp_output[1][k]
|
|
196
|
+
return output
|
|
197
|
+
|
|
198
|
+
def proportional_eval(
|
|
199
|
+
self, all_out, start_wt=[], multiplier=100.0, scaler=0, max_unit=0
|
|
200
|
+
):
|
|
201
|
+
#This method computes layer-wise relevance in the "default" mode.
|
|
202
|
+
#iteratively calculates relevance for each layer based on the layer's type (e.g., Dense, Conv2D, LSTM) and activation function.
|
|
203
|
+
#returns a dictionary mapping layer names to their relevance scores.
|
|
204
|
+
model_resource = self.model_resource
|
|
205
|
+
activation_dict = self.activation_dict
|
|
206
|
+
inputcheck = False
|
|
207
|
+
out_layer = model_resource[2][0]
|
|
208
|
+
all_wt = {}
|
|
209
|
+
if len(start_wt) == 0:
|
|
210
|
+
start_wt = UP.calculate_start_wt(all_out[out_layer])
|
|
211
|
+
all_wt[out_layer] = start_wt * multiplier
|
|
212
|
+
layer_stack = self.layer_stack
|
|
213
|
+
|
|
214
|
+
for start_layer in layer_stack:
|
|
215
|
+
# print("===========================================")
|
|
216
|
+
# print(layer_stack)
|
|
217
|
+
if model_resource[1][start_layer]["child"]:
|
|
218
|
+
child_nodes = model_resource[1][start_layer]["child"]
|
|
219
|
+
for ch in child_nodes:
|
|
220
|
+
if ch not in all_wt:
|
|
221
|
+
all_wt[ch] = np.zeros_like(all_out[ch][0])
|
|
222
|
+
# print(start_layer, child_nodes,all_out[start_layer].shape)
|
|
223
|
+
if model_resource[1][start_layer]["class"] == "Dense":
|
|
224
|
+
print('dense')
|
|
225
|
+
l1 = model_resource[0][start_layer]
|
|
226
|
+
w1 = l1.weights[0]
|
|
227
|
+
b1 = l1.weights[1]
|
|
228
|
+
temp_wt = UP.calculate_wt_fc(
|
|
229
|
+
all_wt[start_layer],
|
|
230
|
+
all_out[child_nodes[0]][0],
|
|
231
|
+
w1,
|
|
232
|
+
b1,
|
|
233
|
+
activation_dict[model_resource[1][start_layer]["name"]],
|
|
234
|
+
)
|
|
235
|
+
all_wt[child_nodes[0]] += temp_wt
|
|
236
|
+
|
|
237
|
+
elif model_resource[1][start_layer]["class"] == "Conv2D":
|
|
238
|
+
l1 = model_resource[0][start_layer]
|
|
239
|
+
w1 = l1.weights[0]
|
|
240
|
+
b1 = l1.weights[1]
|
|
241
|
+
temp_wt = UP.calculate_wt_conv(
|
|
242
|
+
all_wt[start_layer],
|
|
243
|
+
all_out[child_nodes[0]][0],
|
|
244
|
+
w1,
|
|
245
|
+
b1,
|
|
246
|
+
activation_dict[model_resource[1][start_layer]["name"]],
|
|
247
|
+
)
|
|
248
|
+
all_wt[child_nodes[0]] += temp_wt
|
|
249
|
+
elif model_resource[1][start_layer]["class"] == "Reshape":
|
|
250
|
+
temp_wt = UP.calculate_wt_rshp(
|
|
251
|
+
all_wt[start_layer], all_out[child_nodes[0]][0]
|
|
252
|
+
)
|
|
253
|
+
all_wt[child_nodes[0]] += temp_wt
|
|
254
|
+
elif model_resource[1][start_layer]["class"] == "Flatten":
|
|
255
|
+
temp_wt = UP.calculate_wt_rshp(
|
|
256
|
+
all_wt[start_layer], all_out[child_nodes[0]][0]
|
|
257
|
+
)
|
|
258
|
+
all_wt[child_nodes[0]] += temp_wt
|
|
259
|
+
elif (
|
|
260
|
+
model_resource[1][start_layer]["class"] == "GlobalAveragePooling2D"
|
|
261
|
+
):
|
|
262
|
+
temp_wt = UP.calculate_wt_gavgpool(
|
|
263
|
+
all_wt[start_layer], all_out[child_nodes[0]][0]
|
|
264
|
+
)
|
|
265
|
+
all_wt[child_nodes[0]] += temp_wt
|
|
266
|
+
elif model_resource[1][start_layer]["class"] == "MaxPooling2D":
|
|
267
|
+
l1 = model_resource[0][start_layer]
|
|
268
|
+
temp_wt = UP.calculate_wt_maxpool(
|
|
269
|
+
all_wt[start_layer], all_out[child_nodes[0]][0], l1.pool_size
|
|
270
|
+
)
|
|
271
|
+
all_wt[child_nodes[0]] += temp_wt
|
|
272
|
+
elif model_resource[1][start_layer]["class"] == "AveragePooling2D":
|
|
273
|
+
l1 = model_resource[0][start_layer]
|
|
274
|
+
temp_wt = UP.calculate_wt_avgpool(
|
|
275
|
+
all_wt[start_layer], all_out[child_nodes[0]][0], l1.pool_size
|
|
276
|
+
)
|
|
277
|
+
all_wt[child_nodes[0]] += temp_wt
|
|
278
|
+
elif model_resource[1][start_layer]["class"] == "Concatenate":
|
|
279
|
+
temp_wt = UP.calculate_wt_concat(
|
|
280
|
+
all_wt[start_layer],
|
|
281
|
+
[all_out[ch] for ch in child_nodes],
|
|
282
|
+
model_resource[0][start_layer].axis,
|
|
283
|
+
)
|
|
284
|
+
for ind, ch in enumerate(child_nodes):
|
|
285
|
+
all_wt[ch] += temp_wt[ind]
|
|
286
|
+
elif model_resource[1][start_layer]["class"] == "Add":
|
|
287
|
+
temp_wt = UP.calculate_wt_add(
|
|
288
|
+
all_wt[start_layer], [all_out[ch] for ch in child_nodes]
|
|
289
|
+
)
|
|
290
|
+
for ind, ch in enumerate(child_nodes):
|
|
291
|
+
all_wt[ch] += temp_wt[ind]
|
|
292
|
+
elif model_resource[1][start_layer]["class"] == "LSTM":
|
|
293
|
+
print('lstm')
|
|
294
|
+
l1 = model_resource[0][start_layer]
|
|
295
|
+
return_sequence = l1.return_sequences
|
|
296
|
+
units = l1.units
|
|
297
|
+
num_of_cells = l1.input_shape[1]
|
|
298
|
+
lstm_obj_f = UP.LSTM_forward(
|
|
299
|
+
num_of_cells, units, l1.weights, return_sequence, False
|
|
300
|
+
)
|
|
301
|
+
lstm_obj_b = UP.LSTM_backtrace(
|
|
302
|
+
num_of_cells,
|
|
303
|
+
units,
|
|
304
|
+
[i.numpy() for i in l1.weights],
|
|
305
|
+
return_sequence,
|
|
306
|
+
False,
|
|
307
|
+
)
|
|
308
|
+
temp_out_f = lstm_obj_f.calculate_lstm_wt(
|
|
309
|
+
all_out[child_nodes[0]][0]
|
|
310
|
+
)
|
|
311
|
+
temp_wt = lstm_obj_b.calculate_lstm_wt(
|
|
312
|
+
all_wt[start_layer], lstm_obj_f.compute_log
|
|
313
|
+
)
|
|
314
|
+
all_wt[child_nodes[0]] += temp_wt
|
|
315
|
+
|
|
316
|
+
elif model_resource[1][start_layer]["class"] == "Embedding":
|
|
317
|
+
print('embedding')
|
|
318
|
+
temp_wt = all_wt[start_layer]
|
|
319
|
+
temp_wt = np.mean(temp_wt,axis=1)
|
|
320
|
+
|
|
321
|
+
all_wt[child_nodes[0]] = all_wt[child_nodes[0]] + temp_wt
|
|
322
|
+
|
|
323
|
+
else:
|
|
324
|
+
print('else')
|
|
325
|
+
temp_wt = all_wt[start_layer]
|
|
326
|
+
all_wt[child_nodes[0]] += temp_wt.astype('float64')
|
|
327
|
+
|
|
328
|
+
if max_unit > 0 and scaler == 0:
|
|
329
|
+
temp_dict = {}
|
|
330
|
+
for k in all_wt.keys():
|
|
331
|
+
temp_dict[k] = UC.weight_normalize(all_wt[k], max_val=max_unit)
|
|
332
|
+
all_wt = temp_dict
|
|
333
|
+
elif scaler > 0:
|
|
334
|
+
temp_dict = {}
|
|
335
|
+
for k in all_wt.keys():
|
|
336
|
+
temp_dict[k] = UC.weight_scaler(all_wt[k], scaler=scaler)
|
|
337
|
+
all_wt = temp_dict
|
|
338
|
+
return all_wt
|
|
339
|
+
|
|
340
|
+
def contrast_eval(self, all_out, multiplier=100.0):
|
|
341
|
+
#This method computes layer-wise relevance in the "contrast" mode.
|
|
342
|
+
#calculates positive and negative relevance scores for each layer
|
|
343
|
+
#returns a dictionary that maps layer names to dictionaries containing positive and negative relevance values.
|
|
344
|
+
model_resource = self.model_resource
|
|
345
|
+
print(model_resource)
|
|
346
|
+
activation_dict = self.activation_dict
|
|
347
|
+
inputcheck = False
|
|
348
|
+
out_layer = model_resource[2][0]
|
|
349
|
+
all_wt_pos = {}
|
|
350
|
+
all_wt_neg = {}
|
|
351
|
+
start_wt_pos, start_wt_neg = UC.calculate_start_wt(all_out[out_layer])
|
|
352
|
+
all_wt_pos[out_layer] = start_wt_pos * multiplier
|
|
353
|
+
all_wt_neg[out_layer] = start_wt_neg * multiplier
|
|
354
|
+
layer_stack = [out_layer]
|
|
355
|
+
while len(layer_stack) > 0:
|
|
356
|
+
start_layer = layer_stack.pop(0)
|
|
357
|
+
if model_resource[1][start_layer]["child"]:
|
|
358
|
+
child_nodes = model_resource[1][start_layer]["child"]
|
|
359
|
+
for ch in child_nodes:
|
|
360
|
+
if ch not in all_wt_pos:
|
|
361
|
+
all_wt_pos[ch] = np.zeros_like(all_out[ch][0])
|
|
362
|
+
all_wt_neg[ch] = np.zeros_like(all_out[ch][0])
|
|
363
|
+
if model_resource[1][start_layer]["class"] == "Dense":
|
|
364
|
+
print('dense')
|
|
365
|
+
l1 = model_resource[0][start_layer]
|
|
366
|
+
w1 = l1.weights[0]
|
|
367
|
+
b1 = l1.weights[1]
|
|
368
|
+
temp_wt_pos, temp_wt_neg = UC.calculate_wt_fc(
|
|
369
|
+
all_wt_pos[start_layer],
|
|
370
|
+
all_wt_neg[start_layer],
|
|
371
|
+
all_out[child_nodes[0]][0],
|
|
372
|
+
w1,
|
|
373
|
+
b1,
|
|
374
|
+
activation_dict[model_resource[1][start_layer]["name"]],
|
|
375
|
+
)
|
|
376
|
+
all_wt_pos[child_nodes[0]] += temp_wt_pos
|
|
377
|
+
all_wt_neg[child_nodes[0]] += temp_wt_neg
|
|
378
|
+
elif model_resource[1][start_layer]["class"] == "Conv2D":
|
|
379
|
+
l1 = model_resource[0][start_layer]
|
|
380
|
+
w1 = l1.weights[0]
|
|
381
|
+
b1 = l1.weights[1]
|
|
382
|
+
temp_wt_pos, temp_wt_neg = UC.calculate_wt_conv(
|
|
383
|
+
all_wt_pos[start_layer],
|
|
384
|
+
all_wt_neg[start_layer],
|
|
385
|
+
all_out[child_nodes[0]][0],
|
|
386
|
+
w1,
|
|
387
|
+
b1,
|
|
388
|
+
activation_dict[model_resource[1][start_layer]["name"]],
|
|
389
|
+
)
|
|
390
|
+
all_wt_pos[child_nodes[0]] += temp_wt_pos
|
|
391
|
+
all_wt_neg[child_nodes[0]] += temp_wt_neg
|
|
392
|
+
elif model_resource[1][start_layer]["class"] == "Reshape":
|
|
393
|
+
temp_wt_pos = UC.calculate_wt_rshp(
|
|
394
|
+
all_wt_pos[start_layer], all_out[child_nodes[0]][0]
|
|
395
|
+
)
|
|
396
|
+
temp_wt_neg = UC.calculate_wt_rshp(
|
|
397
|
+
all_wt_neg[start_layer], all_out[child_nodes[0]][0]
|
|
398
|
+
)
|
|
399
|
+
all_wt_pos[child_nodes[0]] += temp_wt_pos
|
|
400
|
+
all_wt_neg[child_nodes[0]] += temp_wt_neg
|
|
401
|
+
elif (
|
|
402
|
+
model_resource[1][start_layer]["class"] == "GlobalAveragePooling2D"
|
|
403
|
+
):
|
|
404
|
+
temp_wt_pos, temp_wt_neg = UC.calculate_wt_gavgpool(
|
|
405
|
+
all_wt_pos[start_layer],
|
|
406
|
+
all_wt_neg[start_layer],
|
|
407
|
+
all_out[child_nodes[0]][0],
|
|
408
|
+
)
|
|
409
|
+
all_wt_pos[child_nodes[0]] += temp_wt_pos
|
|
410
|
+
all_wt_neg[child_nodes[0]] += temp_wt_neg
|
|
411
|
+
elif model_resource[1][start_layer]["class"] == "Flatten":
|
|
412
|
+
temp_wt = UC.calculate_wt_rshp(
|
|
413
|
+
all_wt_pos[start_layer], all_out[child_nodes[0]][0]
|
|
414
|
+
)
|
|
415
|
+
all_wt_pos[child_nodes[0]] += temp_wt
|
|
416
|
+
temp_wt = UC.calculate_wt_rshp(
|
|
417
|
+
all_wt_neg[start_layer], all_out[child_nodes[0]][0]
|
|
418
|
+
)
|
|
419
|
+
all_wt_neg[child_nodes[0]] += temp_wt
|
|
420
|
+
elif (
|
|
421
|
+
model_resource[1][start_layer]["class"] == "GlobalAveragePooling2D"
|
|
422
|
+
):
|
|
423
|
+
temp_wt_pos, temp_wt_neg = UC.calculate_wt_gavgpool(
|
|
424
|
+
all_wt_pos[start_layer],
|
|
425
|
+
all_wt_neg[start_layer],
|
|
426
|
+
all_out[child_nodes[0]][0],
|
|
427
|
+
)
|
|
428
|
+
all_wt_pos[child_nodes[0]] += temp_wt_pos
|
|
429
|
+
all_wt_neg[child_nodes[0]] += temp_wt_neg
|
|
430
|
+
elif model_resource[1][start_layer]["class"] == "MaxPooling2D":
|
|
431
|
+
l1 = model_resource[0][start_layer]
|
|
432
|
+
temp_wt = UC.calculate_wt_maxpool(
|
|
433
|
+
all_wt_pos[start_layer],
|
|
434
|
+
all_out[child_nodes[0]][0],
|
|
435
|
+
l1.pool_size,
|
|
436
|
+
)
|
|
437
|
+
all_wt_pos[child_nodes[0]] += temp_wt
|
|
438
|
+
temp_wt = UC.calculate_wt_maxpool(
|
|
439
|
+
all_wt_neg[start_layer],
|
|
440
|
+
all_out[child_nodes[0]][0],
|
|
441
|
+
l1.pool_size,
|
|
442
|
+
)
|
|
443
|
+
all_wt_neg[child_nodes[0]] += temp_wt
|
|
444
|
+
elif model_resource[1][start_layer]["class"] == "AveragePooling2D":
|
|
445
|
+
l1 = model_resource[0][start_layer]
|
|
446
|
+
temp_wt_pos, temp_wt_neg = UC.calculate_wt_avgpool(
|
|
447
|
+
all_wt_pos[start_layer],
|
|
448
|
+
all_wt_neg[start_layer],
|
|
449
|
+
all_out[child_nodes[0]][0],
|
|
450
|
+
l1.pool_size,
|
|
451
|
+
)
|
|
452
|
+
all_wt_pos[child_nodes[0]] += temp_wt_pos
|
|
453
|
+
all_wt_neg[child_nodes[0]] += temp_wt_neg
|
|
454
|
+
elif model_resource[1][start_layer]["class"] == "Concatenate":
|
|
455
|
+
temp_wt = UC.calculate_wt_concat(
|
|
456
|
+
all_wt_pos[start_layer],
|
|
457
|
+
[all_out[ch] for ch in child_nodes],
|
|
458
|
+
model_resource[0][start_layer].axis,
|
|
459
|
+
)
|
|
460
|
+
for ind, ch in enumerate(child_nodes):
|
|
461
|
+
all_wt_pos[ch] += temp_wt[ind]
|
|
462
|
+
temp_wt = UC.calculate_wt_concat(
|
|
463
|
+
all_wt_neg[start_layer],
|
|
464
|
+
[all_out[ch] for ch in child_nodes],
|
|
465
|
+
model_resource[0][start_layer].axis,
|
|
466
|
+
)
|
|
467
|
+
for ind, ch in enumerate(child_nodes):
|
|
468
|
+
all_wt_neg[ch] += temp_wt[ind]
|
|
469
|
+
|
|
470
|
+
elif model_resource[1][start_layer]["class"] == "Add":
|
|
471
|
+
temp_wt = UC.calculate_wt_add(
|
|
472
|
+
all_wt_pos[start_layer],
|
|
473
|
+
all_wt_neg[start_layer],
|
|
474
|
+
[all_out[ch] for ch in child_nodes],
|
|
475
|
+
)
|
|
476
|
+
for ind, ch in enumerate(child_nodes):
|
|
477
|
+
all_wt_pos[ch] += temp_wt[ind][0]
|
|
478
|
+
all_wt_neg[ch] += temp_wt[ind][1]
|
|
479
|
+
elif model_resource[1][start_layer]["class"] == "LSTM":
|
|
480
|
+
print('lstm')
|
|
481
|
+
l1 = model_resource[0][start_layer]
|
|
482
|
+
return_sequence = l1.return_sequences
|
|
483
|
+
units = l1.units
|
|
484
|
+
num_of_cells = l1.input_shape[1]
|
|
485
|
+
lstm_obj_f = UC.LSTM_forward(
|
|
486
|
+
num_of_cells, units, l1.weights, return_sequence, False
|
|
487
|
+
)
|
|
488
|
+
lstm_obj_b = UC.LSTM_backtrace(
|
|
489
|
+
num_of_cells,
|
|
490
|
+
units,
|
|
491
|
+
[i.numpy() for i in l1.weights],
|
|
492
|
+
return_sequence,
|
|
493
|
+
False,
|
|
494
|
+
)
|
|
495
|
+
temp_out_f = lstm_obj_f.calculate_lstm_wt(
|
|
496
|
+
all_out[child_nodes[0]][0]
|
|
497
|
+
)
|
|
498
|
+
temp_wt_pos, temp_wt_neg = lstm_obj_b.calculate_lstm_wt(
|
|
499
|
+
all_wt_pos[start_layer],
|
|
500
|
+
all_wt_neg[start_layer],
|
|
501
|
+
lstm_obj_f.compute_log,
|
|
502
|
+
)
|
|
503
|
+
all_wt_pos[child_nodes[0]] = temp_wt_pos
|
|
504
|
+
all_wt_neg[child_nodes[0]] = temp_wt_neg
|
|
505
|
+
|
|
506
|
+
elif model_resource[1][start_layer]["class"] == "Embedding":
|
|
507
|
+
print('embedding layer')
|
|
508
|
+
temp_wt_pos = all_wt_pos[start_layer]
|
|
509
|
+
temp_wt_neg = all_wt_neg[start_layer]
|
|
510
|
+
|
|
511
|
+
temp_wt_pos = np.mean(temp_wt_pos,axis=1)
|
|
512
|
+
temp_wt_neg = np.mean(temp_wt_neg,axis=1)
|
|
513
|
+
|
|
514
|
+
all_wt_pos[child_nodes[0]] = all_wt_pos[child_nodes[0]] + temp_wt_pos
|
|
515
|
+
all_wt_neg[child_nodes[0]] = all_wt_neg[child_nodes[0]] + temp_wt_neg
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
else:
|
|
519
|
+
print('else')
|
|
520
|
+
temp_wt_pos = all_wt_pos[start_layer]
|
|
521
|
+
temp_wt_neg = all_wt_neg[start_layer]
|
|
522
|
+
all_wt_pos[child_nodes[0]] += temp_wt_pos
|
|
523
|
+
all_wt_neg[child_nodes[0]] += temp_wt_neg
|
|
524
|
+
for ch in child_nodes:
|
|
525
|
+
if not (ch in layer_stack):
|
|
526
|
+
layer_stack.append(ch)
|
|
527
|
+
return all_wt_pos, all_wt_neg
|