今天解读另一个部分,之前(2)里有说过把hook获取到的变量进行转换再存到cpu,那么是怎么转换的呢?我们一起看一下(2)讲过的class ActivationsAndGradients:
def save_activation(self, module, input, output):
activation = output
if self.reshape_transform is not None:
activation = self.reshape_transform(activation)
self.activations.append(activation.cpu().detach())
当存在reshape_transform的时候调用他进行转换
reshape_transform
代码中如下定义
reshape_transform=ReshapeTransform(model)
很简单哇,就是一个ReshapeTransform的实例化
class ReshapeTransform
class ReshapeTransform:
def __init__(self, model):
input_size = model.patch_embed.img_size
patch_size = model.patch_embed.patch_size
self.h = input_size[0] // patch_size[0]
self.w = input_size[1] // patch_size[1]
def __call__(self, x):
# remove cls token and reshape
# [batch_size, num_tokens, token_dim]
result = x[:, 1:, :].reshape(x.size(0),
self.h,
self.w,
x.size(2))
# Bring the channels to the first dimension,
# like in CNNs.
# [batch_size, H, W, C] -> [batch, C, H, W]
result = result.permute(0, 3, 1, 2)
return result
代码很明了,主要作用是对输出tensor进行形状变换
init初始化
类的初始化函数 __init__
接收一个ViT的model模型,
根据模型的 patch_embed.img_size
以及 patch_embed.patch_size
属性计算
目的是得到后面需要 reshape 成的数量,即 self.h
和 self.w
。
call方法
在 __call__
方法中,对输入的张量 x
进行 reshape 操作,使得token化的特征图变成二维的类cnn的特征图
具体来说,先通过除去第一列(代表 CLS token)的方式移除这个 token;
并将剩余的L个 tokens 按 self.h
和 self.w
铺在一个二维图上,将多个 patch 组合在一起形成完整的图像表示;
最后再permute把[batch_size, H, W, C] -> [batch, C, H, W],类似于卷积操作之前的 CNN 格式,返回变换后的结果张量,便于进一步处理和分析图像信息。
之前看到博客vit的cam和注意力图: VIT模型的可解释性_亮子李的博客-CSDN博客里说的,感觉说的不太对,如果是我理解错,希望有同学可以指正
不得不提一句的是,在CNN中,是将多个通道的特征图加权起来。 就是B*H*W*C在C这个维度上加权。 而在vit中计算gradcam时,是将多个patch的特征图加权起来。 也就是B*(L-1)*h*w在L这个维度上加权起来。权重都是根据分配给各自的梯度决定的。