Source code for mindspore.nn.metrics.occlusion_sensitivity

# Copyright 2021 Huawei Technologies Co., Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ============================================================================
"""OcclusionSensitivity."""
import numpy as np
from mindspore import nn
from mindspore.common.tensor import Tensor
from mindspore._checkparam import Validator as validator
from .metric import Metric, rearrange_inputs

try:
    from tqdm import trange
except (ImportError, AttributeError):
    trange = range
finally:
    pass


[docs]class OcclusionSensitivity(Metric): """ Calculates the occlusion sensitivity of the model for a given image. It illustrates which parts of an image are most important for a network's classification. Occlusion sensitivity refers to how the predicted probability changes with the change of the occluded part of an image. The higher the value in the output image is, the greater the decline of certainty, indicating that the occluded area is more important in the decision-making process. Args: pad_val (float): The padding value of the occluded part in an image. Default: 0.0. margin (Union[int, Sequence]): Create a cuboid / cube around the voxel you want to occlude. Default: 2. n_batch (int): number of images in a batch. Default: 128. b_box (Sequence): Bounding box on which to perform the analysis. The output image will also match in size. There should be a minimum and maximum for all dimensions except batch: ``[min1, max1, min2, max2,...]``. If no bounding box is supplied, this will be the same size as the input image. If a bounding box is used, the output image will be cropped to this size. Default: None. Supported Platforms: ``Ascend`` ``GPU`` ``CPU`` Example: >>> import numpy as np >>> from mindspore import nn, Tensor >>> >>> class DenseNet(nn.Cell): ... def __init__(self): ... super(DenseNet, self).__init__() ... w = np.array([[0.1, 0.8, 0.1, 0.1],[1, 1, 1, 1]]).astype(np.float32) ... b = np.array([0.3, 0.6]).astype(np.float32) ... self.dense = nn.Dense(4, 2, weight_init=Tensor(w), bias_init=Tensor(b)) ... ... def construct(self, x): ... return self.dense(x) >>> >>> model = DenseNet() >>> test_data = np.array([[0.1, 0.2, 0.3, 0.4]]).astype(np.float32) >>> label = np.array(1).astype(np.int32) >>> metric = nn.OcclusionSensitivity() >>> metric.clear() >>> metric.update(model, test_data, label) >>> score = metric.eval() >>> print(score) [0.29999995 0.6 1. 0.9] """ def __init__(self, pad_val=0.0, margin=2, n_batch=128, b_box=None): super().__init__() self.pad_val = validator.check_value_type("pad_val", pad_val, [float]) self.margin = validator.check_value_type("margin", margin, [int, list]) self.n_batch = validator.check_value_type("n_batch", n_batch, [int]) self.b_box = b_box if b_box is None else validator.check_value_type("b_box", b_box, [list]) self.clear()
[docs] def clear(self): """Clears the internal evaluation result.""" self._baseline = 0 self._sensitivity_im = 0 self._is_update = False
def _check_input_bounding_box(self, b_box, im_shape): """Check that the bounding box (if supplied) is as expected.""" # If no bounding box has been supplied, set min and max to None if b_box is None: b_box_min = b_box_max = None else: if len(b_box) != 2 * len(im_shape): raise ValueError(f"For 'OcclusionSensitivity', the bounding box should contain upper and lower for " f"all dimensions (except batch number), and the length of 'b_box' should be twice " f"as long as predicted value's (except batch number), but got 'b_box' length " f"{len(b_box)}, predicted value length (except batch number) {len(im_shape)}.") b_box_min = np.array(b_box[::2]) b_box_max = np.array(b_box[1::2]) b_box_min[b_box_min < 0] = 0 b_box_max[b_box_max < 0] = im_shape[b_box_max < 0] - 1 if np.any(b_box_max >= im_shape): raise ValueError("For 'OcclusionSensitivity', maximum bounding box should be smaller than image size " "for all values.") if np.any(b_box_min > b_box_max): raise ValueError("For 'OcclusionSensitivity', minimum bounding box should be smaller than maximum " "bounding box for all values.") return b_box_min, b_box_max def _append_to_sensitivity_im(self, model, batch_images, batch_ids, sensitivity_im): """ For a given number of images, the probability of predicting a given label is obtained. Attach to previous assessment. """ batch_images = np.vstack(batch_images) batch_ids = np.expand_dims(batch_ids, 1) model_numpy = model(Tensor(batch_images)).asnumpy() first_indices = np.arange(batch_ids.shape[0])[:, None] scores = model_numpy[first_indices, batch_ids] if sensitivity_im.size == 0: return np.vstack(scores) return np.vstack((sensitivity_im, scores))
[docs] @rearrange_inputs def update(self, *inputs): """ Updates input, including `model`, `y_pred` and `label`. Args: inputs: `y_pred` and `label` are a Tensor, list or numpy.ndarray. `y_pred`: a batch of images to test, which could be 2D or 3D. `label`: classification labels to check for changes. `label` is normally the true label, but doesn't have to be. Raises: ValueError: If the number of inputs is not 3. RuntimeError: If the batch size is not 1. RuntimeError: If the number of labels is different from the number of batches. """ if len(inputs) != 3: raise ValueError("For 'OcclusionSensitivity.update', it needs 3 inputs (classification model, " "predicted value, label), but got {}.".format(len(inputs))) model = inputs[0] y_pred = self._convert_data(inputs[1]) label = self._convert_data(inputs[2]) model = validator.check_value_type("model", model, [nn.Cell]) if y_pred.shape[0] > 1: raise RuntimeError(f"For 'OcclusionSensitivity.update', the shape at index 0 of the predicted value " f"(input[1]) should be 1, but got {y_pred.shape[0]}.") if isinstance(label, int): label = np.array([[label]], dtype=int) # If the label is a tensor, make sure there's only 1 element elif np.prod(label.shape) != y_pred.shape[0]: raise RuntimeError(f"For 'OcclusionSensitivity.update', the number of the label (input[2]) should be " f"same as the batches, but got the label number {np.prod(label.shape)}, " f"and batches {y_pred.shape[0]}.") y_pred_shape = np.array(y_pred.shape[1:]) b_box_min, b_box_max = self._check_input_bounding_box(self.b_box, y_pred_shape) temp = model(Tensor(y_pred)).asnumpy() self._baseline = temp[0, label].item() batch_images = [] batch_ids = [] sensitivity_im = np.empty(0, dtype=float) output_im_shape = y_pred_shape if self.b_box is None else b_box_max - b_box_min + 1 num_required_predictions = np.prod(output_im_shape) for i in trange(num_required_predictions): idx = np.unravel_index(i, output_im_shape) if b_box_min is not None: idx += b_box_min min_idx = [max(0, i - self.margin) for i in idx] max_idx = [min(j, i + self.margin) for i, j in zip(idx, y_pred_shape)] occlu_im = y_pred.copy() occlu_im[(...,) + tuple(slice(i, j) for i, j in zip(min_idx, max_idx))] = self.pad_val batch_images.append(occlu_im) batch_ids.append(label) if len(batch_images) == self.n_batch or i == num_required_predictions - 1: sensitivity_im = self._append_to_sensitivity_im(model, batch_images, batch_ids, sensitivity_im) batch_images = [] batch_ids = [] self._sensitivity_im = sensitivity_im.reshape(output_im_shape) self._is_update = True
[docs] def eval(self): """ Computes the occlusion_sensitivity. Returns: A numpy ndarray. Raises: RuntimeError: If the update method is not called first, an error will be reported. """ if not self._is_update: raise RuntimeError("Please call the 'update' method before calling 'eval' method.") sensitivity = self._baseline - np.squeeze(self._sensitivity_im) return sensitivity