signal-grad-cam 0.1.1__tar.gz → 0.1.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of signal-grad-cam might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: signal_grad_cam
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: SignalGrad-CAM aims at generalising Grad-CAM to one-dimensional applications, while enhancing usability and efficiency.
5
5
  Home-page: https://github.com/samuelepe11/signal_grad_cam
6
6
  Author: Samuele Pe
@@ -5,7 +5,7 @@ with open("README.md", "r") as f:
5
5
 
6
6
  setup(
7
7
  name="signal_grad_cam",
8
- version="0.1.1",
8
+ version="0.1.3",
9
9
  description="SignalGrad-CAM aims at generalising Grad-CAM to one-dimensional applications, while enhancing usability"
10
10
  " and efficiency.",
11
11
  keywords="XAI, class activation maps, CNN, time series",
@@ -23,8 +23,8 @@ class CamBuilder:
23
23
  def __init__(self, model: torch.nn.Module | tf.keras.Model | Any,
24
24
  transform_fn: Callable[[np.ndarray, *tuple[Any, ...]], torch.Tensor | tf.Tensor] = None,
25
25
  class_names: List[str] = None, time_axs: int = 1, input_transposed: bool = False,
26
- ignore_channel_dim: bool = False, model_output_index: int = None, extend_search: bool = False,
27
- padding_dim: int = None, seed: int = 11):
26
+ ignore_channel_dim: bool = False, is_regression_network: bool = False, model_output_index: int = None,
27
+ extend_search: bool = False, padding_dim: int = None, seed: int = 11):
28
28
  """
29
29
  Initializes the CamBuilder class. The constructor also displays, if present and retrievable, the 1D- and
30
30
  2D-convolutional layers in the network, as well as the final Sigmoid/Softmax activation. Additionally, the CAM
@@ -44,6 +44,10 @@ class CamBuilder:
44
44
  during model inference, either by the model itself or by the preprocessing function.
45
45
  :param ignore_channel_dim: (optional, default is False) A boolean indicating whether to ignore the channel
46
46
  dimension. This is useful when the model expects inputs without a singleton channel dimension.
47
+ :param is_regression_network: (optional, default is False) A boolean indicating whether the network is designed
48
+ for a regression task. If set to True, the CAM will highlight both positive and negative contributions.
49
+ While negative contributions are typically irrelevant for classification-based saliency maps, they can be
50
+ meaningful in regression settings, as they may represent features that decrease the predicted value.
47
51
  :param model_output_index: (optional, default is None) An integer index specifying which of the model's outputs
48
52
  represents output scores (or probabilities). If there is only one output, this argument can be ignored.
49
53
  :param extend_search: (optional, default is False) A boolean flag indicating whether to deepend the search for
@@ -67,6 +71,7 @@ class CamBuilder:
67
71
  self.time_axs = time_axs
68
72
  self.input_transposed = input_transposed
69
73
  self.ignore_channel_dim = ignore_channel_dim
74
+ self.is_regression_network = is_regression_network
70
75
  self.model_output_index = model_output_index
71
76
  self.padding_dim = padding_dim
72
77
  self.original_dims = []
@@ -136,7 +141,8 @@ class CamBuilder:
136
141
  :param results_dir_path: (optional, default is None) A string representing the relative path to the directory
137
142
  for storing results. If None, the output will be displayed in a figure.
138
143
  :param aspect_factor: (optional, default is 100) A numerical value to set the aspect ratio of the output signal
139
- one-dimensional CAM.
144
+ one-dimensional CAM. Note that this value should be grater than the length of the input signal considered,
145
+ otherwise it is set to the length of the considered signal.
140
146
  :param data_shape_list: (optional, default is None) A list of integer tuples storing the original input sizes,
141
147
  used to set the CAM shape after resizing during preprocessing. The expected format is number of rows x
142
148
  number of columns.
@@ -648,7 +654,8 @@ class CamBuilder:
648
654
  :param dt: (optional, default is 10) A numerical value representing the granularity of the time axis in seconds
649
655
  in the output display.
650
656
  :param aspect_factor: (optional, default is 100) A numerical value to set the aspect ratio of the output signal
651
- one-dimensional CAM.
657
+ one-dimensional CAM. Note that this value should be grater than the length of the input signal considered,
658
+ otherwise it is set to the length of the considered signal.
652
659
  :param bar_ranges: A tuple containing two np.ndarrays, corresponding to the minimum and maximum importance scores
653
660
  per CAM for each item in the input data list, based on a given setting (defined by algorithm, target
654
661
  layer, and target class).
@@ -674,7 +681,7 @@ class CamBuilder:
674
681
  norm = self.__get_norm(map)
675
682
 
676
683
  if map.shape[1] == 1:
677
- aspect = int(map.shape[0] / aspect_factor)
684
+ aspect = int(map.shape[0] / aspect_factor) if map.shape[0] <= aspect_factor else 1
678
685
  map = np.transpose(map)
679
686
  else:
680
687
  if is_2d_layer:
@@ -15,8 +15,8 @@ class TorchCamBuilder(CamBuilder):
15
15
 
16
16
  def __init__(self, model: nn.Module | Any, transform_fn: Callable[[np.ndarray, *tuple[Any, ...]], torch.Tensor]
17
17
  = None, class_names: List[str] = None, time_axs: int = 1, input_transposed: bool = False,
18
- ignore_channel_dim: bool = False, model_output_index: int = None, extend_search: bool = False,
19
- use_gpu: bool = False, padding_dim: int = None, seed: int = 11):
18
+ ignore_channel_dim: bool = False, is_regression_network: bool = False, model_output_index: int = None,
19
+ extend_search: bool = False, use_gpu: bool = False, padding_dim: int = None, seed: int = 11):
20
20
  """
