人工智能

特征可视化技术——CAM

2021-01-22  本文已影响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 的作用:

CAM 的获取步骤

总结 CAM 的获取步骤:

利用 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 的方式有它的局限性:

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

上一篇下一篇

猜你喜欢

热点阅读