首页IT科技iva注意力测试百度百科(vit的cam和注意力图: VIT模型的可解释性)

iva注意力测试百度百科(vit的cam和注意力图: VIT模型的可解释性)

时间2025-06-19 06:52:03分类IT科技浏览5627
导读:grad_cam就是热力图,表示模型对图片的关注部分。越关注就越红。...

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 mask

torch.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版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
linux过滤文件大小(利用linux命令行工具进行文本行过滤的方法)