21
21
  Initializes the TorchCamBuilder class. The constructor also displays, if present and retrievable, the 1D- and
22
22
  2D-convolutional layers in the network, as well as the final Sigmoid/Softmax activation. Additionally, the CAM
@@ -36,6 +36,10 @@ class TorchCamBuilder(CamBuilder):
36
36
  during model inference, either by the model itself or by the preprocessing function.
37
37
  :param ignore_channel_dim: (optional, default is False) A boolean indicating whether to ignore the channel
38
38
  dimension. This is useful when the model expects inputs without a singleton channel dimension.
39
+ :param is_regression_network: (optional, default is False) A boolean indicating whether the network is designed
40
+ for a regression task. If set to True, the CAM will highlight both positive and negative contributions.
41
+ While negative contributions are typically irrelevant for classification-based saliency maps, they can be
42
+ meaningful in regression settings, as they may represent features that decrease the predicted value.
39
43
  :param model_output_index: (optional, default is None) An integer index specifying which of the model's outputs
40
44
  represents output scores (or probabilities). If there is only one output, this argument can be ignored.
41
45
  :param extend_search: (optional, default is False) A boolean flag indicating whether to deepend the search for
@@ -52,6 +56,7 @@ class TorchCamBuilder(CamBuilder):
52
56
  super(TorchCamBuilder, self).__init__(model=model, transform_fn=transform_fn, class_names=class_names,
53
57
  time_axs=time_axs, input_transposed=input_transposed,
54
58
  ignore_channel_dim=ignore_channel_dim,
59
+ is_regression_network=is_regression_network,
55
60
  model_output_index=model_output_index, extend_search=extend_search,
56
61
  padding_dim=padding_dim, seed=seed)
57
62
 
@@ -180,12 +185,29 @@ class TorchCamBuilder(CamBuilder):
180
185
 
181
186
  if softmax_final:
182
187
  # Approximate Softmax inversion formula logit = log(prob) + constant, as the constant is negligible
183
- # during derivation
184
- target_scores = torch.log(outputs)
188
+ # during derivation. Clamp probabilities before log application to avoid null maps for maximum confidence.
189
+ target_scores = torch.log(torch.clamp(outputs, min=0, max=1 - 1e-6))
185
190
  target_probs = outputs
191
+
192
+ # Adjust results for binary network
193
+ if len(outputs.shape) == 1:
194
+ target_scores = torch.stack([-target_scores, target_scores], dim=1)
195
+ target_probs = torch.stack([1 - target_probs, target_probs], dim=1)
196
+ elif len(outputs.shape) == 2 and outputs.shape[1] == 1:
197
+ target_scores = torch.cat([-target_scores, target_scores], dim=1)
198
+ target_probs = torch.cat([1 - target_probs, target_probs], dim=1)
186
199
  else:
