在计算机视觉领域,目标检测任务的性能提升一直是研究热点。我们基于对YoloV8模型的深入理解,创新性地引入了DeBiLevelRoutingAttention(简称DBRA)注意力模块,旨在进一步增强模型的特征提取能力和目标检测精度。
一、改进概述
本次改进的核心在于将DeBiLevelRoutingAttention模块嵌入到YoloV8的主干网络中,具体位置是在SPPF(Spatial Pyramid Pooling Fast)模块之后。这一设计充分利用了DBRA模块在捕获长距离依赖关系和语义信息方面的优势,使得YoloV8在保持高效推理速度的同时,显著提升了目标检测的准确性和鲁棒性。
DeBiLevelRoutingAttention模块优势
改进效果
在多个主流数据集上的实验结果表明,采用DeBiLevelRoutingAttention模块改进的YoloV8模型在目标检测任务上取得了显著的性能提升。与原始YoloV8模型相比,改进后的模型在保持高效推理速度的同时,实现了更高的检测精度和更强的泛化能力。
完整链接:
https://blog.csdn.net/m0_47867638/article/details/143092641?spm=1001.2014.3001.5501
带有各种注意力模块的视觉Transformer在视觉任务上已表现出卓越的性能。虽然使用稀疏自适应注意力(如在DAT中)在图像分类任务中取得了显著成果,但在对语义分割任务进行微调时,由可变形点选择的关键值对缺乏语义相关性。BiFormer中的查询感知稀疏注意力旨在使每个查询关注前个路由区域。然而,在注意力计算过程中,所选的关键值对受到过多不相关查询的影响,从而降低了对更重要查询的关注度。为了解决这些问题,我们提出了可变形双级路由注意力(DBRA)模块,该模块使用代理查询优化关键值对的选择,并增强了注意力图中查询的可解释性。在此基础上,我们引入了带有DBRA模块的新型通用视觉Transformer——可变形双级路由注意力Transformer(DeBiFormer)。DeBiFormer已在各种计算机视觉任务上得到验证,包括图像分类、目标检测和语义分割,有力地证明了其有效性。代码可访问:https://github.com/maclong01/DeBiFormer 关键词:视觉Transformer,自注意力机制,图像识别
视觉Transformer在计算机视觉领域近期展现出了巨大的潜力[15,29,44]。它能够捕获数据中的长距离依赖关系[29,41],并几乎引领了一种更灵活、更适合拟合大量数据的无卷积模型[44]。此外,它还具有高并行性,这有利于大型模型的训练和推理[11,41]。计算机视觉领域观察到,视觉Transformer的采用和发展呈现出爆炸式增长[1,14,15,29,44,45]。
为了提高注意力,大量研究精心设计了高效的注意力模式,其中每个查询都通过较小部分的关键值对进行选择性聚焦。如图1所示,在各种表示方法中,一些方法包括局部窗口[50]和空洞窗口[45, 40, 24]。此外,一些研究在方法论上通过数据稀疏性适应采取了不同的路径,如[5,47]中的工作所示。然而,尽管在合并或选择关键和值令牌时采用了不同的策略,但这些令牌对于查询来说并不具有语义性。采用这种方法时,当应用于预训练的ViT[41]和DETR[1]的其他下游任务时,查询并非源自语义区域的关键值对。因此,强制所有查询关注于不足的一组令牌可能无法产生最优结果。最近,随着动态查询感知稀疏注意力机制的出现,查询由最具动态语义性的关键值对进行聚焦,这被称为双级路由注意力[56]。然而,在这种方法中,查询由语义关键值对处理而非源自详细区域,这可能并非在所有情况下都能产生最优结果。此外,在计算注意力时,为所有查询选择的这些关键值和值受到太多不太相关查询的影响,导致对重要查询的注意力降低,这对执行分割任务时具有重大影响[13,25]。
为了使查询的注意力更加高效,我们提出了可变形双级路由注意力(DBRA),这是一种用于视觉识别的注意力中注意力架构。在DBRA的过程中,第一个问题是如何定位可变形点。我们使用了[47]中的观察结果,即注意力具有一个偏移网络,该网络以查询特征为输入,并为所有参考点生成相应的偏移量。因此,候选可变形点以高灵活性和效率向重要区域移动,以捕获更多信息性特征。第二个问题是如何从语义相关的关键值对中聚合信息,然后将信息回传给查询。因此,我们提出了一种注意力中注意力架构,其中如上文所示向可变形点移动的部分作为查询的代理。由于关键值对是为可变形点选择的,我们使用[56]中的观察结果来选择一小部分最具语义相关性的关键值对,即一个区域仅通过关注前个路由区域来所需的部分。然后,在选择了语义相关的关键值对后,我们首先使用带有可变形点查询的令牌到令牌注意力。接着,我们应用第二个令牌到令牌注意力将信息回传给查询,其中作为关键值对的可变形点被设计为表示语义区域中最重要的点。
综上所述,我们的贡献如下:
基于Transformer的主干网络结合了通道级MLP[38]块,通过通道混合嵌入每个位置的特征。此外,还使用注意力[41]块进行跨位置关系建模并促进空间混合。Transformer最初是为自然语言处理[41,11]而设计的,随后通过DETR[1]和ViT[41]等工作被引入计算机视觉领域。与卷积神经网络(CNN)相比,Transformer的主要区别在于它使用注意力替代卷积,从而促进了全局上下文建模。然而,传统的注意力机制计算所有空间位置之间的成对特征亲和力,这带来了巨大的计算负担和内存占用,特别是在处理高分辨率输入时。因此,一个关键的研究重点是设计更高效的注意力机制,这对于减轻计算需求至关重要,尤其是处理高分辨率输入时。
大量研究旨在减轻传统注意力机制带来的计算和内存复杂性。方法包括稀疏连接模式[6]、低秩近似[42]和循环操作[10]。在视觉Transformer的上下文中,稀疏注意力变得流行起来,特别是在Swin Transformer[29]取得显著成功后。在Swin Transformer框架中,注意力被限制在非重叠的局部窗口中,并引入了一种创新的移位窗口操作。该操作促进了相邻窗口之间的通信,为其处理注意力机制提供了独特的方法。为了在不超过计算限制的情况下实现更大或近似全局的感受野,最近的研究结合了多种手动设计的稀疏模式。这些模式包括空洞窗口[45,40,24]和十字形窗口[14]的集成。此外,一些研究致力于使稀疏模式适应数据,如DAT[47]、TCFormer[53]和DPT[5]等工作所示。尽管它们通过使用不同的合并或选择策略来减少关键值令牌的数量,但重要的是要认识到这些令牌缺乏语义特异性。相反,我们加强了查询感知的关键值令牌选择。
我们的工作受到一个观察结果的启发:对于重要查询,语义上关注的区域可能表现出显著差异,如ViT[41]和DETR[1]等预训练模型的可视化所示。在实现通过粗细粒度方法实现的查询自适应稀疏性时,我们提出了一种注意力中注意力架构,该架构结合了可变形注意力[47]和双级路由注意力[56]。与可变形注意力[47]和双级路由注意力[56]不同,我们的可变形双级路由注意力旨在加强最具语义性和灵活性的关键值对。相比之下,双级路由注意力仅关注定位少数高度相关的关键值对,而可变形注意力则优先识别少数最具灵活性的关键值对。
首先,我们回顾了最近视觉Transformer中使用的注意力机制。以扁平化的特征图 作为输入,具有个头的多头自注意力(MHSA)块表示为
其中, 表示softmax函数, 是每个头的维度。 表示第个注意力头的嵌入输出,而 分别表示查询、键和值嵌入。 是投影矩阵。带有归一化层和恒等捷径的第个Transformer块(其中LN表示层归一化)表示为
所提出的可变形双层路由注意力(DBRA)的架构如图2所示。我们首先采用一个可变形注意力模块,该模块包含一个偏移网络,该网络基于查询特征为参考点生成偏移量,从而创建可变形点。然而,这些点往往会在重要区域聚集,导致某些区域过度集中。
为解决此问题,我们引入了可变形点感知区域划分,确保每个可变形点仅与键值对的一个小子集进行交互。然而,仅依赖区域划分可能会导致重要区域和不太重要区域之间的不平衡。为解决此问题,DBRA模块被设计为更有效地分配注意力。在DBRA中,每个可变形点充当代理查询,与语义区域键值对计算注意力。这种方法确保每个重要区域仅分配少数可变形点,从而使注意力分散到图像的所有关键区域,而不是聚集在一个点上。
通过使用DBRA模块,不太重要区域的注意力减少,更重要区域的注意力增加,确保整个图像中注意力的平衡分布。
可变形注意力模块和输入投影。如图2所示,给定输入特征图 ,通过以因子 对输入特征图进行下采样,生成一个均匀的点网格 ,其中 ,作为参考。为了获得每个参考点的偏移量,将特征进行线性投影以生成查询令牌 ,然后将其输入到 子网络中,以产生偏移量 。随后,在变形点的位置对特征进行采样作为键和值,并通过投影矩阵进行进一步处理:
其中, 分别表示变形后的键 和值 嵌入。具体来说,我们将采样函数 设置为双线性插值,使其可微:
其中,函数 ,且 表示 上所有位置的索引。在类似于可变形注意力的设置中,当 在最接近 的四个整数点上不为零时,方程7简化为这四个位置上的加权平均。
区域划分与区域间路由。给定可变形注意力特征图输入 和特征图 ,过程首先将其划分为大小为 的非重叠区域,使得每个区域包含 个特征向量,并将重塑后的 记为 ,将 记为 。然后,我们通过线性投影得到查询、键和值:
接下来,我们使用BiFormer[56]中介绍的区域间方法,通过构建有向图来建立注意关系。首先,通过每个区域的平均值得到区域查询和键 。然后,通过 和 矩阵乘法得到区域间亲和图的邻接矩阵 :
其中,邻接矩阵 量化了两个区域之间的语义关系。该方法的关键步骤是通过使用topk操作符和路由索引矩阵 保留每个区域的topk连接来修剪亲和图:
双层标记到可变形层标记注意力。利用区域路由矩阵 ,我们可以应用标记注意力。对于区域 内的每个可变形查询标记,其注意力跨越位于topk路由区域中的所有键值对,即由 索引的那些。因此,我们继续收集键和值的过程:
其中, 是收集的键和值。然后,我们对 应用注意力:
其中, 是输出特征的投影权重, 使用核大小为5的深度卷积。
可变形层标记到标记注意力。之后,通过[56]语义关注的可变形特征被重塑为 ,并在键和值的位置进行参数化:
和 分别表示语义变形键和值的嵌入。使用现有方法,我们对 和相对位置偏移 执行自注意力。注意力的输出公式如下:
这里, 对应位置嵌入,遵循先前工作[29]的方法。然后,将 通过 投影得到最终输出 ,如方程3所示。
利用DBRA作为基本构建块,我们引入了一种新的视觉转换器,称为DeBiFormer。如图3所示,我们遵循最新的最先进的视觉转换器[14,29,56,47],使用四阶段金字塔结构。在第 阶段,我们在第一阶段使用重叠补丁嵌入,在第二到第四阶段使用补丁合并模块[26,34]。这是为了降低输入空间分辨率,同时增加通道数。随后,使用 个连续的DeBiFormer块来转换特征。在每个DeBiFormer块内,我们遵循最近的方法论[26,40,56],在开始时使用 深度卷积。这是为了隐式编码相对位置信息。之后,我们依次使用一个DBRA模块和一个具有扩展比 的2-ConvFFN模块,分别用于跨位置关系建模和每个位置的嵌入。DeBiFormer以三种不同的模型尺寸实例化,通过按表1中概述的网络宽度和深度进行缩放来实现。每个注意力头包含32个通道,我们使用具有MLP扩展比 的双层ConvFFN和可变形层ConvFFN。对于BRA,我们在四个阶段使用topk =1,4,16, ,对于DBRA,我们使用topk =4,8,16, 。此外,我们将区域划分因子 设置为特定值:分类任务中 ,语义分割任务中 ,目标检测任务中 。
我们通过实验评估了所提出的DeBiFormer在各种主流计算机视觉任务上的有效性,包括图像分类(第4.1节)、语义分割(第4.2节)和目标检测以及实例分割(第4.3节)。在我们的方法中,我们从ImageNet-1K [35]数据集开始从头训练图像分类模型。随后,我们在ADE20K [55]数据集上对预训练的主干网络进行微调,以进行语义分割,并在COCO [17]数据集上进行微调,以进行目标检测和实例分割。此外,我们进行了消融研究,以验证所提出的可变形双级路由注意力(Deformable Bi-level Routing Attention)和DeBiFormer的top-k选择的有效性(第4.4节)。最后,为了验证我们DeBiFormer的识别能力和可解释性,我们对注意力图进行了可视化(第5节)。
设置。我们在ImageNet-1K [35]数据集上进行了图像分类实验,遵循DeiT [39]的实验设置以进行公平比较。具体来说,每个模型在8个V100 GPU上以224×224的输入大小训练300个epoch。我们使用AdamW作为优化器,权重衰减为0.05,并采用余弦衰减学习率调度策略,初始学习率为0.001,同时前五个epoch用于线性预热。批量大小设置为1024。为避免过拟合,我们使用了正则化技术,包括RandAugment [9](rand-m9-mstd0.5-inc1)、MixUp [54](prob=0.8)、CutMix [52](prob=1.0)、随机擦除(prob=0.25)以及增加随机深度[23](对于DeBiFormer-T/S/B,prob分别为0.1/0.2/0.4)。结果。我们在表2中报告了结果,展示了具有相似计算复杂度的top-1准确率。我们的DeBiFormer在所有三个尺度上都优于Swin Transformer [29]、PVT [44]、DeiT [39]、DAT[47]和Biformer [56]。在不将卷积插入Transformer块或使用重叠卷积进行块嵌入的情况下,DeBiFormer相对于BiFormer [56]对应版本分别实现了0.5pt、0.1pt和0.1pt的增益。
设置。与现有工作相同,我们在SemanticFPN [46]和UperNet [48]上使用了我们的DeBiFormer。在这两种情况下,主干网络都使用ImageNet-1K预训练权重进行初始化。优化器是AdamW [31],批量大小为32。为进行公平比较,我们遵循PVT [44]的相同设置,用80k步训练模型,并遵循Swin Transformer [29]的相同设置,用160k步训练模型。
结果。表8展示了两个不同框架的结果。结果表明,在使用Semantic FPN框架的情况下,我们的DeBiFormer-S/B分别实现了49.2/50.6 mIoU,比BiFormer提高了0.3pt/0.7pt。对于UperNet框架,也观察到了类似的性能增益。通过使用DBRA模块,我们的DeBiFormer能够捕获最多的语义键值对,这使得注意力选择更加合理,并在下游语义任务上实现了更高的性能。
设置。我们使用DeBiFormer作为Mask RCNN [19]和RetinaNet [16]框架中的主干网络,以评估模型在COCO 2017 [17]数据集上对于目标检测和实例分割的有效性。实验使用MMDetection [3]工具箱进行。在COCO上进行训练之前,我们使用ImageNet-1K预训练权重对主干网络进行初始化,并遵循与BiFormer [56]相同的训练策略以公平比较我们的方法。请注意,由于设备限制,我们在这些实验中设置小批量大小为4,而在BiFormer中此值为16。有关实验具体设置的详细信息,请参阅补充论文。
结果。我们在表4.2中列出了结果。对于使用RetinaNet进行的目标检测,我们报告了不同IoU阈值(50%,75%)下三个目标尺寸(即小、中、大(S/M/L))的平均精度(mAP)和平均精度(AP)。从结果中可以看出,尽管DeBiFormer的整体性能仅与一些最具竞争力的现有方法相当,但在大目标(AP_L)上的性能却优于这些方法,尽管我们使用的资源有限。这可能是因为DBRA更合理地分配了可变形点。这些点不仅关注小事物,还关注图像中的重要事物。因此,注意力不仅局限于小区域,从而提高了大目标的检测准确性。对于使用Mask R-CNN进行的实例分割,我们报告了不同IoU阈值(50%,75%)下的边界框和掩码的平均精度(AP_b和AP_m)。请注意,尽管受到设备限制(小批量大小),我们的DeBiFormer仍然取得了出色的性能。我们认为,如果小批量大小可以与其他方法相同,我们将能够取得更好的结果,这在语义分割任务中已经得到了证明。
DBRA的有效性。我们将DBRA与几种现有的稀疏注意力机制进行了比较。遵循CSWIN [14],我们为公平比较将宏观架构设计与Swin-T [29]对齐。具体来说,我们在四个阶段分别使用了2、2、6、2个块和非重叠的补丁嵌入,并将初始补丁嵌入维度设置为,MLP扩展比率设置为。结果如表5所示。在图像分类和语义分割方面,我们的可变形双级路由注意力(Deformable Bi-level Routing Attention)性能明显优于现有的稀疏注意力机制。
分区因子。与BiFormer类似,我们选择使用作为训练尺寸的除数,以避免填充。我们使用分辨率为的图像分类,并设置,以确保每个阶段的特征图尺寸都能被整除。这一选择与Swin Transformer [29]中使用的策略一致,其中窗口大小为7。
Top-k选择。我们系统地调整了,以确保在后续阶段区域尺寸减小时,有合理数量的令牌被关注到可变形查询上。探索的各种组合是一个可行的选择。在表9中,我们按照DeBiFormer-STL(“STL”表示Swin-T布局)报告了在IN-1K上的消融结果。从这些实验中得出的一个关键观察结果是,增加关注到可变形查询的令牌数量对准确性和延迟有不利影响,而在第1和第2阶段增加关注到的令牌数量对准确性有影响。
不同阶段的可变形双级路由多头注意力(DBRMHA)。为了评估设计选择的影响,我们系统地用DBRMHA块替换了不同阶段中的双级路由注意力块,如表7所示。最初,所有阶段都使用双级路由注意力,类似于BiFormer-T [56],在图像分类中实现了的准确率。仅将第4阶段的一个块替换为DBRMHA,准确率立即提高了。将第4阶段的所有块都替换为DBRMHA,又增加了。在第3阶段进一步替换DBRMHA块继续提高了各项任务的性能。尽管早期阶段的替换带来的增益逐渐减少,但我们最终确定了一个版本——DeBiFormer,其中所有阶段都使用可变形双级路由注意力,以保持简洁性。
为了进一步说明所提出的DeBiFormer识别重要区域注意力的能力,我们使用Grad-CAM [36]可视化了BiFormer-Base和DeBiFormer-Base最关注的区域。如图4所示,通过使用DBRA模块,我们的DeBiFormer-Base模型在定位目标对象方面表现更好,其中更多的区域被关注到。此外,我们的模型降低了在不必要区域的注意力,并更加关注必要区域。根据对更多必要区域的注意力,我们的DeBiFormer模型更加连续和完整地关注语义区域,这表明我们的模型具有更强的识别能力。这种能力相比BiFormer-Base带来了更好的分类和语义分割性能。
本文介绍了可变形双级路由注意力Transformer(Deformable Bi-level Routing Attention Transformer),这是一种专为图像分类和密集预测任务设计的新型分层视觉Transformer。通过可变形双级路由注意力,我们的模型优化了查询-键-值交互,同时自适应地选择语义相关区域。这实现了更高效和有意义的注意力。大量实验表明,与强大的基线相比,我们的模型具有有效性。我们希望这项工作能为设计灵活且语义感知的注意力机制提供见解。
与[47]类似,为了促进变形点之间的多样性,我们遵循与MHSA中相似的范式,其中通道被分成多个头来计算各种注意力。因此,我们将通道分成组以生成不同的偏移量。偏移生成网络对来自不同组的特征共享权重。
当然,将位置信息融入注意力机制已被证明对模型性能有益。诸如APE[15]、RPE[29]、CPE[8]、LogCPB[28]等方法以及其他方法已证明能够改善结果。Swin Transformer中引入的相对位置嵌入(RPE)特别编码了每对查询和键之间的相对位置,从而通过空间归纳偏置增强了普通注意力[29]。相对位置的显式建模特别适合可变形级别的注意力头。在这种情况下,变形键可以假设任意连续位置,而不是局限于固定的离散网格。
根据[47],相对坐标位移在空间维度上被限制在和范围内,并带有一个相对位置偏置(RPB),表示为,其维度为。
然后,使用带参数偏置的双线性插值,在范围内对相对位置进行采样。这是通过考虑连续相对位移来完成的,以确保覆盖所有可能的偏移值。
可变形双层路由注意力(DBRA)的计算成本与Swin Transformer中的对应机制相当。DBRA的计算包括两部分:令牌到令牌的注意力和偏移量&采样。因此,这部分的计算是:
其中,是采样点的数量,是令牌嵌入维度。双层路由多头注意力的计算包括三部分:线性投影、区域到区域的路由和令牌到令牌的注意力。因此,这部分的计算是:
其中,是要注意的区域数量,是区域划分因子。最后,DBRA的总计算包括两部分:可变形级别的多头注意力和双层路由多头注意力。因此,总计算量是:
换句话说,DBRA实现了的复杂度。例如,对于图像分类的层次模型,其第三阶段具有输入,通常具有,,,的计算规模,因此具有多头自注意力的计算复杂度。此外,通过增大下采样因子并根据区域划分因子进行缩放,可以进一步降低复杂度,使其适用于具有更高分辨率输入的任务,如目标检测和实例分割。
在表9中,我们展示了要关注查询的令牌和要关注可变形点的令牌。与其他方法相比,DeBiFormer每个查询要关注的令牌最少,但在Imagenet1K、ADE20K(S-FPN头)和COCO(Retina头)上表现出高性能。
有效感受野分析 为了评估不同模型中,输入尺寸为224x224时中心像素的有效感受野(ERF)[32],我们在图5中展示了比较分析。为了证明我们DeBiFormer的强大表示能力,我们还比较了具有相似计算成本的几种SOTA(state-of-the-art,当前最优)方法的有效感受野。如图5所示,我们的DeBiFormer在这些方法中拥有最大且最一致的有效感受野,同时保持了强大的局部敏感性,这是很难实现的。
Grad-CAM分析 为了进一步展示DBRA(动态双分支注意力,Dynamic Bi-branch Attention)的工作原理,我们在图6中展示了更多的可视化结果。得益于灵活的键值对选择,在大多数情况下,我们的DeBiFormer在早期阶段就关注于重要对象。同时,由于变形点的合理分配,它在多对象场景中也能更早地关注于不同的重要区域。凭借强大的DBRA模块,我们的DeBiFormer在最后两个阶段具有更大的热图区域,这代表了更强的识别能力。
ImageNet-1K上的图像分类 如主文所述,每个模型在8个V100 GPU上以224x224的输入尺寸训练300个epoch。实验设置严格遵循DeiT[39]以进行公平比较。更多详细信息,请参阅提供的表10。
目标检测和实例分割 当将我们的DeBiFormer微调至COCO[17]上的目标检测和实例分割时,我们考虑了两种常见框架:Mask R-CNN[19]和RetinaNet[16]。对于优化,我们采用AdamW优化器,初始学习率为0.0002,由于设备限制,小批量大小为4。当训练不同大小的模型时,我们根据图像分类中使用的设置调整训练设置。训练模型时使用的详细超参数见表11。
语义分割 对于ADE20K,我们为所有训练了160K迭代的模型使用AdamW优化器,初始学习率为0.00006,权重衰减为0.01,小批量大小为16。在测试方面,我们在主要比较中报告了使用单尺度(SS)和多尺度(MS)测试的结果。对于多尺度测试,我们尝试了从0.5倍到1.75倍训练分辨率的分辨率范围。为了设置不同模型中的路径丢弃率,我们使用了与目标检测和实例分割相同的超参数。表8显示了Upernet框架在单尺度和多尺度IoU下的结果。
与具有简单静态模式的稀疏注意力相比,我们提出了一种新的注意力方法,该方法由两个组件组成。首先,我们修剪区域级图,并为重要区域收集键值对,这些区域由高度灵活的键值对所关注。然后,我们应用令牌到令牌的注意力。虽然这种方法由于在顶级k路由的语义相关区域级别和可变形的重要区域上操作而不会引起太多计算,但它不可避免地在线性投影期间涉及额外的参数容量交易。在未来的工作中,我们计划研究高效的稀疏注意力机制,并增强具有参数容量意识的视觉Transformer。
对DeBiLevelRoutingAttention做了适当的修改,代码如下:
class DeBiLevelRoutingAttention(nn.Module):
"""
n_win: number of windows in one side (so the actual number of windows is n_win*n_win)
kv_per_win: for kv_downsample_mode='ada_xxxpool' only, number of key/values per window. Similar to n_win, the actual number is kv_per_win*kv_per_win.
topk: topk for window filtering
param_attention: 'qkvo'-linear for q,k,v and o, 'none': param free attention
param_routing: extra linear for routing
diff_routing: wether to set routing differentiable
soft_routing: wether to multiply soft routing weights
"""
def __init__(self, dim, num_heads=8, n_win=7, qk_dim=None, qk_scale=None,
kv_per_win=4, kv_downsample_ratio=4, kv_downsample_kernel=None, kv_downsample_mode='identity',
topk=4, param_attention="qkvo", param_routing=False, diff_routing=False, soft_routing=False, side_dwconv=3,
auto_pad=True, param_size='small'):
super().__init__()
# local attention setting
self.dim = dim
self.n_win = n_win # Wh, Ww
self.num_heads = num_heads
self.qk_dim = dim
self.n_groups = 8
self.top_k_def = 49 # 8 512
self.kk = 3
self.stride_def = 1
self.expain_ratio = 1
self.q_size = to_2tuple(7)
self.q_h, self.q_w = self.q_size
self.kv_h, self.kv_w = self.q_h // self.stride_def, self.q_w // self.stride_def
self.n_group_channels = self.dim // self.n_groups
self.n_group_heads = self.num_heads // self.n_groups
self.n_group_channels = self.dim // self.n_groups
self.offset_range_factor = -1
self.head_channels = dim // num_heads
self.n_group_heads = self.num_heads // self.n_groups
#assert self.qk_dim % num_heads == 0 and self.dim % num_heads==0, 'qk_dim and dim must be divisible by num_heads!'
self.scale = qk_scale or self.qk_dim ** -0.5
self.rpe_table = nn.Parameter(
torch.zeros(self.num_heads, self.q_h * 2 - 1, self.q_w * 2 - 1)
)
trunc_normal_(self.rpe_table, std=0.01)
################side_dwconv (i.e. LCE in ShuntedTransformer)###########
self.lepe1 = nn.Conv2d(dim, dim, kernel_size=side_dwconv, stride=self.stride_def, padding=side_dwconv//2, groups=dim) if side_dwconv > 0 else \
lambda x: torch.zeros_like(x)
################ global routing setting #################
self.topk = topk
self.param_routing = param_routing
self.diff_routing = diff_routing
self.soft_routing = soft_routing
# router
#assert not (self.param_routing and not self.diff_routing) # cannot be with_param=True and diff_routing=False
self.router = TopkRouting(qk_dim=self.qk_dim,
qk_scale=self.scale,
topk=self.topk,
diff_routing=self.diff_routing,
param_routing=self.param_routing)
if self.soft_routing: # soft routing, always diffrentiable (if no detach)
mul_weight = 'soft'
elif self.diff_routing: # hard differentiable routing
mul_weight = 'hard'
else: # hard non-differentiable routing
mul_weight = 'none'
self.kv_gather = KVGather(mul_weight=mul_weight)
# qkv mapping (shared by both global routing and local attention)
self.param_attention = param_attention
if self.param_attention == 'qkvo':
#self.qkv = QKVLinear(self.dim, self.qk_dim)
self.qkv_conv = QKVConv(self.dim, self.qk_dim)
#self.wo = nn.Linear(dim, dim)
elif self.param_attention == 'qkv':
#self.qkv = QKVLinear(self.dim, self.qk_dim)
self.qkv_conv = QKVConv(self.dim, self.qk_dim)
#self.wo = nn.Identity()
else:
raise ValueError(f'param_attention mode {self.param_attention} is not surpported!')
self.kv_downsample_mode = kv_downsample_mode
self.kv_per_win = kv_per_win
self.kv_downsample_ratio = kv_downsample_ratio
self.kv_downsample_kenel = kv_downsample_kernel
if self.kv_downsample_mode == 'ada_avgpool':
assert self.kv_per_win is not None
self.kv_down = nn.AdaptiveAvgPool2d(self.kv_per_win)
elif self.kv_downsample_mode == 'ada_maxpool':
assert self.kv_per_win is not None
self.kv_down = nn.AdaptiveMaxPool2d(self.kv_per_win)
elif self.kv_downsample_mode == 'maxpool':
assert self.kv_downsample_ratio is not None
self.kv_down = nn.MaxPool2d(self.kv_downsample_ratio) if self.kv_downsample_ratio > 1 else nn.Identity()
elif self.kv_downsample_mode == 'avgpool':
assert self.kv_downsample_ratio is not None
self.kv_down = nn.AvgPool2d(self.kv_downsample_ratio) if self.kv_downsample_ratio > 1 else nn.Identity()
elif self.kv_downsample_mode == 'identity': # no kv downsampling
self.kv_down = nn.Identity()
elif self.kv_downsample_mode == 'fracpool':
raise NotImplementedError('fracpool policy is not implemented yet!')
elif kv_downsample_mode == 'conv':
raise NotImplementedError('conv policy is not implemented yet!')
else:
raise ValueError(f'kv_down_sample_mode {self.kv_downsaple_mode} is not surpported!')
self.attn_act = nn.Softmax(dim=-1)
self.auto_pad=auto_pad
##########################################################################################
self.proj_q = nn.Conv2d(
dim, dim,
kernel_size=1, stride=1, padding=0
)
self.proj_k = nn.Conv2d(
dim, dim,
kernel_size=1, stride=1, padding=0
)
self.proj_v = nn.Conv2d(
dim, dim,
kernel_size=1, stride=1, padding=0
)
self.proj_out = nn.Conv2d(
dim, dim,
kernel_size=1, stride=1, padding=0
)
self.unifyheads1 = nn.Conv2d(
dim, dim,
kernel_size=1, stride=1, padding=0
)
self.conv_offset_q = nn.Sequential(
nn.Conv2d(self.n_group_channels, self.n_group_channels, (self.kk,self.kk), (self.stride_def,self.stride_def), (self.kk//2,self.kk//2), groups=self.n_group_channels, bias=False),
LayerNormProxy(self.n_group_channels),
nn.GELU(),
nn.Conv2d(self.n_group_channels, 1, 1, 1, 0, bias=False),
)
### FFN
self.norm = nn.LayerNorm(dim, eps=1e-6)
self.norm2 = nn.LayerNorm(dim, eps=1e-6)
self.mlp =TransformerMLPWithConv(dim, self.expain_ratio, 0.)
@torch.no_grad()
def _get_ref_points(self, H_key, W_key, B, dtype, device):
ref_y, ref_x = torch.meshgrid(
torch.linspace(0.5, H_key - 0.5, H_key, dtype=dtype, device=device),
torch.linspace(0.5, W_key - 0.5, W_key, dtype=dtype, device=device)
)
ref = torch.stack((ref_y, ref_x), -1)
ref[..., 1].div_(W_key).mul_(2).sub_(1)
ref[..., 0].div_(H_key).mul_(2).sub_(1)
ref = ref[None, ...].expand(B * self.n_groups, -1, -1, -1) # B * g H W 2
return ref
@torch.no_grad()
def _get_q_grid(self, H, W, B, dtype, device):
ref_y, ref_x = torch.meshgrid(
torch.arange(0, H, dtype=dtype, device=device),
torch.arange(0, W, dtype=dtype, device=device),
indexing='ij'
)
ref = torch.stack((ref_y, ref_x), -1)
ref[..., 1].div_(W - 1.0).mul_(2.0).sub_(1.0)
ref[..., 0].div_(H - 1.0).mul_(2.0).sub_(1.0)
ref = ref[None, ...].expand(B * self.n_groups, -1, -1, -1) # B * g H W 2
return ref
def forward(self, x, ret_attn_mask=False):
x = x.permute(0, 2, 3, 1) # (N, C, H, W) -> (N, H, W, C)
dtype, device = x.dtype, x.device
"""
x: NHWC tensor
Return:
NHWC tensor
"""
# NOTE: use padding for semantic segmentation
###################################################
if self.auto_pad:
N, H_in, W_in, C = x.size()
pad_l = pad_t = 0
pad_r = (self.n_win - W_in % self.n_win) % self.n_win
pad_b = (self.n_win - H_in % self.n_win) % self.n_win
x = F.pad(x, (0, 0, # dim=-1
pad_l, pad_r, # dim=-2
pad_t, pad_b)) # dim=-3
_, H, W, _ = x.size() # padded size
else:
N, H, W, C = x.size()
assert H%self.n_win == 0 and W%self.n_win == 0 #
#print("X_in")
#print(x.shape)
###################################################
#q=self.proj_q_def(x)
x_res = rearrange(x, "n h w c -> n c h w")
#################qkv projection###################
q,kv = self.qkv_conv(x.permute(0, 3, 1, 2))
q_bi = rearrange(q, "n c (j h) (i w) -> n (j i) h w c", j=self.n_win, i=self.n_win)
kv = rearrange(kv, "n c (j h) (i w) -> n (j i) h w c", j=self.n_win, i=self.n_win)
q_pix = rearrange(q_bi, 'n p2 h w c -> n p2 (h w) c')
kv_pix = self.kv_down(rearrange(kv, 'n p2 h w c -> (n p2) c h w'))
kv_pix = rearrange(kv_pix, '(n j i) c h w -> n (j i) (h w) c', j=self.n_win, i=self.n_win)
##################side_dwconv(lepe)##################
# NOTE: call contiguous to avoid gradient warning when using ddp
lepe1 = self.lepe1(rearrange(kv[..., self.qk_dim:], 'n (j i) h w c -> n c (j h) (i w)', j=self.n_win, i=self.n_win).contiguous())
################################################################# Offset Q
q_off = rearrange(q, 'b (g c) h w -> (b g) c h w', g=self.n_groups, c=self.n_group_channels)
offset_q = self.conv_offset_q(q_off).contiguous() # B * g 2 Sg HWg
Hk, Wk = offset_q.size(2), offset_q.size(3)
n_sample = Hk * Wk
if self.offset_range_factor > 0:
offset_range = torch.tensor([1.0 / Hk, 1.0 / Wk], device=device).reshape(1, 2, 1, 1)
offset_q = offset_q.tanh().mul(offset_range).mul(self.offset_range_factor)
offset_q = rearrange(offset_q, 'b p h w -> b h w p') # B * g 2 Hg Wg -> B*g Hg Wg 2
reference = self._get_ref_points(Hk, Wk, N, dtype, device)
if self.offset_range_factor >= 0:
pos_k = offset_q + reference
else:
pos_k = (offset_q + reference).clamp(-1., +1.)
x_sampled_q = F.grid_sample(
input=x_res.reshape(N * self.n_groups, self.n_group_channels, H, W),
grid=pos_k[..., (1, 0)], # y, x -> x, y
mode='bilinear', align_corners=True) # B * g, Cg, Hg, Wg
q_sampled = x_sampled_q.reshape(N, C, Hk, Wk)
######## Bi-LEVEL Gathering
if self.auto_pad:
q_sampled=q_sampled.permute(0, 2, 3, 1)
Ng, Hg, Wg, Cg = q_sampled.size()
pad_l = pad_t = 0
pad_rg = (self.n_win - Wg % self.n_win) % self.n_win
pad_bg = (self.n_win - Hg % self.n_win) % self.n_win
q_sampled = F.pad(q_sampled, (0, 0, # dim=-1
pad_l, pad_rg, # dim=-2
pad_t, pad_bg)) # dim=-3
_, Hg, Wg, _ = q_sampled.size() # padded size
q_sampled=q_sampled.permute(0, 3, 1, 2)
lepe1 = F.pad(lepe1.permute(0, 2, 3, 1), (0, 0, # dim=-1
pad_l, pad_rg, # dim=-2
pad_t, pad_bg)) # dim=-3
lepe1=lepe1.permute(0, 3, 1, 2)
pos_k = F.pad(pos_k, (0, 0, # dim=-1
pad_l, pad_rg, # dim=-2
pad_t, pad_bg)) # dim=-3
queries_def = self.proj_q(q_sampled) #Linnear projection
queries_def = rearrange(queries_def, "n c (j h) (i w) -> n (j i) h w c", j=self.n_win, i=self.n_win).contiguous()
q_win, k_win = queries_def.mean([2, 3]), kv[..., 0:(self.qk_dim)].mean([2, 3])
r_weight, r_idx = self.router(q_win, k_win)
kv_gather = self.kv_gather(r_idx=r_idx, r_weight=r_weight, kv=kv_pix) # (n, p^2, topk, h_kv*w_kv, c )
k_gather, v_gather = kv_gather.split([self.qk_dim, self.dim], dim=-1)
### Bi-level Routing MHA
k = rearrange(k_gather, 'n p2 k hw (m c) -> (n p2) m c (k hw)', m=self.num_heads)
v = rearrange(v_gather, 'n p2 k hw (m c) -> (n p2) m (k hw) c', m=self.num_heads)
q_def = rearrange(queries_def, 'n p2 h w (m c)-> (n p2) m (h w) c',m=self.num_heads)
attn_weight = (q_def * self.scale) @ k
attn_weight = self.attn_act(attn_weight)
out = attn_weight @ v
out_def = rearrange(out, '(n j i) m (h w) c -> n (m c) (j h) (i w)', j=self.n_win, i=self.n_win, h=Hg//self.n_win, w=Wg//self.n_win).contiguous()
out_def = out_def + lepe1
out_def = self.unifyheads1(out_def)
out_def = q_sampled + out_def
out_def = out_def + self.mlp(self.norm2(out_def.permute(0, 2, 3, 1)).permute(0, 3, 1, 2)) # (N, C, H, W)
#############################################################################################
######## Deformable Gathering
#############################################################################################
out_def = self.norm(out_def.permute(0, 2, 3, 1)).permute(0, 3, 1, 2)
k = self.proj_k(out_def)
v = self.proj_v(out_def)
k_pix_sel = rearrange(k, 'n (m c) h w -> (n m) c (h w)', m=self.num_heads)
v_pix_sel = rearrange(v, 'n (m c) h w -> (n m) c (h w)', m=self.num_heads)
q_pix = rearrange(q, 'n (m c) h w -> (n m) c (h w)', m=self.num_heads)
attn = torch.einsum('b c m, b c n -> b m n', q_pix, k_pix_sel) # B * h, HW, Ns
attn = attn.mul(self.scale)
### Bias
rpe_table = self.rpe_table
rpe_bias = rpe_table[None, ...].expand(N, -1, -1, -1)
q_grid = self._get_q_grid(H, W, N, dtype, device)
displacement = (q_grid.reshape(N * self.n_groups, H * W, 2).unsqueeze(2) - pos_k.reshape(N * self.n_groups, Hg*Wg, 2).unsqueeze(1)).mul(0.5)
attn_bias = F.grid_sample(
input=rearrange(rpe_bias, 'b (g c) h w -> (b g) c h w', c=self.n_group_heads, g=self.n_groups),
grid=displacement[..., (1, 0)],
mode='bilinear', align_corners=True) # B * g, h_g, HW, Ns
attn_bias = attn_bias.reshape(N * self.num_heads, H * W, Hg*Wg)
attn = attn + attn_bias
###
attn = F.softmax(attn, dim=2)
out = torch.einsum('b m n, b c n -> b c m', attn, v_pix_sel)
out = out.reshape(N,C,H,W).contiguous()
out = self.proj_out(out).permute(0,2,3,1)
#############################################################################################
# NOTE: use padding for semantic segmentation
# crop padded region
if self.auto_pad and (pad_r > 0 or pad_b > 0):
out = out[:, :H_in, :W_in, :].contiguous()
out = out.permute(0, 3, 1, 2) # (N, H, W, C) -> (N, C, H, W)
if ret_attn_mask:
return out, r_weight, r_idx, attn_weight
else:
return out