【Datawhale可解释性机器学习笔记】CAM

举报
JeffDing 发表于 2022/12/17 11:00:21 2022/12/17
【摘要】 ## CAM是什么论文:[Learning Deep Features for Discriminative Localization](https://arxiv.org/pdf/1512.04150.pdf)CAM全称Class Activation Mapping,既类别激活映射图,也被称为类别热力图、显著性图等。是一张和原始图片等同大小图,该图片上每个位置的像素取值范围从0到1,一般...

## CAM是什么
论文:[Learning Deep Features for Discriminative Localization](https://arxiv.org/pdf/1512.04150.pdf)

CAM全称Class Activation Mapping,既类别激活映射图,也被称为类别热力图、显著性图等。是一张和原始图片等同大小图,该图片上每个位置的像素取值范围从0到1,一般用0到255的灰度图表示。可以理解为对预测输出的贡献分布,分数越高的地方表示原始图片对应区域对网络的响应越高、贡献越大。

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

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

## CAM的作用
1. 有助于理解和分析神经网络的工作原理及决策过程,进而去更好地选择或设计网络。例如对于分类网络,如果参考CAM相当于除了分类accuracy以外,对网络又提出了更高的要求:不但要求预测准确率高,还需要网络提取到我们需要的特征。下图可以看出,不同网络对相同的数据的CAM是有较为明显的差异。当然即便是同一个网络,不同训练过程也会导致CAM有很大的差异。
2. 利用可视化的信息引导网络更好的学习,例如可以利用CAM信息通过"擦除"或""裁剪""的方式对数据进行增强
3. 利用CAM作为原始的种子,进行弱监督语义分割或弱监督定位。既然CAM能够cover到目标物体,所以可以仅利用分类标注来完成语义分割或目标检测任务,极大程度上降低了标注的工作量,但这对CAM的要求更高。一般情况下,分类网络只会提取到最具有判别性的特征。所以也有很多做法来提高分类网络的CAM精准度

## 代码示例
### 可视化卷及神经网络热力图(keras)
```python
#使用预训练的VGG网络来演示这种方法
from keras.applications.vgg16 import VGG16

K.clear_session()
model = VGG16(weights='imagenet')


from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input, decode_predictions
import numpy as np

img_path = '/Users/fchollet/Downloads/creative_commons_elephant.jpg'
img = image.load_img(img_path, target_size=(224, 224))   # 大小为224*224的Python图像库图像
x = image.img_to_array(img)  # 形状为(224, 224, 3)的float32格式Numpy数组
x = np.expand_dims(x, axis=0)  # 添加一个维度,将数组转化为(1, 224, 224, 3)的形状批量
x = preprocess_input(x)   #按批量进行预处理(按通道颜色进行标准化)

preds = model.predict(x)
print('Predicted:', decode_predictions(preds, top=3)[0])

#使用Grad-CAM算法展示那些部分最像非洲象
african_elephant_output = model.output[:, 386]   # 预测向量中的非洲象元素
last_conv_layer = model.get_layer('block5_conv3')  # block5_conv3层的输出特征图,它是VGG16的最后一个卷积层
grads = K.gradients(african_elephant_output, last_conv_layer.output)[0]   # 非洲象类别相对于block5_conv3输出特征图的梯度
pooled_grads = K.mean(grads, axis=(0, 1, 2))   # 形状是(512, )的向量,每个元素是特定特征图通道的梯度平均大小
iterate = K.function([model.input], [pooled_grads, last_conv_layer.output[0]])  # 这个函数允许我们获取刚刚定义量的值:对于给定样本图像,pooled_grads和block5_conv3层的输出特征图
pooled_grads_value, conv_layer_output_value = iterate([x])  # 给我们两个大象样本图像,这两个量都是Numpy数组
for i in range(512):
    conv_layer_output_value[:, :, i] *= pooled_grads_value[i]  # 将特征图数组的每个通道乘以这个通道对大象类别重要程度
heatmap = np.mean(conv_layer_output_value, axis=-1)  # 得到的特征图的逐通道的平均值即为类激活的热力图
heatmap = np.maximum(heatmap, 0)
heatmap /= np.max(heatmap)
plt.matshow(heatmap)
plt.show()
```
用OpenCV来生成一张图像,将原始图像叠加在刚刚得到的热力图上
```python
import cv2
img = cv2.imread(img_path)  # 用cv2加载原始图像
heatmap = cv2.resize(heatmap, (img.shape[1], img.shape[0]))  # 将热力图的大小调整为与原始图像相同
heatmap = np.uint8(255 * heatmap)  # 将热力图转换为RGB格式
heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)   # 将热力图应用于原始图像
superimposed_img = heatmap * 0.4 + img    # 这里的0.4是热力图强度因子
cv2.imwrite('elephant_cam.jpg', superimposed_img)   # 将图像保存到硬盘
```

### CAM方法获取显著图:基于pytorch的实现
```python
# simple implementation of CAM in PyTorch for the networks such as ResNet, DenseNet, SqueezeNet, Inception
# last update by BZ, June 30, 2021

import io
from PIL import Image
from torchvision import models, transforms
from torch.autograd import Variable
from torch.nn import functional as F
import numpy as np
import cv2
import json

# input image
LABELS_file = 'imagenet-simple-labels.json'
image_file = 'test.jpg'

# networks such as googlenet, resnet, densenet already use global average pooling at the end, so CAM could be used directly.
model_id = 1
if model_id == 1:
    net = models.squeezenet1_1(pretrained=True)
    finalconv_name = 'features' # this is the last conv layer of the network
elif model_id == 2:
    net = models.resnet18(pretrained=True)
    finalconv_name = 'layer4'
elif model_id == 3:
    net = models.densenet161(pretrained=True)
    finalconv_name = 'features'

net.eval()

# hook the feature extractor
features_blobs = []
def hook_feature(module, input, output):
    features_blobs.append(output.data.cpu().numpy())

net._modules.get(finalconv_name).register_forward_hook(hook_feature)

# get the softmax weight
params = list(net.parameters())
weight_softmax = np.squeeze(params[-2].data.numpy())

def returnCAM(feature_conv, weight_softmax, class_idx):
    # generate the class activation maps upsample to 256x256
    size_upsample = (256, 256)
    bz, nc, h, w = feature_conv.shape
    output_cam = []
    for idx in class_idx:
        cam = weight_softmax[idx].dot(feature_conv.reshape((nc, h*w)))
        cam = cam.reshape(h, w)
        cam = cam - np.min(cam)
        cam_img = cam / np.max(cam)
        cam_img = np.uint8(255 * cam_img)
        output_cam.append(cv2.resize(cam_img, size_upsample))
    return output_cam


normalize = transforms.Normalize(
   mean=[0.485, 0.456, 0.406],
   std=[0.229, 0.224, 0.225]
)
preprocess = transforms.Compose([
   transforms.Resize((224,224)),
   transforms.ToTensor(),
   normalize
])

# load test image
img_pil = Image.open(image_file)
img_tensor = preprocess(img_pil)
img_variable = Variable(img_tensor.unsqueeze(0))
logit = net(img_variable)

# load the imagenet category list
with open(LABELS_file) as f:
    classes = json.load(f)


h_x = F.softmax(logit, dim=1).data.squeeze()
probs, idx = h_x.sort(0, True)
probs = probs.numpy()
idx = idx.numpy()

# output the prediction
for i in range(0, 5):
    print('{:.3f} -> {}'.format(probs[i], classes[idx[i]]))

# generate class activation mapping for the top1 prediction
CAMs = returnCAM(features_blobs[0], weight_softmax, [idx[0]])

# render the CAM and output
print('output CAM.jpg for the top1 prediction: %s'%classes[idx[0]])
img = cv2.imread('test.jpg')
height, width, _ = img.shape
heatmap = cv2.applyColorMap(cv2.resize(CAMs[0],(width, height)), cv2.COLORMAP_JET)
result = heatmap * 0.3 + img * 0.5
cv2.imwrite('CAM.jpg', result)
```

## 参考资料
[万字长文:特征可视化技术(CAM)](https://zhuanlan.zhihu.com/p/269702192)
[特征可视化技术——CAM](https://www.jianshu.com/p/41d85603ccc1)

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。