美文网首页人工智能
特征可视化技术——CAM

特征可视化技术——CAM

作者: 星光下的胖子 | 来源:发表于2021-01-22 16:33 被阅读0次

    前言

    深度学习是一个“黑盒系统”,它通过“end-end”的方式来工作,图像数据作为输入,输出类别标签、回归值等信息,中间过程不可见。如何才能打开“黑盒”,一探究竟,让“黑盒”变成“灰盒”,甚至“白盒”?于是有了“深度学习可解释性”这一研究领域,而 CAM 技术就是其中之一,其利用“特征可视化”来探究深度卷积神经网络的工作机制和判断依据。

    CAM的概念

    CAM(Class Activation Mapping,类别激活映射图),亦称为类别热力图或显著性图。它的大小与原图一致,像素值表示原始图片的对应区域对预测输出的影响程度,值越大贡献越大。

    像素值的取值范围从0到1,一般也用0~255的灰度图表示。示例:

    cam_gray = np.uint8(255 * cam)  # 转换为0~255的灰度图
    

    为了更直观的表达,可更进一步将灰度图转换为彩色图。示例:

    cam_color = cv2.applyColorMap(cam_gray, cv2.COLORMAP_HSV)  # 转换成伪彩色图
    

    一般用热力图和原图叠加的形式进行呈现,如下所示:

    CAM 的作用

    CAM 的作用:

    • 1)有助于理解和分析神经网络的工作原理及决策过程,进而更好地选择或设计网络。
      不同模型对同一张图的 CAM 是有差异的,同一个模型的不同训练过程的 CAM 也是有差异的。参考 CAM 我们可以对设计的网络提出更高的要求:不但关注预测准确率,还可关注网络是否提取到我们需要的特征。例如对于两个模型 A、B,若它们的 accuracy 一致,但从 CAM 上看到 A 相对 B 提取更多所需的特征(高亮区域更集中在目标附近),那么我们判断模型 A 更好一些。
    • 2)利用可视化的信息引导网络更好的学习。
      例如可利用 CAM 信息通过"擦除"或"裁剪"的方式对数据进行增强。
    • 3)利用 CAM 作为原始的种子,进行弱监督语义分割或弱监督定位。
      由于 CAM 能够覆盖到目标物体,因此仅利用分类标注也可用来完成语义分割或目标检测任务,这极大程度降低了标注的工作量。当然,对分类网络的 CAM 精度的要求很高,不然误差相对较大。

    CAM 的获取步骤

    总结 CAM 的获取步骤:

    • 1)提取需要可视化的特征图,例如尺寸为512*7*7的张量;
    • 2)获取该张量的每个 channel 的权重,即长度为512的向量;
    • 3)通过线性融合的方式,将该张量在 channel 维度上加权求和,获取尺寸为7*7的 map;
    • 4)对该 map 进行归一化,并通过插值的方式 resize 成和原图一样的尺寸。

    利用 GAP 获取 CAM

    GAP(Global Average Pooling,全局平均池化操作),可直接将 C*W*H 的特征图转换成 C*1*1。分别选用 resnet18、resnet50、densenet121 三种不同的模型,结合 hook 机制获取 CAM:

    import numpy as np
    from torchvision import models, transforms
    import cv2
    from PIL import Image
    from torch.nn import functional as F
    
    # 定义预训练模型: resnet18、resnet50、densenet121
    resnet18 = models.resnet18(pretrained=True)
    resnet50 = models.resnet50(pretrained=True)
    densenet121 = models.densenet121(pretrained=True)
    resnet18.eval()
    resnet50.eval()
    densenet121.eval()
    
    # 图片数据转换
    image_transform = transforms.Compose([
        # 将输入图片resize成统一尺寸
        transforms.Resize([224, 224]),
        # 将PIL Image或numpy.ndarray转换为tensor,并除255归一化到[0,1]之间
        transforms.ToTensor(),
        # 标准化处理-->转换为标准正太分布,使模型更容易收敛
        transforms.Normalize(
            mean=[0.485, 0.456, 0.406],
            std=[0.229, 0.224, 0.225]
        )
    ])
    
    # =====注册hook start=====
    feature_data = []
    
    
    def feature_hook(model, input, output):
        feature_data.append(output.data.numpy())
    
    
    resnet18._modules.get('layer4').register_forward_hook(feature_hook)
    resnet50._modules.get('layer4').register_forward_hook(feature_hook)
    densenet121._modules.get('features').register_forward_hook(feature_hook)
    # =====注册hook end=====
    
    # 获取fc层的权重
    fc_weights_resnet18 = resnet18._modules.get('fc').weight.data.numpy()
    fc_weights_resnet50 = resnet50._modules.get('fc').weight.data.numpy()
    fc_weights_densenet121 = densenet121._modules.get('classifier').weight.data.numpy()
    
    # 获取预测类别id
    image = image_transform(Image.open("cat.jpg")).unsqueeze(0)
    out_resnet18 = resnet18(image)
    out_resnet50 = resnet50(image)
    out_densenet121 = densenet121(image)
    predict_classes_id_resnet18 = np.argmax(F.softmax(out_resnet18, dim=1).data.numpy())
    predict_classes_id_resnet50 = np.argmax(F.softmax(out_resnet50, dim=1).data.numpy())
    predict_classes_id_densenet121 = np.argmax(F.softmax(out_densenet121, dim=1).data.numpy())
    
    
    # =====获取CAM start=====
    def makeCAM(feature, weights, classes_id):
        print(feature.shape, weights.shape, classes_id)
        # batchsize, C, h, w
        bz, nc, h, w = feature.shape
        # (512,) @ (512, 7*7) = (49,)
        cam = weights[classes_id].dot(feature.reshape(nc, h * w))
        cam = cam.reshape(h, w)  # (7, 7)
        # 归一化到[0, 1]之间
        cam = (cam - cam.min()) / (cam.max() - cam.min())
        # 转换为0~255的灰度图
        cam_gray = np.uint8(255 * cam)
        # 最后,上采样操作,与网络输入的尺寸一致,并返回
        return cv2.resize(cam_gray, (224, 224))
    
    
    cam_gray_resnet18 = makeCAM(feature_data[0], fc_weights_resnet18, predict_classes_id_resnet18)
    cam_gray_resnet50 = makeCAM(feature_data[1], fc_weights_resnet50, predict_classes_id_resnet50)
    cam_gray_densenet121 = makeCAM(feature_data[2], fc_weights_densenet121, predict_classes_id_densenet121)
    # =====获取CAM start=====
    
    # =====叠加CAM和原图,并保存图片=====
    # 1)读取原图
    src_image = cv2.imread("cat.jpg")
    h, w, _ = src_image.shape
    # 2)cam转换成与原图大小一致的彩色度(cv2.COLORMAP_HSV为彩色图的其中一种类型)
    cam_color_resnet18 = cv2.applyColorMap(cv2.resize(cam_gray_resnet18, (w, h)),
                                           cv2.COLORMAP_HSV)
    cam_color_resnet50 = cv2.applyColorMap(cv2.resize(cam_gray_resnet50, (w, h)),
                                           cv2.COLORMAP_HSV)
    cam_color_densenet121 = cv2.applyColorMap(cv2.resize(cam_gray_densenet121, (w, h)),
                                              cv2.COLORMAP_HSV)
    # 3)合并cam和原图,并保存
    cam_resnet18 = src_image * 0.5 + cam_color_resnet18 * 0.5
    cam_resnet50 = src_image * 0.5 + cam_color_resnet50 * 0.5
    cam_densenet121 = src_image * 0.5 + cam_color_densenet121 * 0.5
    cam_hstack = np.hstack((src_image, cam_resnet18, cam_resnet50, cam_densenet121))
    cv2.imwrite("cam_hstack.jpg", cam_hstack)
    # 可视化
    Image.open("cam_hstack.jpg").show()
    

    最终的可视化效果如下图所示(从左到右依次是原图、cam_resnet18、cam_resnet50、cam_densenet121):

    可见,不同的模型得到的 CAM 效果不同,从覆盖区域来看,densenet121 的效果相对更好一些。

    更通用的获取 CAM 的方法——Grad-CAM

    利用 GAP 获取 CAM 的方式有它的局限性:

    • 1)要求模型必须有 GAP 层;
    • 2)只能提取最后一层特征图的热力图。

    Grad-CAM 是为了克服上面的缺陷而提出的,Grad-CAM 可适用于非 GAP 的网络结构,并且可提取任意层的热力图。

    相关文章

      网友评论

        本文标题:特征可视化技术——CAM

        本文链接:https://www.haomeiwen.com/subject/twfdzktx.html