别再用平均池化层了,Meta AI把注意力塞到池化层,性能立刻提升0.3

注意力机制这么好用,怎么不把它塞到卷积网络里?最近Meta AI的研究人员提出了一个基于注意力的池化层,仅仅把平均池化层替换掉,就能获得+0.3%的性能提升!

Visual Transformer(ViT)作为计算机视觉领域的新兴霸主,已经在各个研究任务中逐渐替换掉了卷积神经网络CNN。

ViT与CNN之间存在着许多不同点,例如ViT的输入是image patch,而非像素;分类任务中,ViT是通过对类标记(class token)进行决策等等。

干货推荐

class token实际上是ViT论文原作者提出,用于整合模型输入信息的token。class token与每个patch进行信息交互后,模型就能了解到具体的分类信息。

并且在自注意力机制中,最后一层中的softmax可以作为注意力图,根据class token和不同patch之间的交互程度,就能够了解哪些patch对最终分类结果有影响及具体程度,也增加了模型可解释性。

但这种可解释性目前仍然是很弱的,因为patch和最后一层的softmax之间还隔着很多层和很多个header,信息之间的不断融合后,很难搞清楚最后一层softmax是否真的可以解释分类。

所以如果ViT和CNN一样有视觉属性就好了!

最近Meta AI就提出了一个新模型,用attention map来增强卷积神经网络,说简单点,其实就是用了一个基于注意力的层来取代常用的平均池化层。

图片

仔细一想,池化层和attention好像确实很配啊,都是对输入信息的加权平均进行整合。加入了注意力机制以后的池化层,可以明确地显示出不同patch所占的权重。

并且与经典ViT相比,每个patch都会获得一个单一的权重,无需考虑多层和多头的影响,这样就可以用一个简单的方法达到对注意力可视化的目的了。

图片

在分类任务中更神奇,如果对每个类别使用不同颜色进行单独标记的话,就会发现分类任务也能识别出图片中的不同物体。

图片

基于Attention的池化层

==========================

文章中新提出的模型叫做PatchConvNet,核心组件就是可学习的、基于attention的池化层。

图片

模型架构的主干是一个卷积网络,相当于是一个轻量级的预处理操作,它的作用就是把图像像素进行分割,并映射为一组向量,和ViT中patch extraction操作对应。

图片

最近也有研究表明,采用卷积的预处理能让模型的性能更加稳定。

模型的第二部分column(主干trunk),包含了整个模型中的大部分层、参数和计算量,它由N个堆叠的残差卷积块组成。每个块由一个归一化、1*1卷积,3*3卷积用来做空间处理,一个squeeze-and-excitation层用于混合通道特征,最后在残差连接前加入一个1*1的卷积。

图片

研究人员对模型块的选择也提出了一些建议,例如在batch size够大的情况下,BatchNorm往往效果比LayerNorm更好。但训练大模型或者高分辨率的图像输入时,由于batch size更小,所以BatchNorm在这种情况下就不太实用了。

下一个模块就是基于注意力的池化层了。

在主干模型的输出端,预处理后的向量通过类似Transformer的交叉注意力层(cross attention layer)的方式进行融合。

图片

注意力层中的每个权重值取决于预测patch与可训练向量(CLS)之间的相似度,结果和经典ViT中的class token类似。

然后将产生的d维向量添加到CLS向量中,并经过一个前馈网络处理。

与之前提出的class-attention decoder不同之处在于,研究人员仅仅只用一个block和一个head,大幅度简化了计算量,也能够避免多个block和head之间互相影响,从而导致注意力权重失真。

因此,class token和预处理patch之间的通信只发生在一个softmax中,直接反映了池化操作者如何对每个patch进行加权。

也可以通过将CLS向量替换为k×d矩阵来对每个类别的attention map进行归一化处理,这样就可以看出每个块和每个类别之间的关联程度。

但这种设计也会增加内存的峰值使用量,并且会使网络的优化更加复杂。通常只在微调优化的阶段以一个小的学习率和小batch size来规避这类问题。

实验结果

===============

在图像分类任务上,研究人员首先将模型与ImageNet1k和ImageNet-v2上的其他模型从参数量,FLOPS,峰值内存用量和256张图像batch size下的模型推理吞吐量上进行对比。

图片

实验结果肯定是好的,可以看到PatchConvNet的简单柱状结构(column architecture)相比其他模型更加简便和易于扩展。对于高分辨率图像来说,不同模型可能会针对FLOPs和准确率进行不同的平衡,更大的模型肯定会取得更高的准确率,相应的吞吐量就会低一些。

在语义分割任务上,研究人员通过ADE20k数据集上的语义分割实验来评估模型,数据集中包括2万张训练图像和5千张验证图像,标签超过150个类别。由于PatchConvNet模型不是金字塔式的,所以模型只是用模型的最后一层输出和UpperNet的多层次网络输出,能够简化模型参数。研究结果显示,虽然PatchConvNet的结构更简单,但与最先进的Swin架构性能仍处于同一水平,并且在FLOPs-MIoU权衡方面优于XCiT。

图片

在检测和实例分割上,研究人员在COCO数据集上对模型进行评估,实验结果显示PatchConvNet相比其他sota架构来说,能够在FLOPs和AP之间进行很好的权衡。

图片

在消融实验中,为了验证架构问题,研究人员使用不同的架构对比了Transformer中的class attention和卷积神经网络的平均池化操作,还对比了卷积主干和线性投影之间的性能差别等等。实验结果可以看到卷积主干是模型取得最佳性能的关键,class-attention几乎没有带来额外的性能提升。

