From 62fbbf9eb74e147a9fc92450b21be618d22c9414 Mon Sep 17 00:00:00 2001 From: lixiaohui Date: Tue, 9 Mar 2021 20:27:05 +0800 Subject: [PATCH] fix explainer doc bugs --- .../explainer/_image_classification_runner.py | 21 +++++-- mindspore/explainer/_operators.py | 8 +-- mindspore/explainer/_utils.py | 59 ++++++++----------- .../_attribution/class_sensitivity.py | 16 ++--- .../benchmark/_attribution/faithfulness.py | 14 +++-- .../benchmark/_attribution/localization.py | 20 ++++--- .../benchmark/_attribution/metric.py | 4 +- .../benchmark/_attribution/robustness.py | 16 +++-- .../_attribution/_backprop/backprop_utils.py | 10 ++-- .../_attribution/_backprop/gradcam.py | 15 +++-- .../_attribution/_backprop/gradient.py | 53 +++++------------ .../_attribution/_backprop/modified_relu.py | 22 +++---- .../_attribution/_perturbation/ablation.py | 4 +- .../_attribution/_perturbation/occlusion.py | 14 ++--- .../_attribution/_perturbation/rise.py | 38 ++++++------ 15 files changed, 143 insertions(+), 171 deletions(-) diff --git a/mindspore/explainer/_image_classification_runner.py b/mindspore/explainer/_image_classification_runner.py index d61f719811..faeacaa1d2 100644 --- a/mindspore/explainer/_image_classification_runner.py +++ b/mindspore/explainer/_image_classification_runner.py @@ -83,21 +83,30 @@ class ImageClassificationRunner: >>> from mindspore.explainer.benchmark import Faithfulness >>> from mindspore.nn import Softmax >>> from mindspore.train.serialization import load_checkpoint, load_param_into_net - >>> # Prepare the dataset for explaining and evaluation, e.g., Cifar10 - >>> dataset = get_dataset('/path/to/Cifar10_dataset') + >>> + >>> # The detail of AlexNet is shown in model_zoo.official.cv.alexnet.src.alexnet.py + >>> net = AlexNet(10) + >>> # Load the checkpoint + >>> param_dict = load_checkpoint("/path/to/checkpoint") + >>> load_param_into_net(net, param_dict) + >>> + >>> # Prepare the dataset for explaining and evaluation. + >>> # The detail of create_dataset_cifar10 method is shown in model_zoo.official.cv.alexnet.src.dataset.py + >>> + >>> dataset = create_dataset_cifar10("/path/to/cifar/dataset", 1) >>> labels = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck'] - >>> # load checkpoint to a network, e.g. checkpoint of resnet50 trained on Cifar10 - >>> param_dict = load_checkpoint("checkpoint.ckpt") - >>> net = resnet50(len(labels)) + >>> >>> activation_fn = Softmax() - >>> load_param_into_net(net, param_dict) >>> gbp = GuidedBackprop(net) >>> gradient = Gradient(net) >>> explainers = [gbp, gradient] >>> faithfulness = Faithfulness(len(labels), activation_fn, "NaiveFaithfulness") >>> benchmarkers = [faithfulness] + >>> >>> runner = ImageClassificationRunner("./summary_dir", (dataset, labels), net, activation_fn) >>> runner.register_saliency(explainers=explainers, benchmarkers=benchmarkers) + >>> runner.register_uncertainty() + >>> runner.register_hierarchical_occlusion() >>> runner.run() """ diff --git a/mindspore/explainer/_operators.py b/mindspore/explainer/_operators.py index 76832937d1..25a0238026 100644 --- a/mindspore/explainer/_operators.py +++ b/mindspore/explainer/_operators.py @@ -1,4 +1,4 @@ -# Copyright 2020 Huawei Technologies Co., Ltd +# Copyright 2020-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. @@ -250,9 +250,9 @@ def summation(inputs: Tensor, axis: _Axis = (), keep_dims: bool = False) -> Tens def stack(inputs: List[Tensor], axis: int) -> Tensor: - """Packs a list of tensors in specified axis.""" - pack_op = op.Pack(axis) - outputs = pack_op(inputs) + """Stacks a list of tensors in specified axis.""" + stack_op = op.Stack(axis) + outputs = stack_op(inputs) return outputs diff --git a/mindspore/explainer/_utils.py b/mindspore/explainer/_utils.py index 212a5bc48a..356ca2acf4 100644 --- a/mindspore/explainer/_utils.py +++ b/mindspore/explainer/_utils.py @@ -1,4 +1,4 @@ -# Copyright 2020 Huawei Technologies Co., Ltd +# Copyright 2020-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. @@ -104,16 +104,14 @@ def retrieve_layer_by_name(model: _Module, layer_name: str): Retrieve the layer in the model by the given layer_name. Args: - model (_Module): model which contains the target layer - layer_name (str): name of target layer + model (Cell): Model which contains the target layer. + layer_name (str): Name of target layer. - Return: - - target_layer (_Module) - - Raise: - ValueError: if module with given layer_name is not found in the model, - raise ValueError. + Returns: + Cell, the target layer. + Raises: + ValueError: If module with given layer_name is not found in the model. """ if not isinstance(layer_name, str): raise TypeError('layer_name should be type of str, but receive {}.' @@ -146,13 +144,14 @@ def retrieve_layer(model: _Module, target_layer: Union[str, _Module] = ''): be raised. Args: - model (_Module): the model to retrieve the target layer - target_layer (Union[str, _Module]): target layer to retrieve. Can be - either string (layer name) or the Cell object. If '' is provided, - the input model will be returned. + model (Cell): Model which contains the target layer. + target_layer (str, Cell): Name of target layer or the target layer instance. + + Returns: + Cell, the target layer. - Return: - target layer (_Module) + Raises: + ValueError: If module with given layer_name is not found in the model. """ if isinstance(target_layer, str): target_layer = retrieve_layer_by_name(model, target_layer) @@ -174,9 +173,7 @@ class ForwardProbe: Probe to capture output of specific layer in a given model. Args: - target_layer (_Module): name of target layer or just provide the - target layer. - + target_layer (str, Cell): Name of target layer or the target layer instance. """ def __init__(self, target_layer: _Module): @@ -204,7 +201,7 @@ class ForwardProbe: def format_tensor_to_ndarray(x: Union[ms.Tensor, np.ndarray]) -> np.ndarray: - """Unify `mindspore.Tensor` and `np.ndarray` to `np.ndarray`. """ + """Unify Tensor and numpy.array to numpy.array.""" if isinstance(x, ms.Tensor): x = x.asnumpy() @@ -231,7 +228,7 @@ def calc_correlation(x: Union[ms.Tensor, np.ndarray], def calc_auc(x: _Array) -> _Array: - """Calculate the Aera under Curve.""" + """Calculate the Area under Curve.""" # take mean for multiple patches if the model is fully convolutional model if len(x.shape) == 4: x = np.mean(np.mean(x, axis=2), axis=3) @@ -242,18 +239,11 @@ def calc_auc(x: _Array) -> _Array: def rank_pixels(inputs: _Array, descending: bool = True) -> _Array: """ - Generate rank order fo every pixel in an 2D array. + Generate rank order for every pixel in an 2D array. The rank order start from 0 to (num_pixel-1). If descending is True, the rank order will generate in a descending order, otherwise in ascending order. - - Example: - x = np.array([[4., 3., 1.], [5., 9., 1.]]) - rank_pixels(x, descending=True) - >> np.array([[2, 3, 4], [1, 0, 5]]) - rank_pixels(x, descending=False) - >> np.array([[3, 2, 0], [4, 5, 1]]) """ if len(inputs.shape) < 2 or len(inputs.shape) > 3: raise ValueError('Only support 2D or 3D inputs currently.') @@ -275,16 +265,15 @@ def resize(inputs: _Tensor, size: Tuple[int, int], mode: str) -> _Tensor: Resize the intermediate layer _attribution to the same size as inputs. Args: - inputs (ms.Tensor): the input tensor to be resized - size (tupleint]): the targeted size resize to - mode (str): the resize mode. Options: 'nearest_neighbor', 'bilinear' + inputs (Tensor): The input tensor to be resized. + size (tuple[int]): The targeted size resize to. + mode (str): The resize mode. Options: 'nearest_neighbor', 'bilinear'. Returns: - outputs (ms.Tensor): the resized tensor. + Tensor, the resized tensor. Raises: - ValueError: the resize mode is not in ['nearest_neighbor', - 'bilinear']. + ValueError: the resize mode is not in ['nearest_neighbor', 'bilinear']. """ h, w = size if mode == 'nearest_neighbor': @@ -305,6 +294,6 @@ def resize(inputs: _Tensor, size: Tuple[int, int], mode: str) -> _Tensor: resized_np = np.transpose(array_lst, [0, 3, 1, 2]) outputs = ms.Tensor(resized_np, inputs.dtype) else: - raise ValueError('Unsupported resize mode {}'.format(mode)) + raise ValueError('Unsupported resize mode {}.'.format(mode)) return outputs diff --git a/mindspore/explainer/benchmark/_attribution/class_sensitivity.py b/mindspore/explainer/benchmark/_attribution/class_sensitivity.py index 8f22571903..83fe41a7af 100644 --- a/mindspore/explainer/benchmark/_attribution/class_sensitivity.py +++ b/mindspore/explainer/benchmark/_attribution/class_sensitivity.py @@ -1,4 +1,4 @@ -# Copyright 2020 Huawei Technologies Co., Ltd +# Copyright 2020-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. @@ -45,19 +45,19 @@ class ClassSensitivity(LabelAgnosticMetric): numpy.ndarray, 1D array of shape :math:`(N,)`, result of class sensitivity evaluated on `explainer`. Examples: + >>> import numpy as np >>> import mindspore as ms >>> from mindspore.explainer.benchmark import ClassSensitivity >>> from mindspore.explainer.explanation import Gradient - >>> from mindspore.train.serialization import load_checkpoint, load_param_into_net - >>> # prepare your network and load the trained checkpoint file, e.g., resnet50. - >>> network = resnet50(10) - >>> param_dict = load_checkpoint("resnet50.ckpt") - >>> load_param_into_net(network, param_dict) + >>> + >>> # The detail of LeNet5 is shown in model_zoo.official.cv.lenet.src.lenet.py + >>> net = LeNet5(10, num_channel=3) >>> # prepare your explainer to be evaluated, e.g., Gradient. - >>> gradient = Gradient(network) - >>> input_x = ms.Tensor(np.random.rand(1, 3, 224, 224), ms.float32) + >>> gradient = Gradient(net) + >>> input_x = ms.Tensor(np.random.rand(1, 3, 32, 32), ms.float32) >>> class_sensitivity = ClassSensitivity() >>> res = class_sensitivity.evaluate(gradient, input_x) + >>> print(res) """ self._check_evaluate_param(explainer, inputs) diff --git a/mindspore/explainer/benchmark/_attribution/faithfulness.py b/mindspore/explainer/benchmark/_attribution/faithfulness.py index 66ebd443a8..5a77cc72cc 100644 --- a/mindspore/explainer/benchmark/_attribution/faithfulness.py +++ b/mindspore/explainer/benchmark/_attribution/faithfulness.py @@ -1,4 +1,4 @@ -# Copyright 2020 Huawei Technologies Co., Ltd +# Copyright 2020-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. @@ -419,16 +419,20 @@ class Faithfulness(LabelSensitiveMetric): >>> import numpy as np >>> import mindspore as ms >>> from mindspore.explainer.explanation import Gradient - >>> # init an explainer with a trained network, e.g., resnet50 - >>> gradient = Gradient(network) - >>> inputs = ms.Tensor(np.random.rand(1, 3, 224, 224), ms.float32) + >>> + >>> + >>> # The detail of LeNet5 is shown in model_zoo.official.cv.lenet.src.lenet.py + >>> net = LeNet5(10, num_channel=3) + >>> gradient = Gradient(net) + >>> inputs = ms.Tensor(np.random.rand(1, 3, 32, 32), ms.float32) >>> targets = 5 >>> # usage 1: input the explainer and the data to be explained, - >>> # calculate the faithfulness with the specified metric + >>> # faithfulness is a Faithfulness instance >>> res = faithfulness.evaluate(gradient, inputs, targets) >>> # usage 2: input the generated saliency map >>> saliency = gradient(inputs, targets) >>> res = faithfulness.evaluate(gradient, inputs, targets, saliency) + >>> print(res) """ self._check_evaluate_param(explainer, inputs, targets, saliency) diff --git a/mindspore/explainer/benchmark/_attribution/localization.py b/mindspore/explainer/benchmark/_attribution/localization.py index bc2776ec9d..f135b84f32 100644 --- a/mindspore/explainer/benchmark/_attribution/localization.py +++ b/mindspore/explainer/benchmark/_attribution/localization.py @@ -1,4 +1,4 @@ -# Copyright 2020 Huawei Technologies Co., Ltd +# Copyright 2020-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. @@ -61,7 +61,7 @@ class Localization(LabelSensitiveMetric): Examples: >>> from mindspore.explainer.benchmark import Localization - >>> num_labels = 100 + >>> num_labels = 10 >>> localization = Localization(num_labels, "PointingGame") """ @@ -113,18 +113,22 @@ class Localization(LabelSensitiveMetric): >>> import numpy as np >>> import mindspore as ms >>> from mindspore.explainer.explanation import Gradient - >>> # init an explainer with a trained network, e.g., resnet50 - >>> gradient = Gradient(network) - >>> inputs = ms.Tensor(np.random.rand(1, 3, 224, 224), ms.float32) - >>> masks = np.zeros([1, 1, 224, 224]) - >>> masks[:, :, 65: 100, 65: 100] = 1 + >>> + >>> # The detail of LeNet5 is shown in model_zoo.official.cv.lenet.src.lenet.py + >>> net = LeNet5(10, num_channel=3) + >>> gradient = Gradient(net) + >>> inputs = ms.Tensor(np.random.rand(1, 3, 32, 32), ms.float32) + >>> masks = np.zeros([1, 1, 32, 32]) + >>> masks[:, :, 10: 20, 10: 20] = 1 >>> targets = 5 >>> # usage 1: input the explainer and the data to be explained, - >>> # calculate the faithfulness with the specified metric + >>> # localization is a Localization instance >>> res = localization.evaluate(gradient, inputs, targets, mask=masks) + >>> print(res) >>> # usage 2: input the generated saliency map >>> saliency = gradient(inputs, targets) >>> res = localization.evaluate(gradient, inputs, targets, saliency, mask=masks) + >>> print(res) """ self._check_evaluate_param_with_mask(explainer, inputs, targets, saliency, mask) diff --git a/mindspore/explainer/benchmark/_attribution/metric.py b/mindspore/explainer/benchmark/_attribution/metric.py index 91b0b69705..b6002508b6 100644 --- a/mindspore/explainer/benchmark/_attribution/metric.py +++ b/mindspore/explainer/benchmark/_attribution/metric.py @@ -69,7 +69,7 @@ class AttributionMetric: if self._explainer is None: self._explainer = explainer elif self._explainer is not explainer: - logger.info('Provided explainer is not the same as previously evaluted one. Please reset the evaluated ' + logger.info('Provided explainer is not the same as previously evaluated one. Please reset the evaluated ' 'results. Previous explainer: %s, current explainer: %s', self._explainer, explainer) self._explainer = explainer @@ -107,7 +107,7 @@ class LabelAgnosticMetric(AttributionMetric): raise TypeError('result should have type of float, ms.Tensor or np.ndarray, but receive %s' % type(result)) def get_results(self): - """Return the gloabl results.""" + """Return the global results.""" return self._global_results.copy() def reset(self): diff --git a/mindspore/explainer/benchmark/_attribution/robustness.py b/mindspore/explainer/benchmark/_attribution/robustness.py index c6226baabe..5618dd8473 100644 --- a/mindspore/explainer/benchmark/_attribution/robustness.py +++ b/mindspore/explainer/benchmark/_attribution/robustness.py @@ -1,4 +1,4 @@ -# Copyright 2020 Huawei Technologies Co., Ltd +# Copyright 2020-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. @@ -80,18 +80,16 @@ class Robustness(LabelSensitiveMetric): >>> import numpy as np >>> import mindspore as ms >>> from mindspore.explainer.explanation import Gradient - >>> from mindspore.explainer.benchmark import Robustness - >>> from mindspore.train.serialization import load_checkpoint, load_param_into_net - >>> # prepare your network and load the trained checkpoint file, e.g., resnet50. - >>> network = resnet50(10) - >>> param_dict = load_checkpoint("resnet50.ckpt") - >>> load_param_into_net(network, param_dict) + >>> + >>> # The detail of LeNet5 is shown in model_zoo.official.cv.lenet.src.lenet.py + >>> net = LeNet5(10, num_channel=3) >>> # prepare your explainer to be evaluated, e.g., Gradient. - >>> gradient = Gradient(network) - >>> input_x = ms.Tensor(np.random.rand(1, 3, 224, 224), ms.float32) + >>> gradient = Gradient(net) + >>> input_x = ms.Tensor(np.random.rand(1, 3, 32, 32), ms.float32) >>> target_label = ms.Tensor([0], ms.int32) >>> # robustness is a Robustness instance >>> res = robustness.evaluate(gradient, input_x, target_label) + >>> print(res) """ self._check_evaluate_param(explainer, inputs, targets, saliency) diff --git a/mindspore/explainer/explanation/_attribution/_backprop/backprop_utils.py b/mindspore/explainer/explanation/_attribution/_backprop/backprop_utils.py index 04396f451b..2c1017bfd3 100644 --- a/mindspore/explainer/explanation/_attribution/_backprop/backprop_utils.py +++ b/mindspore/explainer/explanation/_attribution/_backprop/backprop_utils.py @@ -24,15 +24,15 @@ def get_bp_weights(model, inputs, targets=None, weights=None): Compute the gradient of output w.r.t input. Args: - model (`ms.nn.Cell`): Differentiable black-box model. - inputs (`ms.Tensor`): Input to calculate gradient and explanation. + model (Cell): Differentiable black-box model. + inputs (Tensor): Input to calculate gradient and explanation. targets (int, optional): Target label id specifying which category to compute gradient. Default: None. - weights (`ms.Tensor`, optional): Custom weights for computing gradients. The shape of weights should match the - model outputs. If None is provided, an one-hot weights with one in targets positions will be used instead. + weights (Tensor, optional): Custom weights for computing gradients. The shape of weights should match the model + outputs. If None is provided, an one-hot weights with one in targets positions will be used instead. Default: None. Returns: - saliency map (ms.Tensor): Gradient back-propagated to the input. + Tensor, signal to be back-propagated to the input. """ inputs = unify_inputs(inputs) if targets is None and weights is None: diff --git a/mindspore/explainer/explanation/_attribution/_backprop/gradcam.py b/mindspore/explainer/explanation/_attribution/_backprop/gradcam.py index 2727d68260..f25460539d 100644 --- a/mindspore/explainer/explanation/_attribution/_backprop/gradcam.py +++ b/mindspore/explainer/explanation/_attribution/_backprop/gradcam.py @@ -61,7 +61,7 @@ class GradCAM(IntermediateLayerAttribution): Args: network (Cell): The black-box model to be explained. layer (str, optional): The layer name to generate the explanation, usually chosen as the last convolutional - layer for better practice. If it is '', the explantion will be generated at the input layer. + layer for better practice. If it is '', the explanation will be generated at the input layer. Default: ''. Inputs: @@ -76,18 +76,17 @@ class GradCAM(IntermediateLayerAttribution): >>> import numpy as np >>> import mindspore as ms >>> from mindspore.explainer.explanation import GradCAM - >>> from mindspore.train.serialization import load_checkpoint, load_param_into_net - >>> # load a trained network - >>> net = resnet50(10) - >>> param_dict = load_checkpoint("resnet50.ckpt") - >>> load_param_into_net(net, param_dict) + >>> + >>> # The detail of LeNet5 is shown in model_zoo.official.cv.lenet.src.lenet.py + >>> net = LeNet5(10, num_channel=3) >>> # specify a layer name to generate explanation, usually the layer can be set as the last conv layer. - >>> layer_name = 'layer4' + >>> layer_name = 'conv2' >>> # init GradCAM with a trained network and specify the layer to obtain attribution >>> gradcam = GradCAM(net, layer=layer_name) - >>> inputs = ms.Tensor(np.random.rand(1, 3, 224, 224), ms.float32) + >>> inputs = ms.Tensor(np.random.rand(1, 3, 32, 32), ms.float32) >>> label = 5 >>> saliency = gradcam(inputs, label) + >>> print(saliency.shape) """ def __init__(self, network, layer=""): diff --git a/mindspore/explainer/explanation/_attribution/_backprop/gradient.py b/mindspore/explainer/explanation/_attribution/_backprop/gradient.py index e1e9d99604..e060a61633 100644 --- a/mindspore/explainer/explanation/_attribution/_backprop/gradient.py +++ b/mindspore/explainer/explanation/_attribution/_backprop/gradient.py @@ -15,32 +15,14 @@ """Gradient explainer.""" from copy import deepcopy -from mindspore import nn from mindspore.train._utils import check_value_type -from mindspore.explainer._operators import reshape, sqrt, Tensor +from mindspore.explainer._operators import Tensor from mindspore.explainer._utils import abs_max, unify_inputs, unify_targets from .. import Attribution from .backprop_utils import get_bp_weights, GradNet -def _get_hook(bntype, cache): - """Provide backward hook function for BatchNorm layer in eval mode.""" - var, gamma, eps = cache - if bntype == "2d": - var = reshape(var, (1, -1, 1, 1)) - gamma = reshape(gamma, (1, -1, 1, 1)) - elif bntype == "1d": - var = reshape(var, (1, -1, 1)) - gamma = reshape(gamma, (1, -1, 1)) - - def reset_gradient(_, grad_input, grad_output): - grad_output = grad_input[0] * gamma / sqrt(var + eps) - return grad_output - - return reset_gradient - - class Gradient(Attribution): r""" Provides Gradient explanation method. @@ -72,15 +54,14 @@ class Gradient(Attribution): >>> import numpy as np >>> import mindspore as ms >>> from mindspore.explainer.explanation import Gradient - >>> from mindspore.train.serialization import load_checkpoint, load_param_into_net - >>> # init Gradient with a trained network - >>> net = resnet50(10) # please refer to model_zoo - >>> param_dict = load_checkpoint("resnet50.ckpt") - >>> load_param_into_net(net, param_dict) + >>> + >>> # The detail of LeNet5 is shown in model_zoo.official.cv.lenet.src.lenet.py + >>> net = LeNet5(10, num_channel=3) >>> gradient = Gradient(net) - >>> inputs = ms.Tensor(np.random.rand(1, 3, 224, 224), ms.float32) + >>> inputs = ms.Tensor(np.random.rand(1, 3, 32, 32), ms.float32) >>> label = 5 >>> saliency = gradient(inputs, label) + >>> print(saliency.shape) """ def __init__(self, network): @@ -88,7 +69,6 @@ class Gradient(Attribution): self._backward_model = deepcopy(network) self._backward_model.set_train(False) self._backward_model.set_grad(False) - self._hook_bn() self._grad_net = GradNet(self._backward_model) self._aggregation_fn = abs_max @@ -103,22 +83,19 @@ class Gradient(Attribution): saliency = self._aggregation_fn(gradient) return saliency - def _hook_bn(self): - """Hook BatchNorm layer for `self._backward_model.`""" - for _, cell in self._backward_model.cells_and_names(): - if isinstance(cell, nn.BatchNorm2d): - cache = (cell.moving_variance, cell.gamma, cell.eps) - cell.register_backward_hook(_get_hook("2d", cache=cache)) - elif isinstance(cell, nn.BatchNorm1d): - cache = (cell.moving_variance, cell.gamma, cell.eps) - cell.register_backward_hook(_get_hook("1d", cache=cache)) - @staticmethod def _verify_data(inputs, targets): - """Verify the validity of the parsed inputs.""" + """ + Verify the validity of the parsed inputs. + + Args: + inputs (Tensor): The inputs to be explained. + targets (Tensor, int): The label of interest. It should be a 1D or 0D tensor, or an integer. + If it is a 1D tensor, its length should be the same as `inputs`. + """ check_value_type('inputs', inputs, Tensor) if len(inputs.shape) != 4: - raise ValueError('Argument inputs must be 4D Tensor') + raise ValueError(f'Argument inputs must be 4D Tensor. But got {len(inputs.shape)}D Tensor.') check_value_type('targets', targets, (Tensor, int)) if isinstance(targets, Tensor): if len(targets.shape) > 1 or (len(targets.shape) == 1 and len(targets) != len(inputs)): diff --git a/mindspore/explainer/explanation/_attribution/_backprop/modified_relu.py b/mindspore/explainer/explanation/_attribution/_backprop/modified_relu.py index d0e465c422..a753a4e263 100644 --- a/mindspore/explainer/explanation/_attribution/_backprop/modified_relu.py +++ b/mindspore/explainer/explanation/_attribution/_backprop/modified_relu.py @@ -109,16 +109,14 @@ class Deconvolution(ModifiedReLU): >>> import numpy as np >>> import mindspore as ms >>> from mindspore.explainer.explanation import Deconvolution - >>> from mindspore.train.serialization import load_checkpoint, load_param_into_net - >>> # init Deconvolution with a trained network. - >>> net = resnet50(10) # please refer to model_zoo - >>> param_dict = load_checkpoint("resnet50.ckpt") - >>> load_param_into_net(net, param_dict) + >>> # The detail of LeNet5 is shown in model_zoo.official.cv.lenet.src.lenet.py + >>> net = LeNet5(10, num_channel=3) >>> deconvolution = Deconvolution(net) >>> # parse data and the target label to be explained and get the saliency map - >>> inputs = ms.Tensor(np.random.rand(1, 3, 224, 224), ms.float32) + >>> inputs = ms.Tensor(np.random.rand(1, 3, 32, 32), ms.float32) >>> label = 5 >>> saliency = deconvolution(inputs, label) + >>> print(saliency.shape) """ def __init__(self, network): @@ -154,17 +152,15 @@ class GuidedBackprop(ModifiedReLU): Examples: >>> import numpy as np >>> import mindspore as ms - >>> from mindspore.train.serialization import load_checkpoint, load_param_into_net >>> from mindspore.explainer.explanation import GuidedBackprop - >>> # init GuidedBackprop with a trained network. - >>> net = resnet50(10) # please refer to model_zoo - >>> param_dict = load_checkpoint("resnet50.ckpt") - >>> load_param_into_net(net, param_dict) + >>> # The detail of LeNet5 is shown in model_zoo.official.cv.lenet.src.lenet.py + >>> net = LeNet5(10, num_channel=3) >>> gbp = GuidedBackprop(net) - >>> # parse data and the target label to be explained and get the saliency map - >>> inputs = ms.Tensor(np.random.rand(1, 3, 224, 224), ms.float32) + >>> # feed data and the target label to be explained and get the saliency map + >>> inputs = ms.Tensor(np.random.rand(1, 3, 32, 32), ms.float32) >>> label = 5 >>> saliency = gbp(inputs, label) + >>> print(saliency.shape) """ def __init__(self, network): diff --git a/mindspore/explainer/explanation/_attribution/_perturbation/ablation.py b/mindspore/explainer/explanation/_attribution/_perturbation/ablation.py index 5db62dee5c..531079aecb 100644 --- a/mindspore/explainer/explanation/_attribution/_perturbation/ablation.py +++ b/mindspore/explainer/explanation/_attribution/_perturbation/ablation.py @@ -130,7 +130,7 @@ class AblationWithSaliency(Ablation): Generate mask for perturbations based on given saliency ranks. Args: - saliency (np.ndarray): Perturbing masks will be generated based on the given saliency map. The shape of + saliency (numpy.array): Perturbing masks will be generated based on the given saliency map. The shape of saliency is expected to be: [batch_size, optional(num_channels), *spatial_size]. If multi-channel saliency is provided, an averaged saliency will be taken to calculate pixel order in spatial dimension. num_channels (optional[int]): Number of channels of the input data. In order to match the shape of inputs, @@ -139,7 +139,7 @@ class AblationWithSaliency(Ablation): no channel dimension. Default: None. Return: - mask (np.ndarray): boolen mask for generate perturbations. + numpy.array, boolean masks for perturbation generation. """ batch_size = saliency.shape[0] diff --git a/mindspore/explainer/explanation/_attribution/_perturbation/occlusion.py b/mindspore/explainer/explanation/_attribution/_perturbation/occlusion.py index c1062cf964..1a72098d6f 100644 --- a/mindspore/explainer/explanation/_attribution/_perturbation/occlusion.py +++ b/mindspore/explainer/explanation/_attribution/_perturbation/occlusion.py @@ -1,4 +1,4 @@ -# Copyright 2020 Huawei Technologies Co., Ltd +# Copyright 2020-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. @@ -71,17 +71,15 @@ class Occlusion(PerturbationAttribution): >>> import numpy as np >>> import mindspore as ms >>> from mindspore.explainer.explanation import Occlusion - >>> from mindspore.train.serialization import load_checkpoint, load_param_into_net - >>> # prepare your network and load the trained checkpoint file, e.g., resnet50. - >>> network = resnet50(10) - >>> param_dict = load_checkpoint("resnet50.ckpt") - >>> load_param_into_net(network, param_dict) + >>> # The detail of LeNet5 is shown in model_zoo.official.cv.lenet.src.lenet.py + >>> net = LeNet5(10, num_channel=3) >>> # initialize Occlusion explainer with the pretrained model and activation function >>> activation_fn = ms.nn.Softmax() # softmax layer is applied to transform logits to probabilities - >>> occlusion = Occlusion(network, activation_fn=activation_fn) - >>> input_x = ms.Tensor(np.random.rand(1, 3, 224, 224), ms.float32) + >>> occlusion = Occlusion(net, activation_fn=activation_fn) + >>> input_x = ms.Tensor(np.random.rand(1, 3, 32, 32), ms.float32) >>> label = ms.Tensor([1], ms.int32) >>> saliency = occlusion(input_x, label) + >>> print(saliency.shape) """ def __init__(self, network, activation_fn, perturbation_per_eval=32): diff --git a/mindspore/explainer/explanation/_attribution/_perturbation/rise.py b/mindspore/explainer/explanation/_attribution/_perturbation/rise.py index d407f8fa55..9d6c5ebad5 100644 --- a/mindspore/explainer/explanation/_attribution/_perturbation/rise.py +++ b/mindspore/explainer/explanation/_attribution/_perturbation/rise.py @@ -1,4 +1,4 @@ -# Copyright 2020 Huawei Technologies Co., Ltd +# Copyright 2020-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. @@ -62,23 +62,22 @@ class RISE(PerturbationAttribution): >>> import numpy as np >>> import mindspore as ms >>> from mindspore.explainer.explanation import RISE - >>> from mindspore.nn import Sigmoid - >>> from mindspore.train.serialization import load_checkpoint, load_param_into_net - >>> # prepare your network and load the trained checkpoint file, e.g., resnet50. - >>> network = resnet50(10) - >>> param_dict = load_checkpoint("resnet50.ckpt") - >>> load_param_into_net(network, param_dict) + >>> + >>> # The detail of LeNet5 is shown in model_zoo.official.cv.lenet.src.lenet.py + >>> net = LeNet5(10, num_channel=3) >>> # initialize RISE explainer with the pretrained model and activation function >>> activation_fn = ms.nn.Softmax() # softmax layer is applied to transform logits to probabilities - >>> rise = RISE(network, activation_fn=activation_fn) + >>> rise = RISE(net, activation_fn=activation_fn) >>> # given an instance of RISE, saliency map can be generate - >>> inputs = ms.Tensor(np.random.rand(2, 3, 224, 224), ms.float32) + >>> inputs = ms.Tensor(np.random.rand(2, 3, 32, 32), ms.float32) >>> # when `targets` is an integer >>> targets = 5 >>> saliency = rise(inputs, targets) + >>> print(saliency.shape) >>> # `targets` can also be a 2D tensor >>> targets = ms.Tensor([[5], [1]], ms.int32) >>> saliency = rise(inputs, targets) + >>> print(saliency.shape) """ def __init__(self, @@ -88,7 +87,7 @@ class RISE(PerturbationAttribution): super(RISE, self).__init__(network, activation_fn, perturbation_per_eval) self._num_masks = 6000 # number of masks to be sampled - self._mask_probability = 0.2 # ratio of inputs to be masked + self._mask_probability = 0.5 # ratio of inputs to be masked self._down_sample_size = 10 # the original size of binary masks self._resize_mode = 'bilinear' # mode choice to resize the down-sized binary masks to size of the inputs self._perturbation_mode = 'constant' # setting the perturbed pixels to a constant value @@ -127,7 +126,9 @@ class RISE(PerturbationAttribution): self._num_classes = num_classes # Due to the unsupported Op of slice assignment, we use numpy array here - attr_np = np.zeros(shape=(batch_size, self._num_classes, height, width)) + targets = self._unify_targets(inputs, targets) + + attr_np = np.zeros(shape=(batch_size, targets.shape[1], height, width)) cal_times = math.ceil(self._num_masks / self._perturbation_per_eval) @@ -143,24 +144,21 @@ class RISE(PerturbationAttribution): weights = self._activation_fn(self.network(masked_input)) while len(weights.shape) > 2: weights = op.mean(weights, axis=2) - weights = op.reshape(weights, - (bs, self._num_classes, 1, 1)) - attr_np[idx] += op.summation(weights * masks, axis=0).asnumpy() + weights = np.expand_dims(np.expand_dims(weights.asnumpy()[:, targets[idx]], 2), 3) - attr_np = attr_np / self._num_masks - targets = self._unify_targets(inputs, targets) + attr_np[idx] += np.sum(weights * masks.asnumpy(), axis=0) - attr_classes = [att_i[target] for att_i, target in zip(attr_np, targets)] + attr_np = attr_np / self._num_masks - return op.Tensor(attr_classes, dtype=inputs.dtype) + return op.Tensor(attr_np, dtype=inputs.dtype) @staticmethod def _verify_data(inputs, targets): """Verify the validity of the parsed inputs.""" check_value_type('inputs', inputs, Tensor) if len(inputs.shape) != 4: - raise ValueError('Argument inputs must be 4D Tensor') + raise ValueError(f'Argument inputs must be 4D Tensor, but got {len(inputs.shape)}D Tensor.') check_value_type('targets', targets, (Tensor, int, tuple, list)) if isinstance(targets, Tensor): if len(targets.shape) > 2: @@ -168,7 +166,7 @@ class RISE(PerturbationAttribution): 'But got {}D.'.format(len(targets.shape))) if targets.shape and len(targets) != len(inputs): raise ValueError( - 'If `targets` is a 2D, 1D Tensor, it should have the same length as inputs {}. But got {}'.format( + 'If `targets` is a 2D, 1D Tensor, it should have the same length as inputs {}. But got {}.'.format( len(inputs), len(targets))) @staticmethod