200
+ if len(outputs.shape) == 1:
201
+ outputs = torch.stack([-outputs, outputs], dim=1)
202
+ elif len(outputs.shape) == 2 and outputs.shape[1] == 1:
203
+ outputs = torch.cat([-outputs, outputs], dim=1)
187
204
  target_scores = outputs
188
- target_probs = torch.softmax(target_scores, dim=1)
205
+
206
+ if len(outputs.shape) == 2 and outputs.shape[1] > 1:
207
+ target_probs = torch.softmax(target_scores, dim=1)
208
+ else:
209
+ tmp = torch.sigmoid(target_scores[:, 1])
210
+ target_probs = torch.stack([1 - tmp, tmp], dim=1)
189
211
 
190
212
  target_probs = target_probs[:, target_class].cpu().detach().numpy()
191
213
 
@@ -229,7 +251,8 @@ class TorchCamBuilder(CamBuilder):
229
251
  activations[i, :] *= weights[i]
230
252
 
231
253
  cam = torch.sum(activations, dim=0)
232
- cam = torch.relu(cam)
254
+ if not self.is_regression_network:
255
+ cam = torch.relu(cam)
233
256
  return cam
234
257
 
235
258
  def _get_hirescam_map(self, is_2d_layer: bool, batch_idx: int) -> torch.Tensor:
@@ -254,7 +277,8 @@ class TorchCamBuilder(CamBuilder):
254
277
  activations[i, :] *= gradients[i, :]
255
278
 
256
279
  cam = torch.sum(activations, dim=0)
257
- cam = torch.relu(cam)
280
+ if not self.is_regression_network:
281
+ cam = torch.relu(cam)
258
282
  return cam
259
283
 
260
284
  def __get_activation_forward_hook(self, layer: nn.Module, inputs: Tuple[torch.Tensor, ...], outputs: torch.Tensor) \
@@ -20,8 +20,8 @@ class TfCamBuilder(CamBuilder):
20
20
 
