LN1996 commited on
Commit
cd3492a
·
1 Parent(s): 25a0da8

Upload grad_cam_func.py

Browse files
Files changed (1) hide show
  1. grad_cam_func.py +150 -0
grad_cam_func.py ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import torch
3
+ import ttach as tta
4
+ from typing import Callable, List, Tuple
5
+ from pytorch_grad_cam.activations_and_gradients import ActivationsAndGradients
6
+ from pytorch_grad_cam.utils.svd_on_activations import get_2d_projection
7
+ from pytorch_grad_cam.utils.image import scale_cam_image
8
+ from pytorch_grad_cam.utils.model_targets import ClassifierOutputTarget
9
+ import pandas as pd
10
+
11
+ import config as config
12
+ import utils
13
+
14
+ class BaseCAM:
15
+ def __init__(self,
16
+ model: torch.nn.Module,
17
+ target_layers: List[torch.nn.Module],
18
+ use_cuda: bool = False,
19
+ reshape_transform: Callable = None,
20
+ compute_input_gradient: bool = False,
21
+ uses_gradients: bool = True) -> None:
22
+
23
+ self.model = model.eval()
24
+ self.target_layers = target_layers
25
+ self.cuda = use_cuda
26
+ if self.cuda:
27
+ self.model = model.cuda()
28
+ self.reshape_transform = reshape_transform
29
+ self.compute_input_gradient = compute_input_gradient
30
+ self.uses_gradients = uses_gradients
31
+ self.activations_and_grads = ActivationsAndGradients(
32
+ self.model, target_layers, reshape_transform)
33
+
34
+ """ Get a vector of weights for every channel in the target layer.
35
+ Methods that return weights channels,
36
+ will typically need to only implement this function. """
37
+
38
+ def get_cam_image(self,
39
+ input_tensor: torch.Tensor,
40
+ target_layer: torch.nn.Module,
41
+ targets: List[torch.nn.Module],
42
+ activations: torch.Tensor,
43
+ grads: torch.Tensor,
44
+ eigen_smooth: bool = False) -> np.ndarray:
45
+
46
+ return get_2d_projection(activations)
47
+
48
+ def forward(self,
49
+ input_tensor: torch.Tensor,
50
+ targets: List[torch.nn.Module],
51
+ eigen_smooth: bool = False) -> np.ndarray:
52
+
53
+ if self.cuda:
54
+ input_tensor = input_tensor.cuda()
55
+
56
+ if self.compute_input_gradient:
57
+ input_tensor = torch.autograd.Variable(input_tensor,
58
+ requires_grad=True)
59
+
60
+ outputs = self.activations_and_grads(input_tensor)
61
+
62
+ if targets is None:
63
+ bboxes = [[] for _ in range(1)]
64
+ for i in range(3):
65
+ batch_size, A, S, _, _ = outputs[i].shape
66
+ anchor = config.SCALED_ANCHORS[i]
67
+ boxes_scale_i = utils.cells_to_bboxes(
68
+ outputs[i], anchor, S=S, is_preds=True
69
+ )
70
+ for idx, (box) in enumerate(boxes_scale_i):
71
+ bboxes[idx] += box
72
+
73
+ nms_boxes = utils.non_max_suppression(
74
+ bboxes[0], iou_threshold=0.5, threshold=0.4, box_format="midpoint",
75
+ )
76
+ # target_categories = np.argmax(outputs.cpu().data.numpy(), axis=-1)
77
+ target_categories = [box[0] for box in nms_boxes]
78
+ targets = [ClassifierOutputTarget(
79
+ category) for category in target_categories]
80
+
81
+
82
+ if self.uses_gradients:
83
+ self.model.zero_grad()
84
+ loss = sum([target(output)
85
+ for target, output in zip(targets, outputs)])
86
+ loss.backward(retain_graph=True)
87
+
88
+ # In most of the saliency attribution papers, the saliency is
89
+ # computed with a single target layer.
90
+ # Commonly it is the last convolutional layer.
91
+ # Here we support passing a list with multiple target layers.
92
+ # It will compute the saliency image for every image,
93
+ # and then aggregate them (with a default mean aggregation).
94
+ # This gives you more flexibility in case you just want to
95
+ # use all conv layers for example, all Batchnorm layers,
96
+ # or something else.
97
+
98
+ cam_per_layer = self.compute_cam_per_layer(input_tensor,
99
+ targets,
100
+ eigen_smooth)
101
+ return self.aggregate_multi_layers(cam_per_layer)
102
+
103
+ def get_target_width_height(self,
104
+ input_tensor: torch.Tensor) -> Tuple[int, int]:
105
+ width, height = input_tensor.size(-1), input_tensor.size(-2)
106
+ return width, height
107
+
108
+ def compute_cam_per_layer(
109
+ self,
110
+ input_tensor: torch.Tensor,
111
+ targets: List[torch.nn.Module],
112
+ eigen_smooth: bool) -> np.ndarray:
113
+
114
+ activations_list = [a.cpu().data.numpy()
115
+ for a in self.activations_and_grads.activations]
116
+ grads_list = [g.cpu().data.numpy()
117
+ for g in self.activations_and_grads.gradients]
118
+ target_size = self.get_target_width_height(input_tensor)
119
+
120
+ cam_per_target_layer = []
121
+ # Loop over the saliency image from every layer
122
+ for i in range(len(self.target_layers)):
123
+ target_layer = self.target_layers[i]
124
+ layer_activations = None
125
+ layer_grads = None
126
+ if i < len(activations_list):
127
+ layer_activations = activations_list[i]
128
+ if i < len(grads_list):
129
+ layer_grads = grads_list[i]
130
+
131
+ cam = self.get_cam_image(input_tensor,
132
+ target_layer,
133
+ targets,
134
+ layer_activations,
135
+ layer_grads,
136
+ eigen_smooth)
137
+ cam = np.maximum(cam, 0)
138
+ scaled = scale_cam_image(cam, target_size)
139
+ cam_per_target_layer.append(scaled[:, None, :])
140
+
141
+ return cam_per_target_layer
142
+
143
+ def aggregate_multi_layers(
144
+ self,
145
+ cam_per_target_layer: np.ndarray) -> np.ndarray:
146
+ cam_per_target_layer = np.concatenate(cam_per_target_layer, axis=1)
147
+ cam_per_target_layer = np.maximum(cam_per_target_layer, 0)
148
+ result = np.mean(cam_per_target_layer, axis=1)
149
+
150
+ return scale_cam_image(result)