一、RGA关系感知全局注意力模型概述
RGA关系感知全局注意力是中国科学技术大学和微软亚洲研究院在2020年CVPR提出的一篇基于全局注意力的行人重识别文章。行人重识别(re-ID)的目的就是在一个或者多个摄像机拍摄的不同场合下去匹配特定的人。通常给定输入图像,我们使用卷积神经网络来获取特征向量。再识别是通过匹配图像特征向量来找到具有相同身份的图像(基于特征距离)。对于CNN,注意力通常是局部卷积学习到的,而局部卷积会忽略全局信息和隐藏信息的关系。文章提出有效的关系感知全局注意力(RGA)模块使CNN能够充分利用全局的相关性来推断注意力。通过全局考虑特征之间的相互关系来确定注意力,对于每一个特征节点,通过堆叠关于该节点的成对关系和特征本身,提出了一种紧凑的表示。将RGA模块应用于空间和通道维度并且进行组合可以得到更好的效果。
二、RGA整体网络架构—如何利用网络进行预测
行人重识别(re-ID)系统通常的做法就是通过主干特征提取网络(文章中采用了ResNet50)输入图像的特征向量表示,然后基于距离来进行图像匹配找到具有相同身份的图像。而本文的关系感知全局注意力模型是将特有的RGA模块应用于ResNet50主干网络,然后得到图像的特征向量表示。具体做法,就是在ResNet50的每一个残差块即res_layer1,res_layer2,res_layer3,res_layer4之后添加RGA模块,RGA模块包含了基于空间RGA-S和通道维度RGA-C的注意力机制。RGA网络的具体实现如下图所示:
需要注意的是,ResNet50的res4_layer中的最后一个空间下采用操作被删除,我们将其步长stride设置为1。
1、主干特征提取网络—ResNet50
ResNet残差网络有两个最主要的基本块,我们称之为Conv Block和Identity Block。Conv Block和Identity Block的结构如下:
图片转自https://blog.csdn.net/weixin_44791964/article/details/104629135。
可以看到在Conv Block中,输入input在左右两边进行的操作的是不一样的,左边先进行了两次卷积块,一次卷积块即Conv+BN+Relu,然后又进行了Conv+BN的操作;而右边只进行了一次的Conv+BN;最后是将两边的输出进行相加Add和Relu激活函数操作;在Conv Block中输入和输出的shape可以是不一样的,因此Conv Block是不可以连续的串联,它是用来改变网络的维度;而Identity Block中的左边的操作和Conv Block是一样的,而右边输入input不进行任何的操作,因此在Identity Block中输入和输出的shape是一样的,可以进行连续多次的串联,用于加深网络的深度。ResNet网络就是由Conv Block和Identity Block不断重复,ResNet网络不仅能达到很深,而且每次Block提取到的特征总是不比原来差!!!
具体ResNet50特征提取代码如下:
# Networks
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)
self.bn1 = nn.BatchNorm2d(64)
self.relu = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
self.layer1 = self._make_layer(block, 64, layers[0])
self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
self.layer4 = self._make_layer(block, 512, layers[3], stride=last_stride)
# 残差卷积块的具体实现
def _make_layer(self, block, channels, blocks, stride=1):
downsample = None
if stride != 1 or self.in_channels != channels * block.expansion:
downsample = nn.Sequential(
nn.Conv2d(self.in_channels, channels * block.expansion,
kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(channels * block.expansion),
)
layers = []
layers.append(block(self.in_channels, channels, stride, downsample))
self.in_channels = channels * block.expansion
for i in range(1, blocks):
layers.append(block(self.in_channels, channels))
return nn.Sequential(*layers)
2、RGA Module 关系感知全局注意力模块
为实现注意力,就要对特征的重要性有很好的认识,要对其进行客观评估。因此,全局信息至关重要,提出的关系感知全局注意力模块,能够充分利用结构的相关信息。
通常对于具有d维的N个相关特征的特征集
v={xi ∈ \in ∈Rd,i=1,…,N},我们的目标是学习一个mask,其有a=(a1,…,aN) ∈ \in ∈RN表示N个特征的权重,根据它们的重要性进行加权,更新第i个特征向量为zi=ai*xi。要学习第i个特征向量的注意力值ai,有两种常见的策略,如下图(a)和(b)。
(a)局部注意力:每个特征都决定其局部注意力,例如对自身使用共享变换函数 Γ \Gamma Γ,即ai= Γ \Gamma Γ(x)。这种局部策略不能完全利用与其它特征的相关性。关于全局注意力,一种方式就是使用更大kernel size的卷积使注意力的学习更加全局化;
另一种方式,如图(b):通过全连接的操作共同学习注意力。但是这通常在计算上很昂贵,并且需要大量的参数,尤其是当特征数量N较大时。
本文提出的关系感知全局注意力,如图( c )所示。其主要思想就是利用与第i个特征相关的成对关系(例如,相似性),来表示该特征节点的全局结构信息。特别地,使用Ri,j表示第i个特征和第j个特征之间的相似性。则对于第i个特征xi,其相似性向量为Ri=[Ri,1,…,Ri,N,R1,i,…,RN,i]。如何理解该Ri,Ri,1,…,Ri,N表示的是第i个特征与其它特征之间的关系,而R1,i,…,RN,i则表示的是其它特征与第i个特征之间的关系。听上去感觉有点重复?但是论文中解释这样加上去效果会好,而我自己的理解就是,“我(i)认为我跟别人(1,…,N)是好朋友,但是别人(1,…,N)却不一定认为跟我(i)是好朋友”,即Ri,1,…,Ri,N和R1,i,…,RN,i表示的相似性可能是不同的。
1、Spatial Relation-Aware Attention 空间关系感知注意力—RGA-S
通过CNN层得到的特征图为的shape为HWC,设计的RGA-S空间关系感知注意力来学习大小为H*W的空间注意力图,取每个空间位置的C维特征向量作为特征节点。所有的空间形成位置形成一个有N=WxH个节点的图形,通过建立空间中节点之间的相似性矩阵,即NxN的矩阵,来表示节点之间的成对关系。
RGA-S的实现如下图所示:
为了方便解释RGA-S如何具体实现,我们以经过第一个残差块res1_layer的RGA-S1为例来给予说明,其它的RGA-S实现于此一样,只不过是特征图的H和W不一样而已。经过数据增强输入图片的shape为3x256x128,首先经过Zeropadding,然后是步长stride=2的卷积块(Conv+BN+Relu),得到的shape为64x128x64,再经过stride=2的MaxPooling,得到的shape为64x64x32,经过残差网络的第一个残差块,其中的卷积步长stride=1,得到res1_layer的shape为256x64x32,作为RGA-S1的输入。
RGA-S1的输入input为256x64x32,两个去向,如上图所示,一个是向右做embedding操作,即嵌入全局信息。具体做法是经过一个1x1卷积进行降维,将通道数减少为 256/8 =32,得到g_xs,此时shape为32x64x32,由于我们需要实现的是空间注意力机制,因此沿着通道数方向,进行mean求平均操作,将64维通道数使用其均值进行替代,此时g_xs的shape维1x64x32。第二个去向向下的操作,首先经过一个1x1卷积进行降维,将通道数减少为 256/8 =32,此时shape为32x64x32,记为theta_xs;进行了两次这样的操作,另一个记为phi_xs,shape同样为32x64x32。然后我们将theta_xs和phi_xs进行reshape操作,reshape为32x(64x32)=32x2048,然后再将theta_xs经过一次维度的调换permute,故此时的shape为2048x32,而phi_xs的shape为32x2048,于是将theta_xs和phi_xs进行矩阵的乘法,得到Gs,shape为2048x2048,至此Gs表示的就是该特征图空间中2048个特征节点之间的成对关系。首先找到我和别人的成对关系,将Gs进行reshape操作得到Gs_out,shape为 2048,64,32。其次,找到别人和我之间的成对关系,于是将Gs进行维度调换permute操作,得到的shape为2048x2048,再进行reshape操作得到Gs_in,shape为2048,64,32,进行关系对的堆叠cat操作,得到Gs_joint,shape为2048+2048=4096,64,32,然后再将Gs_joint进行1x1操作将4096浓缩成2048/8=256, 使用256维来代表空间成对关系,此时Gs_joint的shape变为256,64,32。于是,将全局的信息与空间中特征点之间的关系进行堆叠操作,得到ys,shape为257,64,32,再将关系维度使用1x1卷积进行浓缩,先将维度压缩成257/8=32,再使用1x1卷积压缩为1,shape为1,64,32,即我们得到了1个特征图上空间中特征节点之间的关系,输入input的shape为256,64,32,我们直到空间中的特征节点在即使在不同的特征图中其位置是不变的,于是我们将上述得到的一个特征节点关系特征图进行repeat操作,得到256个关系特征图,shape变为1,64,32->256,64,32,然后求sigmoid,转化为0-1之间的概率值,最后于输入input进行相乘操作,得到最后的输出256,64,32,即输出带有空间注意力的特征节点值。
空间关系感知全局注意力的具体代码实现如下:
if self.use_spatial:
self.gx_spatial = nn.Sequential(
nn.Conv2d(in_channels=self.in_channel, out_channels=self.inter_channel,
kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(self.inter_channel),
nn.ReLU())
if self.use_channel:
self.gx_channel = nn.Sequential(
nn.Conv2d(in_channels=self.in_spatial, out_channels=self.inter_spatial,
kernel_size=1, stride=1, padding=0