21
21
  def __init__(self, model: tf.keras.Model | Any, transform_fn: Callable[[np.ndarray, *tuple[Any, ...]], tf.Tensor]
22
22
  = None, class_names: List[str] = None, time_axs: int = 1, input_transposed: bool = False,
23
- ignore_channel_dim: bool = False, model_output_index: int = None, extend_search: bool = False,
24
- padding_dim: int = None, seed: int = 11):
23
+ ignore_channel_dim: bool = False, is_regression_network: bool = False, model_output_index: int = None,
24
+ extend_search: bool = False, padding_dim: int = None, seed: int = 11):
25
25
  """
26
26
  Initializes the TfCamBuilder class. The constructor also displays, if present and retrievable, the 1D- and
27
27
  2D-convolutional layers in the network, as well as the final Sigmoid/Softmax activation. Additionally, the CAM
@@ -41,6 +41,10 @@ class TfCamBuilder(CamBuilder):
41
41
  during model inference, either by the model itself or by the preprocessing function.
42
42
  :param ignore_channel_dim: (optional, default is False) A boolean indicating whether to ignore the channel
43
43
  dimension. This is useful when the model expects inputs without a singleton channel dimension.
44
+ :param is_regression_network: (optional, default is False) A boolean indicating whether the network is designed
45
+ for a regression task. If set to True, the CAM will highlight both positive and negative contributions.
46
+ While negative contributions are typically irrelevant for classification-based saliency maps, they can be
47
+ meaningful in regression settings, as they may represent features that decrease the predicted value.
44
48
  :param model_output_index: (optional, default is None) An integer index specifying which of the model's outputs
45
49
  represents output scores (or probabilities). If there is only one output, this argument can be ignored.
46
50
  :param extend_search: (optional, default is False) A boolean flag indicating whether to deepend the search for
@@ -54,7 +58,9 @@ class TfCamBuilder(CamBuilder):
54
58
  # Initialize attributes
55
59
  super(TfCamBuilder, self).__init__(model=model, transform_fn=transform_fn, class_names=class_names,
56
60
  time_axs=time_axs, input_transposed=input_transposed,
57
- ignore_channel_dim=ignore_channel_dim, model_output_index=model_output_index,
61
+ ignore_channel_dim=ignore_channel_dim,
62
+ is_regression_network=is_regression_network,
63
+ model_output_index=model_output_index,
58
64
  extend_search=extend_search, padding_dim=padding_dim, seed=seed)
59
65
 
60
66
  # Set seeds
@@ -189,17 +195,35 @@ class TfCamBuilder(CamBuilder):
189
195
 
190
196
  grad_model = keras.models.Model(self.model.inputs, [target_layer.output, self.model.output])
191
197
  extra_inputs_list = extra_inputs_list or []
192
- with tf.GradientTape() as tape:
198
+ with (tf.GradientTape() as tape):
193
199
  self.activations, outputs = grad_model([data_batch] + extra_inputs_list)
194
200
 
195
201
  if softmax_final:
196
202
  # Approximate Softmax inversion formula logit = log(prob) + constant, as the constant is negligible
197
- # during derivation
198
- target_scores = tf.math.log(outputs)
203
+ # during derivation. Clamp probabilities before log application to avoid null maps for maximum
204
+ # confidence.
205
+ target_scores = tf.math.log(tf.clip_by_value(outputs, 0, 1.0 - 1e-6))
199
206
  target_probs = outputs
207
+
208
+ # Adjust results for binary network
209
+ if len(outputs.shape) == 1:
210
+ target_scores = tf.stack([-target_scores, target_scores], axis=1)
211
+ target_probs = tf.stack([1 - target_probs, target_probs], axis=1)
212
+ elif len(outputs.shape) == 2 and outputs.shape[1] == 1:
213
+ target_scores = tf.concat([-target_scores, target_scores], axis=1)
214
+ target_probs = tf.concat([1 - target_probs, target_probs], axis=1)
200
215
  else:
216
+ if len(outputs.shape) == 1:
217
+ outputs = tf.stack([-outputs, outputs], axis=1)
218
+ elif len(outputs.shape) == 2 and outputs.shape[1] == 1:
219
+ outputs = tf.concat([-outputs, outputs], axis=1)
201
220
  target_scores = outputs
202
- target_probs = tf.nn.softmax(target_scores, axis=1)
221
+
222
+ if len(outputs.shape) == 2 and outputs.shape[1] > 1:
223
+ target_probs = tf.nn.softmax(target_scores, axis=1)
224
+ else:
225
+ tmp = tf.math.sigmoid(target_scores[:, 1])
226
+ target_probs = tf.stack([1 - tmp, tmp], axis=1)
203
227
 
204
228
  target_scores = target_scores[:, target_class]
205
229
  target_probs = target_probs[:, target_class]
@@ -243,7 +267,8 @@ class TfCamBuilder(CamBuilder):
243
267
  activations[:, i] *= weights[i]
244
268
 
245
269
  cam = tf.reduce_sum(tf.convert_to_tensor(activations), axis=-1)
246
- cam = tf.nn.relu(cam)
270
+ if not self.is_regression_network:
271
+ cam = tf.nn.relu(cam)
247
272
  return cam
248
273
 
249
274
  def _get_hirecam_map(self, is_2d_layer: bool, batch_idx: int) -> tf.Tensor:
@@ -268,7 +293,8 @@ class TfCamBuilder(CamBuilder):
268
293
  activations[:, i] *= gradients[:, i]
269
294
 
270
295
  cam = tf.reduce_sum(tf.convert_to_tensor(activations), axis=-1)
271
- cam = tf.nn.relu(cam)
296
+ if not self.is_regression_network:
297
+ cam = tf.nn.relu(cam)
272
298
  return cam
273
299
 
274
300
  @staticmethod
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: signal-grad-cam
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: SignalGrad-CAM aims at generalising Grad-CAM to one-dimensional applications, while enhancing usability and efficiency.
5
5
  Home-page: https://github.com/samuelepe11/signal_grad_cam
6
6
  Author: Samuele Pe
File without changes