From: @lixiaohui33 Reviewed-by: Signed-off-by:pull/13966/MERGE
| @@ -0,0 +1,15 @@ | |||
| # 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. | |||
| # ============================================================================ | |||
| """Initialization of tests of explanation related classes.""" | |||
| @@ -0,0 +1,15 @@ | |||
| # 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. | |||
| # ============================================================================ | |||
| """Initialization of tests of mindspore.explainer.benchmark.""" | |||
| @@ -0,0 +1,15 @@ | |||
| # 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. | |||
| # ============================================================================ | |||
| """Initialization of tests of in mindspore.explainer.benchmark.""" | |||
| @@ -0,0 +1,134 @@ | |||
| # 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. | |||
| # ============================================================================ | |||
| """Tests of Localization of mindspore.explainer.benchmark.""" | |||
| from unittest.mock import patch | |||
| import numpy as np | |||
| import pytest | |||
| import mindspore as ms | |||
| from mindspore import context | |||
| from mindspore import nn | |||
| from mindspore.explainer.benchmark import Localization | |||
| from mindspore.explainer.explanation import Gradient | |||
| context.set_context(mode=context.PYNATIVE_MODE) | |||
| H, W = 4, 4 | |||
| SALIENCY = ms.Tensor(np.random.rand(1, 1, H, W), ms.float32) | |||
| class CustomNet(nn.Cell): | |||
| """Simple net for unit test.""" | |||
| def __init__(self): | |||
| super().__init__() | |||
| def construct(self, _): | |||
| return ms.Tensor([[0.1, 0.9]], ms.float32) | |||
| def mock_gradient_call(_, inputs, targets): | |||
| del inputs, targets | |||
| return SALIENCY | |||
| class TestLocalization: | |||
| """Test on Localization.""" | |||
| def setup_method(self): | |||
| self.net = CustomNet() | |||
| self.data = ms.Tensor(np.random.rand(1, 1, H, W), ms.float32) | |||
| self.target = 1 | |||
| masks_np = np.zeros((1, 1, H, W)) | |||
| masks_np[:, :, 1:3, 1:3] = 1 | |||
| self.masks_np = masks_np | |||
| self.masks = ms.Tensor(masks_np, ms.float32) | |||
| self.explainer = Gradient(self.net) | |||
| self.saliency_gt = mock_gradient_call(self.explainer, self.data, self.target) | |||
| self.num_class = 2 | |||
| @pytest.mark.level0 | |||
| @pytest.mark.platform_arm_ascend_training | |||
| @pytest.mark.platform_x86_ascend_training | |||
| @pytest.mark.env_onecard | |||
| def test_pointing_game(self): | |||
| """Test case for `metric="PointingGame"` without input saliency.""" | |||
| with patch.object(Gradient, "__call__", mock_gradient_call): | |||
| max_pos = np.argmax(abs(self.saliency_gt.asnumpy().flatten())) | |||
| x_gt, y_gt = max_pos // W, max_pos % W | |||
| res_gt = self.masks_np[0, 0, x_gt, y_gt] | |||
| pg = Localization(self.num_class, metric="PointingGame") | |||
| pg._metric_arg = 1 # make the tolerance smaller to simplify the test | |||
| res = pg.evaluate(self.explainer, self.data, targets=self.target, mask=self.masks) | |||
| assert np.max(np.abs(np.array([res_gt]) - res)) < 1e-5 | |||
| @pytest.mark.level0 | |||
| @pytest.mark.platform_arm_ascend_training | |||
| @pytest.mark.platform_x86_ascend_training | |||
| @pytest.mark.env_onecard | |||
| def test_iosr(self): | |||
| """Test case for `metric="IoSR"` without input saliency.""" | |||
| with patch.object(Gradient, "__call__", mock_gradient_call): | |||
| threshold = 0.5 | |||
| max_val = np.max(self.saliency_gt.asnumpy()) | |||
| sr = (self.saliency_gt.asnumpy() > (max_val * threshold)).astype(int) | |||
| res_gt = np.sum(sr * self.masks_np) / (np.sum(sr).clip(1e-10)) | |||
| iosr = Localization(self.num_class, metric="IoSR") | |||
| iosr._metric_arg = threshold | |||
| res = iosr.evaluate(self.explainer, self.data, targets=self.target, mask=self.masks) | |||
| assert np.allclose(np.array([res_gt]), res) | |||
| @pytest.mark.level0 | |||
| @pytest.mark.platform_arm_ascend_training | |||
| @pytest.mark.platform_x86_ascend_training | |||
| @pytest.mark.env_onecard | |||
| def test_pointing_game_with_saliency(self): | |||
| """Test metric PointingGame with input saliency.""" | |||
| max_pos = np.argmax(abs(self.saliency_gt.asnumpy().flatten())) | |||
| x_gt, y_gt = max_pos // W, max_pos % W | |||
| res_gt = self.masks_np[0, 0, x_gt, y_gt] | |||
| pg = Localization(self.num_class, metric="PointingGame") | |||
| pg._metric_arg = 1 # make the tolerance smaller to simplify the test | |||
| res = pg.evaluate(self.explainer, self.data, targets=self.target, mask=self.masks, saliency=self.saliency_gt) | |||
| assert np.allclose(np.array([res_gt]), res) | |||
| @pytest.mark.level0 | |||
| @pytest.mark.platform_arm_ascend_training | |||
| @pytest.mark.platform_x86_ascend_training | |||
| @pytest.mark.env_onecard | |||
| def test_iosr_with_saliency(self): | |||
| """Test metric IoSR with input saliency map.""" | |||
| threshold = 0.5 | |||
| max_val = np.max(self.saliency_gt.asnumpy()) | |||
| sr = (self.saliency_gt.asnumpy() > (max_val * threshold)).astype(int) | |||
| res_gt = np.sum(sr * self.masks_np) / (np.sum(sr).clip(1e-10)) | |||
| iosr = Localization(self.num_class, metric="IoSR") | |||
| res = iosr.evaluate(self.explainer, self.data, targets=self.target, mask=self.masks, saliency=self.saliency_gt) | |||
| assert np.allclose(np.array([res_gt]), res) | |||
| @@ -0,0 +1,15 @@ | |||
| # 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. | |||
| # ============================================================================ | |||
| """Initialization of tests of mindspore.explainer.explanation.""" | |||
| @@ -0,0 +1,15 @@ | |||
| # 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. | |||
| # ============================================================================ | |||
| """Initialization of tests of explainers of mindspore.explainer.explanation.""" | |||
| @@ -0,0 +1,15 @@ | |||
| # 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. | |||
| # ============================================================================ | |||
| """Initialization of tests of back-propagation based explainers.""" | |||
| @@ -0,0 +1,104 @@ | |||
| # 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. | |||
| # ============================================================================ | |||
| """Tests of GradCAM of mindspore.explainer.explanation.""" | |||
| from unittest.mock import patch | |||
| import numpy as np | |||
| import pytest | |||
| import mindspore as ms | |||
| from mindspore import context | |||
| import mindspore.ops.operations as op | |||
| from mindspore import nn | |||
| from mindspore.explainer.explanation import GradCAM | |||
| from mindspore.explainer.explanation._attribution._backprop.gradcam import _gradcam_aggregation as aggregation | |||
| context.set_context(mode=context.PYNATIVE_MODE) | |||
| class SimpleAvgLinear(nn.Cell): | |||
| """Simple linear model for the unit test.""" | |||
| def __init__(self): | |||
| super().__init__() | |||
| self.avgpool = nn.AvgPool2d(2, 2) | |||
| self.flatten = nn.Flatten() | |||
| self.fc2 = nn.Dense(4, 3) | |||
| def construct(self, x): | |||
| x = self.avgpool(x) | |||
| x = self.flatten(x) | |||
| return self.fc2(x) | |||
| def resize_fn(attributions, inputs, mode): | |||
| """Mocked resize function for test.""" | |||
| del inputs, mode | |||
| return attributions | |||
| class TestGradCAM: | |||
| """Test GradCAM.""" | |||
| def setup_method(self): | |||
| self.net = SimpleAvgLinear() | |||
| self.data = ms.Tensor(np.random.random(size=(1, 1, 4, 4)), ms.float32) | |||
| @pytest.mark.level0 | |||
| @pytest.mark.platform_arm_ascend_training | |||
| @pytest.mark.platform_x86_ascend_training | |||
| @pytest.mark.env_onecard | |||
| def test_gradcam_attribution(self): | |||
| """Test __call__ method in GradCAM.""" | |||
| with patch.object(GradCAM, "_resize_fn", side_effect=resize_fn): | |||
| layer = "avgpool" | |||
| gradcam = GradCAM(self.net, layer=layer) | |||
| data = ms.Tensor(np.random.random(size=(1, 1, 4, 4)), ms.float32) | |||
| num_classes = 3 | |||
| activation = self.net.avgpool(data) | |||
| reshape = op.Reshape() | |||
| for x in range(num_classes): | |||
| target = ms.Tensor([x], ms.int32) | |||
| attribution = gradcam(data, target) | |||
| # intermediate grad should be reshape of weight of fc2 | |||
| intermediate_grad = self.net.fc2.weight.data[x] | |||
| reshaped = reshape(intermediate_grad, (1, 1, 2, 2)) | |||
| gap_grad = self.net.avgpool(reshaped) | |||
| res = aggregation(gap_grad * activation) | |||
| assert np.allclose(res.asnumpy(), attribution.asnumpy(), atol=1e-5, rtol=1e-3) | |||
| @pytest.mark.level0 | |||
| @pytest.mark.platform_arm_ascend_training | |||
| @pytest.mark.platform_x86_ascend_training | |||
| @pytest.mark.env_onecard | |||
| def test_layer_default(self): | |||
| """Test layer argument of GradCAM.""" | |||
| with patch.object(GradCAM, "_resize_fn", side_effect=resize_fn): | |||
| gradcam = GradCAM(self.net) | |||
| num_classes = 3 | |||
| sum_ = op.ReduceSum() | |||
| for x in range(num_classes): | |||
| target = ms.Tensor([x], ms.int32) | |||
| attribution = gradcam(self.data, target) | |||
| # intermediate_grad should be reshape of weight of fc2 | |||
| intermediate_grad = self.net.fc2.weight.data[x] | |||
| avggrad = float(sum_(intermediate_grad).asnumpy() / 16) | |||
| res = aggregation(avggrad * self.data) | |||
| assert np.allclose(res.asnumpy(), attribution.asnumpy(), atol=1e-5, rtol=1e-3) | |||
| @@ -0,0 +1,74 @@ | |||
| # 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. | |||
| # ============================================================================ | |||
| """Tests of Gradient of mindspore.explainer.explanation.""" | |||
| import numpy as np | |||
| import pytest | |||
| import mindspore as ms | |||
| from mindspore import context | |||
| import mindspore.ops.operations as P | |||
| from mindspore import nn | |||
| from mindspore.explainer.explanation import Gradient | |||
| context.set_context(mode=context.PYNATIVE_MODE) | |||
| class SimpleLinear(nn.Cell): | |||
| """Simple linear model for the unit test.""" | |||
| def __init__(self): | |||
| super().__init__() | |||
| self.relu = nn.ReLU() | |||
| self.flatten = nn.Flatten() | |||
| self.fc2 = nn.Dense(16, 3) | |||
| def construct(self, x): | |||
| x = self.relu(x) | |||
| x = self.flatten(x) | |||
| return self.fc2(x) | |||
| class TestGradient: | |||
| """Test Gradient.""" | |||
| def setup_method(self): | |||
| """Setup the test case.""" | |||
| self.net = SimpleLinear() | |||
| self.relu = P.ReLU() | |||
| self.abs_ = P.Abs() | |||
| @pytest.mark.level0 | |||
| @pytest.mark.platform_arm_ascend_training | |||
| @pytest.mark.platform_x86_ascend_training | |||
| @pytest.mark.env_onecard | |||
| def test_gradient(self): | |||
| """Test gradient __call__ function.""" | |||
| data = (ms.Tensor(np.random.random(size=(1, 1, 4, 4)), | |||
| ms.float32) - 0.5) * 2 | |||
| explainer = Gradient(self.net) | |||
| num_classes = 3 | |||
| reshape = P.Reshape() | |||
| for x in range(num_classes): | |||
| target = ms.Tensor([x], ms.int32) | |||
| attribution = explainer(data, target) | |||
| # intermediate_grad should be reshape of weight of fc2 | |||
| grad = self.net.fc2.weight.data[x] | |||
| grad = self.abs_(reshape(grad, (1, 1, 4, 4)) * (self.abs_(self.relu(data) / data))) | |||
| assert np.allclose(grad.asnumpy(), attribution.asnumpy()) | |||
| @@ -0,0 +1,92 @@ | |||
| # 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. | |||
| # ============================================================================ | |||
| """Tests of Deconvolution and GuidedBackprop of mindspore.explainer.explanation.""" | |||
| import numpy as np | |||
| import pytest | |||
| import mindspore as ms | |||
| import mindspore.ops.operations as P | |||
| from mindspore import context | |||
| from mindspore import nn | |||
| from mindspore.explainer.explanation import Deconvolution, GuidedBackprop | |||
| context.set_context(mode=context.PYNATIVE_MODE) | |||
| class SimpleLinear(nn.Cell): | |||
| """Simple linear model for the unit test.""" | |||
| def __init__(self): | |||
| super().__init__() | |||
| self.relu = nn.ReLU() | |||
| self.flatten = nn.Flatten() | |||
| self.fc2 = nn.Dense(16, 3) | |||
| def construct(self, x): | |||
| x = self.relu(x) | |||
| x = self.flatten(x) | |||
| return self.fc2(x) | |||
| class TestModifiedReLU: | |||
| """Test on modified_relu module, Deconvolution and GuidedBackprop specifically.""" | |||
| def setup_method(self): | |||
| """Setup the test case.""" | |||
| self.net = SimpleLinear() | |||
| self.relu = P.ReLU() | |||
| self.abs_ = P.Abs() | |||
| self.reshape = P.Reshape() | |||
| @pytest.mark.level0 | |||
| @pytest.mark.platform_arm_ascend_training | |||
| @pytest.mark.platform_x86_ascend_training | |||
| @pytest.mark.env_onecard | |||
| def test_deconvolution(self): | |||
| """Test deconvolution attribution.""" | |||
| data = (ms.Tensor(np.random.random(size=(1, 1, 4, 4)), | |||
| ms.float32) - 0.5) * 2 | |||
| deconv = Deconvolution(self.net) | |||
| num_classes = 3 | |||
| for x in range(num_classes): | |||
| target = ms.Tensor([x], ms.int32) | |||
| attribution = deconv(data, target) | |||
| # intermediate_grad should be reshape of weight of fc2 | |||
| grad = self.net.fc2.weight.data[x] | |||
| grad = self.abs_(self.relu(self.reshape(grad, (1, 1, 4, 4)))) | |||
| assert np.allclose(attribution.asnumpy(), grad.asnumpy()) | |||
| def test_guided_backprop(self): | |||
| """Test deconvolution attribution.""" | |||
| data = (ms.Tensor(np.random.random(size=(1, 1, 4, 4)), | |||
| ms.float32) - 0.5) * 2 | |||
| explainer = GuidedBackprop(self.net) | |||
| num_classes = 3 | |||
| for x in range(num_classes): | |||
| target = ms.Tensor([x], ms.int32) | |||
| attribution = explainer(data, target) | |||
| # intermediate_grad should be reshape of weight of fc2 | |||
| grad = self.net.fc2.weight.data[x] | |||
| grad = self.reshape(grad, (1, 1, 4, 4)) | |||
| guided_grad = self.abs_(self.relu(grad * (self.abs_(self.relu(data) / data)))) | |||
| assert np.allclose(guided_grad.asnumpy(), attribution.asnumpy()) | |||
| @@ -0,0 +1,200 @@ | |||
| # 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. | |||
| # ============================================================================ | |||
| """Tests on mindspore.explainer.ImageClassificationRunner.""" | |||
| import os | |||
| import shutil | |||
| from random import random | |||
| from unittest.mock import patch | |||
| import numpy as np | |||
| import pytest | |||
| from PIL import Image | |||
| from mindspore import context | |||
| import mindspore as ms | |||
| import mindspore.nn as nn | |||
| from mindspore.dataset import GeneratorDataset | |||
| from mindspore.explainer import ImageClassificationRunner | |||
| from mindspore.explainer._image_classification_runner import _normalize | |||
| from mindspore.explainer.benchmark import Faithfulness | |||
| from mindspore.explainer.explanation import Gradient | |||
| from mindspore.train.summary import SummaryRecord | |||
| CONST = random() | |||
| NUMDATA = 2 | |||
| context.set_context(mode=context.PYNATIVE_MODE) | |||
| def image_label_bbox_generator(): | |||
| for i in range(NUMDATA): | |||
| image = np.arange(i, i + 16 * 3).reshape((3, 4, 4)) / 50 | |||
| label = np.array(i) | |||
| bbox = np.array([1, 1, 2, 2]) | |||
| yield (image, label, bbox) | |||
| class SimpleNet(nn.Cell): | |||
| """ | |||
| Simple model for the unit test. | |||
| """ | |||
| def __init__(self): | |||
| super(SimpleNet, self).__init__() | |||
| self.reshape = ms.ops.operations.Reshape() | |||
| def construct(self, x): | |||
| prob = ms.Tensor([0.1, 0.9], ms.float32) | |||
| prob = self.reshape(prob, (1, 2)) | |||
| return prob | |||
| class ActivationFn(nn.Cell): | |||
| """ | |||
| Simple activation function for unit test. | |||
| """ | |||
| def __init__(self): | |||
| super(ActivationFn, self).__init__() | |||
| def construct(self, x): | |||
| return x | |||
| def mock_gradient_call(_, inputs, targets): | |||
| return inputs[:, 0:1, :, :] | |||
| def mock_faithfulness_evaluate(_, explainer, inputs, targets, saliency): | |||
| return CONST * targets | |||
| def mock_make_rgba(array): | |||
| return array.asnumpy() | |||
| class TestRunner: | |||
| """Test on Runner.""" | |||
| def setup_method(self): | |||
| self.dataset = GeneratorDataset(image_label_bbox_generator, ["image", "label", "bbox"]) | |||
| self.labels = ["label_{}".format(i) for i in range(2)] | |||
| self.network = SimpleNet() | |||
| self.summary_dir = "summary_test_temp" | |||
| self.explainer = [Gradient(self.network)] | |||
| self.activation_fn = ActivationFn() | |||
| self.benchmarkers = [Faithfulness(num_labels=len(self.labels), | |||
| metric="NaiveFaithfulness", | |||
| activation_fn=self.activation_fn)] | |||
| @pytest.mark.level0 | |||
| @pytest.mark.platform_arm_ascend_training | |||
| @pytest.mark.platform_x86_ascend_training | |||
| @pytest.mark.env_onecard | |||
| def test_run_saliency_no_benchmark(self): | |||
| """Test case when argument benchmarkers is not parsed.""" | |||
| res = [] | |||
| runner = ImageClassificationRunner(summary_dir=self.summary_dir, data=(self.dataset, self.labels), | |||
| network=self.network, activation_fn=self.activation_fn) | |||
| def mock_summary_add_value(_, plugin, name, value): | |||
| res.append((plugin, name, value)) | |||
| with patch.object(SummaryRecord, "add_value", mock_summary_add_value), \ | |||
| patch.object(Gradient, "__call__", mock_gradient_call): | |||
| runner.register_saliency(self.explainer) | |||
| runner.run() | |||
| # test on meta data | |||
| idx = 0 | |||
| assert res[idx][0] == "explainer" | |||
| assert res[idx][1] == "metadata" | |||
| assert res[idx][2].metadata.label == self.labels | |||
| assert res[idx][2].metadata.explain_method == ["Gradient"] | |||
| # test on inference data | |||
| for i in range(NUMDATA): | |||
| idx += 1 | |||
| data_np = np.arange(i, i + 3 * 16).reshape((3, 4, 4)) / 50 | |||
| assert res[idx][0] == "explainer" | |||
| assert res[idx][1] == "sample" | |||
| assert res[idx][2].sample_id == i | |||
| original_path = os.path.join(self.summary_dir, res[idx][2].image_path) | |||
| with open(original_path, "rb") as f: | |||
| image_data = np.asarray(Image.open(f)) / 255.0 | |||
| original_image = _normalize(np.transpose(data_np, [1, 2, 0])) | |||
| assert np.allclose(image_data, original_image, rtol=3e-2, atol=3e-2) | |||
| idx += 1 | |||
| assert res[idx][0] == "explainer" | |||
| assert res[idx][1] == "inference" | |||
| assert res[idx][2].sample_id == i | |||
| assert res[idx][2].ground_truth_label == [i] | |||
| diff = np.array(res[idx][2].inference.ground_truth_prob) - np.array([[0.1, 0.9][i]]) | |||
| assert np.max(np.abs(diff)) < 1e-6 | |||
| assert res[idx][2].inference.predicted_label == [1] | |||
| diff = np.array(res[idx][2].inference.predicted_prob) - np.array([0.9]) | |||
| assert np.max(np.abs(diff)) < 1e-6 | |||
| # test on explanation data | |||
| for i in range(NUMDATA): | |||
| idx += 1 | |||
| data_np = np.arange(i, i + 3 * 16).reshape((3, 4, 4)) / 50 | |||
| saliency_np = data_np[0, :, :] | |||
| assert res[idx][0] == "explainer" | |||
| assert res[idx][1] == "explanation" | |||
| assert res[idx][2].sample_id == i | |||
| assert res[idx][2].explanation[0].explain_method == "Gradient" | |||
| assert res[idx][2].explanation[0].label in [i, 1] | |||
| heatmap_path = os.path.join(self.summary_dir, res[idx][2].explanation[0].heatmap_path) | |||
| assert os.path.exists(heatmap_path) | |||
| with open(heatmap_path, "rb") as f: | |||
| heatmap_data = np.asarray(Image.open(f)) / 255.0 | |||
| heatmap_image = _normalize(saliency_np) | |||
| assert np.allclose(heatmap_data, heatmap_image, atol=3e-2, rtol=3e-2) | |||
| @pytest.mark.level0 | |||
| @pytest.mark.platform_arm_ascend_training | |||
| @pytest.mark.platform_x86_ascend_training | |||
| @pytest.mark.env_onecard | |||
| def test_run_saliency_with_benchmark(self): | |||
| """Test case when argument benchmarkers is parsed.""" | |||
| res = [] | |||
| def mock_summary_add_value(_, plugin, name, value): | |||
| res.append((plugin, name, value)) | |||
| runner = ImageClassificationRunner(summary_dir=self.summary_dir, data=(self.dataset, self.labels), | |||
| network=self.network, activation_fn=self.activation_fn) | |||
| with patch.object(SummaryRecord, "add_value", mock_summary_add_value), \ | |||
| patch.object(Gradient, "__call__", mock_gradient_call), \ | |||
| patch.object(Faithfulness, "evaluate", mock_faithfulness_evaluate): | |||
| runner.register_saliency(self.explainer, self.benchmarkers) | |||
| runner.run() | |||
| idx = 3 * NUMDATA + 1 # start index of benchmark data | |||
| assert res[idx][0] == "explainer" | |||
| assert res[idx][1] == "benchmark" | |||
| assert abs(res[idx][2].benchmark[0].total_score - 2 / 3 * CONST) < 1e-6 | |||
| diff = np.array(res[idx][2].benchmark[0].label_score) - np.array([i * CONST for i in range(NUMDATA)]) | |||
| assert np.max(np.abs(diff)) < 1e-6 | |||
| def teardown_method(self): | |||
| shutil.rmtree(self.summary_dir) | |||
| @@ -0,0 +1,119 @@ | |||
| # 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. | |||
| # ============================================================================ | |||
| """Unit test on mindspore.explainer._utils.""" | |||
| import numpy as np | |||
| import pytest | |||
| import mindspore as ms | |||
| import mindspore.nn as nn | |||
| from mindspore.explainer._utils import ( | |||
| ForwardProbe, | |||
| rank_pixels, | |||
| retrieve_layer, | |||
| retrieve_layer_by_name) | |||
| from mindspore.explainer.explanation._attribution._backprop.backprop_utils import GradNet, get_bp_weights | |||
| class CustomNet(nn.Cell): | |||
| """Simple net for test.""" | |||
| def __init__(self): | |||
| super(CustomNet, self).__init__() | |||
| self.fc1 = nn.Dense(10, 10) | |||
| self.fc2 = nn.Dense(10, 10) | |||
| self.fc3 = nn.Dense(10, 10) | |||
| self.fc4 = nn.Dense(10, 10) | |||
| def construct(self, inputs): | |||
| out = self.fc1(inputs) | |||
| out = self.fc2(out) | |||
| out = self.fc3(out) | |||
| out = self.fc4(out) | |||
| return out | |||
| @pytest.mark.level0 | |||
| @pytest.mark.platform_arm_ascend_training | |||
| @pytest.mark.platform_x86_ascend_training | |||
| @pytest.mark.env_onecard | |||
| def test_rank_pixels(): | |||
| """Test on rank_pixels.""" | |||
| saliency = np.array([[4., 3., 1.], [5., 9., 1.]]) | |||
| descending_target = np.array([[0, 1, 2], [1, 0, 2]]) | |||
| ascending_target = np.array([[2, 1, 0], [1, 2, 0]]) | |||
| descending_rank = rank_pixels(saliency) | |||
| ascending_rank = rank_pixels(saliency, descending=False) | |||
| assert (descending_rank - descending_target).any() == 0 | |||
| assert (ascending_rank - ascending_target).any() == 0 | |||
| @pytest.mark.level0 | |||
| @pytest.mark.platform_arm_ascend_training | |||
| @pytest.mark.platform_x86_ascend_training | |||
| @pytest.mark.env_onecard | |||
| def test_retrieve_layer_by_name(): | |||
| """Test on rank_pixels.""" | |||
| model = CustomNet() | |||
| target_layer_name = 'fc3' | |||
| target_layer = retrieve_layer_by_name(model, target_layer_name) | |||
| assert target_layer is model.fc3 | |||
| @pytest.mark.level0 | |||
| @pytest.mark.platform_arm_ascend_training | |||
| @pytest.mark.platform_x86_ascend_training | |||
| @pytest.mark.env_onecard | |||
| def test_retrieve_layer_by_name_no_name(): | |||
| """Test on retrieve layer.""" | |||
| model = CustomNet() | |||
| target_layer = retrieve_layer_by_name(model, '') | |||
| assert target_layer is model | |||
| @pytest.mark.level0 | |||
| @pytest.mark.platform_arm_ascend_training | |||
| @pytest.mark.platform_x86_ascend_training | |||
| @pytest.mark.env_onecard | |||
| def test_forward_probe(): | |||
| """Test case for ForwardProbe.""" | |||
| model = CustomNet() | |||
| model.set_grad() | |||
| inputs = np.random.random((1, 10)) | |||
| inputs = ms.Tensor(inputs, ms.float32) | |||
| gt_activation = model.fc3(model.fc2(model.fc1(inputs))).asnumpy() | |||
| targets = 1 | |||
| weights = get_bp_weights(model, inputs, targets=targets) | |||
| gradnet = GradNet(model) | |||
| grad_before_probe = gradnet(inputs, weights).asnumpy() | |||
| # Probe forward tensor | |||
| saliency_layer = retrieve_layer(model, 'fc3') | |||
| with ForwardProbe(saliency_layer) as probe: | |||
| grad_after_probe = gradnet(inputs, weights).asnumpy() | |||
| activation = probe.value.asnumpy() | |||
| grad_after_unprobe = gradnet(inputs, weights).asnumpy() | |||
| assert np.array_equal(gt_activation, activation) | |||
| assert np.array_equal(grad_before_probe, grad_after_probe) | |||
| assert np.array_equal(grad_before_probe, grad_after_unprobe) | |||
| assert probe.value is None | |||