图片

另一个重要的消融实验时attention-based pooling和ConvNets之间的对比,研究人员惊奇地发现可学习的聚合函数甚至可以提高一个ResNet魔改后模型的性能。

通过把attention添加到ResNet50中,直接在Imagenet1k上获得了80.1%的最高准确率,比使用平均池化层的baseline模型提高了+0.3%的性能,并且attention-based只稍微增加了模型的FLOPs数量,从4.1B提升到4.6B。

参考资料:

https://arxiv.org/abs/2112.13692

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在ResNet50模型中,池化层是在每个残差块之间添加的,可以通过修改代码来禁用池化层。具体来说,可以在每个残差块的代码中将池化层替换为步幅为1的卷积或者直接删除池化层。以下是一段示例代码,展示了如何在ResNet50中禁用池化层: ```python from keras.layers import Input, Conv2D, BatchNormalization, Activation, Add, ZeroPadding2D, AveragePooling2D, Flatten, Dense from keras.models import Model # 定义卷积块函数 def conv_block(input_tensor, kernel_size, filters, stage, block, strides=(2, 2)): filters1, filters2, filters3 = filters conv_name_base = 'res' + str(stage) + block + '_branch' bn_name_base = 'bn' + str(stage) + block + '_branch' x = Conv2D(filters1, (1, 1), strides=strides, name=conv_name_base + '2a')(input_tensor) x = BatchNormalization(name=bn_name_base + '2a')(x) x = Activation('relu')(x) x = Conv2D(filters2, kernel_size, padding='same', name=conv_name_base + '2b')(x) x = BatchNormalization(name=bn_name_base + '2b')(x) x = Activation('relu')(x) x = Conv2D(filters3, (1, 1), name=conv_name_base + '2c')(x) x = BatchNormalization(name=bn_name_base + '2c')(x) shortcut = Conv2D(filters3, (1, 1), strides=strides, name=conv_name_base + '1')(input_tensor) shortcut = BatchNormalization(name=bn_name_base + '1')(shortcut) x = Add()([x, shortcut]) x = Activation('relu')(x) return x # 定义恒等块函数 def identity_block(input_tensor, kernel_size, filters, stage, block): filters1, filters2, filters3 = filters conv_name_base = 'res' + str(stage) + block + '_branch' bn_name_base = 'bn' + str(stage) + block + '_branch' x = Conv2D(filters1, (1, 1), name=conv_name_base + '2a')(input_tensor) x = BatchNormalization(name=bn_name_base + '2a')(x) x = Activation('relu')(x) x = Conv2D(filters2, kernel_size, padding='same', name=conv_name_base + '2b')(x) x = BatchNormalization(name=bn_name_base + '2b')(x) x = Activation('relu')(x) x = Conv2D(filters3, (1, 1), name=conv_name_base + '2c')(x) x = BatchNormalization(name=bn_name_base + '2c')(x) x = Add()([x, input_tensor]) x = Activation('relu')(x) return x # 定义ResNet50模型 def ResNet50(input_shape=(224, 224, 3), classes=1000): input_tensor = Input(shape=input_shape) x = ZeroPadding2D((3, 3))(input_tensor) x = Conv2D(64, (7, 7), strides=(2, 2), name='conv1')(x) x = BatchNormalization(name='bn_conv1')(x) x = Activation('relu')(x) x = ZeroPadding2D((1, 1))(x) x = MaxPooling2D((3, 3), strides=(2, 2))(x) x = conv_block(x, 3, [64, 64, 256], stage=2, block='a', strides=(1, 1)) x = identity_block(x, 3, [64, 64, 256], stage=2, block='b') x = identity_block(x, 3, [64, 64, 256], stage=2, block='c') x = conv_block(x, 3, [128, 128, 512], stage=3, block='a') x = identity_block(x, 3, [128, 128, 512], stage=3, block='b') x = identity_block(x, 3, [128, 128, 512], stage=3, block='c') x = identity_block(x, 3, [128, 128, 512], stage=3, block='d') x = conv_block(x, 3, [256, 256, 1024], stage=4, block='a') x = identity_block(x, 3, [256, 256, 1024], stage=4, block='b') x = identity_block(x, 3, [256, 256, 1024], stage=4, block='c') x = identity_block(x, 3, [256, 256, 1024], stage=4, block='d') x = identity_block(x, 3, [256, 256, 1024], stage=4, block='e') x = identity_block(x, 3, [256, 256, 1024], stage=4, block='f') x = conv_block(x, 3, [512, 512, 2048], stage=5, block='a') x = identity_block(x, 3, [512, 512, 2048], stage=5, block='b') x = identity_block(x, 3, [512, 512, 2048], stage=5, block='c') x = AveragePooling2D((7, 7), name='avg_pool')(x) x = Flatten()(x) x = Dense(classes, activation='softmax', name='fc1000')(x) model = Model(inputs=input_tensor, outputs=x, name='resnet50') return model # 禁用池化层 def disable_pooling(model): for layer in model.layers: if 'pool' in layer.name: layer.trainable = False return model # 创建ResNet50模型并禁用池化层 model = ResNet50() model = disable_pooling(model) ``` 在上述代码中,我们定义了一个`disable_pooling`函数,它遍历了ResNet50模型中的所有,并将所有名称带有`pool`的的`trainable`属性设置为False,这样就禁用了所有的池化层。最后,我们用`disable_pooling`函数来禁用池化层

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值