可以通过多种方式获得模型特征,而无需手动修改模型
今天讲第一种方法
获取倒数第二层特征
所有的model都定义了forward_features和forward_head方法 而forward一般写为如下形式
def forward(self, x): x = self.forward_features(x) x = self.forward_head(x) return x
其中forward_head一般是由pool,norm,mlp这些部分组成 我们一般关心模型主体能否提取图像特征,特征能否很好的迁移到其他任务,所以模型head之前的那一层很重要,经常要拿出来单独分析,关于这部分大概有三种实现方式
1.model.forward_features(input)
使用模型前向推理时就只使用forward_features,而不经过head,直接得到特征
modelvit = timm.create_model('vit_small_patch16_224', pretrained=True) #print(modelvit.global_pool)#token outfeatures = modelvit.forward_features(img224) #torch.Size([2, 197, 384])
2.创建一个没有head(池化和分类层)的模型
创建模型时, num_classes=0,取消分类层 build_model_with_cfg中,使用指定的default_cfg和可选的model_cfg构建模型,其中
# For classification models, check class attr, then kwargs, then default to 1k, otherwise 0 for feats num_classes_pretrained = 0 if features else getattr(model, 'num_classes', kwargs.get('num_classes', 1000))
getattr() 函数用于返回一个对象属性值 get() 函数在字典中搜索给定的键,如果找不到则返回默认值 features意味着提取模型所有中间特征,这个下一篇再说 这句话意思是对于模型,先检查类attr有没有num_classes定义,没有的话 去获取kwargs中num_classes,没有就默认为1k 当我们设定num_classes=0时,num_classes_pretrained = 0 ,从而在模型创建时去掉分类层
global_pool='' 取消池化 在影响模型架构,分别看一下ViT和ConvNext的源码
#vit def forward_head(self, x, pre_logits: bool = False): if self.global_pool: x = x[:, self.num_prefix_tokens:].mean(dim=1) if self.global_pool == 'avg' else x[:, 0] x = self.fc_norm(x) return x if pre_logits else self.head(x) #convnext self.head = nn.Sequential(OrderedDict([ ('global_pool', SelectAdaptivePool2d(pool_type=global_pool)), ('norm', nn.Identity() if head_norm_first else norm_layer(self.num_features)), ('flatten', nn.Flatten(1) if global_pool else nn.Identity()), ('drop', nn.Dropout(self.drop_rate)), ('fc', nn.Linear(self.num_features, num_classes) if num_classes > 0 else nn.Identity())]))
代码如下
modelvitnohead = timm.create_model('vit_small_patch16_224', pretrained=True, num_classes=0, global_pool='') outfeatures = modelvitnohead(img224) print(outfeatures.size())#torch.Size([2, 197, 384])
3.也可reset_classifier重制移除head层
每个模型都有定义此方法(reset_classifier),除此之外一般还有get_classifier/set_grad_checkpointing/group_matcher vit中
def reset_classifier(self, num_classes: int, global_pool=None): self.num_classes = num_classes if global_pool is not None: assert global_pool in ('', 'avg', 'token') self.global_pool = global_pool self.head = nn.Linear(self.embed_dim, num_classes) if num_classes > 0 else nn.Identity()
convnext中
def reset_classifier(self, num_classes=0, global_pool=None): if global_pool is not None: self.head.global_pool = SelectAdaptivePool2d(pool_type=global_pool) self.head.flatten = nn.Flatten(1) if global_pool else nn.Identity() self.head.fc = nn.Linear(self.num_features, num_classes) if num_classes > 0 else nn.Identity()
代码如下
m = timm.create_model('vit_small_patch16_224', pretrained=True) m.reset_classifier(0,'') outfeatures = m(img224) print(outfeatures.size())
注意:想清楚自己需要去除分类层还是分类层和池化层
影响head的不止num_class分类层 ,还要注意global_pool池化层 以上面reset_classifier的方法为例 m.reset_classifier(0,'')--outfeatures为-torch.Size([2, 197, 384]) m.reset_classifier(0)----outfeatures为-torch.Size([2, 384]) 可以看到有没有池化层对ViT输出的特征影响很大,ViT 使用Token池化,把182个Token池化为一个最终输出来进行分类
当想要未被池化的特征选择前面三种方法,想要池化后的,有两张方法(移除head层不可用) 可以 modelvitnoclass = timm.create_model('vit_small_patch16_224', pretrained=True, num_classes=0)#不写global_pool='' 或者 m.reset_classifier(0)#重制head的分类层