iva注意力测试百度百科(vit的cam和注意力图: VIT模型的可解释性)
grad_cam就是热力图 ,表示模型对图片的关注部分 。越关注就越红 。
图是用大佬的 ,原理大家也请看大佬的博客。
Grad-CAM简介_太阳花的小绿豆的博客-CSDN博客_grad-cam
不得不提一句的是,在CNN中 ,是将多个通道的特征图加权起来 。 就是B*H*W*C在C这个维度上加权 。 而在vit中计算gradcam时 ,是将多个patch的特征图加权起来 。 也就是B*(L-1)*h*w在L这个维度上加权起来 。 小写的h和w 表示是一个小patch的长宽 。L就是token的长度了 ,减1 是减的clstoken 。权重都是根据分配给各自的梯度决定的 。
这样看来其实vit用gradcam的解释性可能没那么的强 。因为把patch的特征图resize到224*224 ,这样于情于理 都感觉 没那么的合适 。 还有一种是官方的ViT用的方法Attention Rollout。就是按照注意力权重来给颜色 , 感觉合理上许多 。我们直接通过代码理解 。
https://github.com/jacobgil/vit-explain 代码地址在这里。
拿到后 直接运行vit_explain 如果是在服务器上运行的 就把cv2 换成plt 。 换的时候注意转通道 。
b, g, r = cv2.split(mask) mask = cv2.merge((r, g, b)) plt.imshow(mask) plt.show()要不然画出来 图是反着来的。
根据是否指定图片所属的类 ,分为带grad的atten加权和不带grad的att加权 。 我们进上面看 。
class VITAttentionRollout: def __init__(self, model, attention_layer_name=attn_drop, head_fusion="mean", discard_ratio=0.9): self.model = model self.head_fusion = head_fusion self.discard_ratio = discard_ratio for name, module in self.model.named_modules(): if attention_layer_name in name: module.register_forward_hook(self.get_attention) self.attentions = [] def get_attention(self, module, input, output): self.attentions.append(output.cpu()) def __call__(self, input_tensor): self.attentions = [] with torch.no_grad(): output = self.model(input_tensor) return rollout(self.attentions, self.discard_ratio, self.head_fusion)这里面有很多我平时很少用的代码写法 ,正好学学 。
第一个是__call__ 这个方法 ,可以用声明的类直接当作函数名字使用 。像下图 上面定义 下面就当作函数了 。
第二个是hook方法 ,hook 钩子对吧,下面这个就是就是在模型前向过程中 ,把我们想要的东西勾出来 。 当然是只在我们想要的层添加hook 。 钩子勾到后 ,用定义的函数来处理 。这里处理方式是把输出记录下来 。
for name, module in self.model.named_modules(): if attention_layer_name in name: module.register_forward_hook(self.get_attention)注意
attention_layer_name=attn_drop也就是说 hook提取的是atten矩阵drop层的输出。我们找到这个层,会发现是q和k的乘积那里 。
当然 ,这里的模型在eval ,模式下, drop率都是0 ,所以输入输出都是一样的 。记录输出即可。
也就是第一步 提取了attention矩阵 , 下面用rollout函数来计算热力图 。 我们进去看看 。
with torch.no_grad(): for attention in attentions: if head_fusion == "mean": attention_heads_fused = attention.mean(axis=1) elif head_fusion == "max": attention_heads_fused = attention.max(axis=1)[0] elif head_fusion == "min": attention_heads_fused = attention.min(axis=1)[0] else: raise "Attention head fusion type Not supported" # Drop the lowest attentions, but # dont drop the class token flat = attention_heads_fused.view(attention_heads_fused.size(0), -1) _, indices = flat.topk(int(flat.size(-1)*discard_ratio), -1, False) indices = indices[indices != 0] flat[0, indices] = 0 I = torch.eye(attention_heads_fused.size(-1)) a = (attention_heads_fused + 1.0*I)/2 a = a / a.sum(dim=-1) result = torch.matmul(a, result) # Look at the total attention between the class token, # and the image patches mask = result[0, 0 , 1 :] # In case of 224x224 image, this brings us from 196 to 14 width = int(mask.size(-1)**0.5) mask = mask.reshape(width, width).numpy() mask = mask / np.max(mask) return masktorch.eye 创建对角矩阵 ,长宽是token的长度 ,197.
然后选一个pooling方式 ,这里是max。这个pooling是在图片通道间pooling 彩图就是3通道 。
之后去掉一定比例atten值比较小的 ,默认是0.9的比例 。 令人惊讶的是 ,当把flat中的小值置为0时 , attention_heads_fused 中的对应值也会变为0. 之后是归一化的过程 。 对于多层atten 就通过matmul的方式叠加上去 。 也就是相乘 。
mask = result[0, 0 , 1 :] # In case of 224x224 image, this brings us from 196 to 14 width = int(mask.size(-1)**0.5) mask = mask.reshape(width, width).numpy() mask = mask / np.max(mask)像分类一样 , 取cls的atten加权值 。 这个相当于从cls 看其他token的权重,也是一种总览 。 然后归一化 ,
np_img = np.array(img)[:, :, ::-1] mask = cv2.resize(mask, (np_img.shape[1], np_img.shape[0])) mask = show_mask_on_image(np_img, mask) # cv2.imshow("Input Image", np_img) b, g, r = cv2.split(mask) mask = cv2.merge((r, g, b)) plt.imshow(mask) plt.show()画图 得到结果
为什么是在看狗嘞? 可能这就是没有梯度的坏处吧 。 没法控制?
如果用自己的模型画:
1 修改载入模型
2 修改图地址
3 看是否需要修改层名字 。
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!