[技术干货] 技术干货 | 预测概率如何随遮挡图像变化?基于MindSpore实现遮挡敏感度

image.png

本文来源于:知乎

作者:李嘉琪

image.png

Occlusion Sensitivity来源于论文《Visualizing and Understanding Convolutional Networks》ECCV’14.(https://ieeexplore.ieee.org/document/8237336) 。

Birju正在阅读一篇关于卷积神经网络可视化技术的论文,在其中他遇到了遮挡敏感的想法。如果您遮挡或遮挡图像的一部分,这将如何影响网络的概率得分?以及结果如何取决于您遮挡的那一部分?
Occlusion Sensitivity 原理定义


在训练一个神经网络进行图片分类的时候,我们希望知道这个模型是能够定位出图片中主要目标的位置来进行分类,或只是通过一些周边的context来进行分类。

通过对图片进行部分遮挡来观察输入修改后的图片后,网络中间层的情况以及预测值的变化。这个可以有助于理解为什么网络会做出某些决策。
所以我们可以得出一个具体的定义:遮挡敏感度(Occlusion Sensitivity)是指给定预测的概率如何随图像被遮挡部分的变化而变化。
Occlusion Sensitivity 结果判定


对于结果,输出概率是一个区域的概率,是某一输出的概率减去被遮挡区域的输出概率。输出图像中的值越高,确定度下降越大,说明遮挡区域在决策过程中更为重要。

image.png

好了,原理已经讲完,话不多说,我们开始上代码。使用的是MindSpore框架实现的代码。

MindSpore代码实现

"""OcclusionSensitivity."""
from collections.abc import Sequence
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
try:
    from tqdm import trange
except (ImportError, AttributeError):
    trange = range
class OcclusionSensitivity(Metric):
    """
    该函数用于计算给定图像模型的遮挡灵敏度。
    """
    def __init__(self, pad_val=0.0, margin=2, n_batch=128, b_box=None):
        super().__init__()
        # 当遮挡图像的一部分时,我们应该在图像中输入哪些值?默认0.0
        self.pad_val = validator.check_value_type("pad_val", pad_val, [float])
        # 围绕要遮挡的体素创建长方体/立方体。默认2
        self.margin = validator.check_value_type("margin", margin, [int, Sequence])
        # 推断前一批中的图像数,默认128
        self.n_batch = validator.check_value_type("n_batch", n_batch, [int])
        # 要在其上执行分析的边界框。输出图像的大小也将匹配。除批次外,所有维度都应有最小值和最大值:`[min1,max1,min2,max2,…]``。
        self.b_box = b_box if b_box is None else validator.check_value_type("b_box", b_box, [list])
        self.clear()
    def clear(self):
        """清除历史数据"""
        self._baseline = 0
        self._sensitivity_im = 0
        self._is_update = False
    def _check_input_bounding_box(self, b_box, im_shape):
        """检查边界框(如果提供)是否如预期的那样。"""
        # 如果未提供边界框,请将“最小值”和“最大值”设置为“无”
        if b_box is None:
            b_box_min = b_box_max = None
        # 已提供边界框
        else:
            # “b_box”中的元素数应是“im_shape”中元素数的两倍
            if len(b_box) != 2 * len(im_shape):
                raise ValueError("Bounding box should contain upper and lower for all dimensions (except batch number)")
 
            # 如果任何最小值或最大值为-ve,则分别将它们设置为0和im_shape-1。
            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
            # 检查所有最大值是否小于im_shape
            if np.any(b_box_max >= im_shape):
                raise ValueError("Max bounding box should be < image size for all values")
            # 检查所有最小值是否大于最大值
            if np.any(b_box_min > b_box_max):
                raise ValueError("Min bounding box should be <= max for all values")
        return b_box_min, b_box_max
    def _append_to_sensitivity_im(self, model, batch_images, batch_ids, sensitivity_im):
        """对于给定数量的图像,得到预测给定标签的概率。附上以前的评估。"""
        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))
    def update(self, *inputs):
        """
        更新输入,包括要使用的模型,预测值和真实值。
        """
        # 校验输入个数是否是3个
        if len(inputs) != 3:
            raise ValueError('occlusion_sensitivity need 3 inputs (model, y_pred, y), but got {}'.format(len(inputs)))
        model = inputs[0]
        # 输入转为numpy
        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("Expected batch size of 1.")
        # 检查输入标签是否如预期的那样。
        if isinstance(label, int):
            label = np.array([[label]], dtype=int)
        # 如果标签是张量,请确保只有一个元素
        elif np.prod(label.shape) != y_pred.shape[0]:
            raise RuntimeError("Expected as many labels as batches.")
        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)
        # 如果未提供边界框,则输出形状与输入形状相同。如果存在边界框,则形状为max-min+1
        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)
        # 在图像上循环1D
        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:
                # 做预测并附加到灵敏度的map上
                sensitivity_im = self._append_to_sensitivity_im(model, batch_images, batch_ids, sensitivity_im)
                # 清空list
                batch_images = []
                batch_ids = []
        # 调整输出图像的大小
        self._sensitivity_im = sensitivity_im.reshape(output_im_shape)
        self._is_update = True
    def eval(self):
        """
         Computes the occlusion_sensitivity.
         Returns:
             A numpy ndarray.
        """
        if not self._is_update:
            raise RuntimeError('Call the update method before calling eval.')
        # 上面输出的概率减去被遮挡区域的输出概率
        sensitivity = self._baseline - np.squeeze(self._sensitivity_im)
        return sensitivit

使用方法如下:

import numpy as np
from mindspore import nn
from mindspore.nn.metrics import OcclusionSensitivity
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 = OcclusionSensitivity()
metric.clear()
metric.update(model, test_data, label)
score = metric.eval()
print(score)
[0.29999995    0.6    1    0.9]
返回结果是一个numpy,对于返回结果的大小:如果没有提供边界框,则大小与输入图像相同。如果使用边界框,则输出图像将被裁剪为该大小。

image.png

image.png

MindSpore官方资料
GitHub : https://github.com/mindspore-ai/mindspore
Gitee:https : //gitee.com/mindspore/mindspore
官方QQ群 : 871543426

长按下方二维码加入MindSpore项目↓

image.png