dl-backtrace 0.0.2.dev3__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.

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