1简介
谷歌Landmark Dataset v2 (GLDv2)已经成为一个流行的数据集,用于评估用于解决大规模实例级识别任务的架构和方法的性能。原始数据集由超过500万张图片和超过20万个类组成。数据集还提出了一些有趣的挑战,如类的长尾分布、类内变动性和噪声标签。
2019年的获奖方案带来了GLDv2的清洁版本,这是一个子集,包含150万张图像,包含81313个类,将在下面用GLDv2c表示。此外,GLDv2x将用于表示GLDv2的子集,该子集被限制为GLDv2c中存在但未被清除的81313个类。
对于识别,是关于对一组测试图像中的地标进行正确分类,其中有大量的非地标被用作噪声数据。采用全局平均精度(GAP)作为度量指标进行评价。
对于检索,任务是在数据库中找到与给定查询图像相关的类似图像,并使用mean Average Precision@100 (mAP)进行评估。
2本文方法
2.1 DOLG模型
DOLG是一种用于端到端图像检索的信息融合框架(正交Local and Global, DOLG)。该算法首先利用多尺度卷积和自注意力方法集中提取具有代表性的局部信息。然后从局部信息中提取与全局图像表示正交的分量。最后,将正交分量与全局表示法进行互补连接,然后进行聚合生成最终的表征。
图1
基于ResNet50的DOLG模型的Pytorch实现如下:
class DolgNet(LightningModule): def __init__(self, input_dim, hidden_dim, output_dim, num_of_classes): super().__init__() self.cnn = timm.create_model( 'resnet101', pretrained=True, features_only=True, in_chans=input_dim, out_indices=(2, 3) ) self.orthogonal_fusion = OrthogonalFusion() self.local_branch = DolgLocalBranch(512, hidden_dim) self.gap = nn.AdaptiveAvgPool2d(1) self.gem_pool = GeM() self.fc_1 = nn.Linear(1024, hidden_dim) self.fc_2 = nn.Linear(int(2*hidden_dim), output_dim) self.criterion = ArcFace( in_features=output_dim, out_features=num_of_classes, scale_factor=30, margin=0.15, criterion=nn.CrossEntropyLoss() ) def forward(self, x): output = self.cnn(x) local_feat = self.local_branch(output[0]) # ,hidden_channel,16,16 global_feat = self.fc_1(self.gem_pool(output[1]).squeeze()) # ,1024 feat = self.orthogonal_fusion(local_feat, global_feat) feat = self.gap(feat).squeeze() feat = self.fc_2(feat) return feat
1、Local Branch
Local Branch的2个主要构建模块是Multi-Atrous卷积层和Self-Attention模块。前者是模拟特征金字塔,可以处理不同图像实例之间的比例变化,后者是利用注意力机制进行建模。这个分支的详细网络配置如图2所示。
- Multi-Atrous卷积模块包含3个空洞卷积,以获取具有不同空间感受野的特征映射和一个全局平均池化分支。
- 将Multi-Atrous卷积输出特征cat起来,然后经过1x1卷积层处理。
- 将输出的特征传递给Self-Att模块建模每个局部特征点的重要性。
- 首先使用CNN(1x1)-BN模块对其输入进行处理
- 然后通过1x1卷积层生成的注意力映射
- 再通过SoftPlus操作以及L2-Norm对后续特征进行归一化
Local Branch的配置。“Ds, c, k”表示空洞率为s,输出通道数为c,kernel-size为k的空洞卷积。“c, c, k”表示输入通道为c,输出通道为c,kernel-size为1的普通卷积。“R”、“B”和“S”分别代表ReLU、BN和Softplus。
Local Branch的Pytorch实现
class MultiAtrous(nn.Module): def __init__(self, in_channel, out_channel, size, dilation_rates=[3, 6, 9]): super().__init__() self.dilated_convs = [ nn.Conv2d(in_channel, int(out_channel/4), kernel_size=3, dilation=rate, padding=rate) for rate in dilation_rates ] self.gap_branch = nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Conv2d(in_channel, int(out_channel/4), kernel_size=1), nn.ReLU(), nn.Upsample(size=(size, size), mode='bilinear') ) self.dilated_convs.append(self.gap_branch) self.dilated_convs = nn.ModuleList(self.dilated_convs) def forward(self, x): local_feat = [] for dilated_conv in self.dilated_convs: local_feat.append(dilated_conv(x)) local_feat = torch.cat(local_feat, dim=1) return local_feat class DolgLocalBranch(nn.Module): def __init__(self, in_channel, out_channel, hidden_channel=2048): super().__init__() self.multi_atrous = MultiAtrous(in_channel, hidden_channel, size=int(Config.image_size/8)) self.conv1x1_1 = nn.Conv2d(hidden_channel, out_channel, kernel_size=1) self.conv1x1_2 = nn.Conv2d(out_channel, out_channel, kernel_size=1, bias=False) self.conv1x1_3 = nn.Conv2d(out_channel, out_channel, kernel_size=1) self.relu = nn.ReLU() self.bn = nn.BatchNorm2d(out_channel) self.softplus = nn.Softplus() def forward(self, x): local_feat = self.multi_atrous(x) local_feat = self.conv1x1_1(local_feat) local_feat = self.relu(local_feat) local_feat = self.conv1x1_2(local_feat) local_feat = self.bn(local_feat) attention_map = self.relu(local_feat) attention_map = self.conv1x1_3(attention_map) attention_map = self.softplus(attention_map) local_feat = F.normalize(local_feat, p=2, dim=1) local_feat = local_feat * attention_map return local_feat
2、Orthogonal Fusion Module
以和为输入,计算每个局部特征点在全局特征上的投影。在数学上,投影可以表示为:
其中,是点积运算,是的L2范数:
如图3所示,正交分量是局部特征与其投影向量的差值,因此可以通过以下方法得到正交于的分量:
这样,可以提取出一个C×H×W张量,其中每个点都与正交。然后,在这个张量的每个点上加上C×1向量,然后这个新的张量被聚合成的向量。最后,使用一个FC层来生成512×1的特征向量。
这里,利用池化操作来聚合cat的张量,也就是说,图4a中的“A”是当前实现中的池化操作。实际上,它可以被设计成其他可学习的模块来聚合张量。
Orthogonal Fusion Module的Pytorch实现
class OrthogonalFusion(nn.Module): def __init__(self): super().__init__() def forward(self, local_feat, global_feat): global_feat_norm = torch.norm(global_feat, p=2, dim=1) projection = torch.bmm(global_feat.unsqueeze(1), torch.flatten( local_feat, start_dim=2)) projection = torch.bmm(global_feat.unsqueeze( 2), projection).view(local_feat.size()) projection = projection / \ (global_feat_norm * global_feat_norm).view(-1, 1, 1, 1) orthogonal_comp = local_feat - projection global_feat = global_feat.unsqueeze(-1).unsqueeze(-1) return torch.cat([global_feat.expand(orthogonal_comp.size()), orthogonal_comp], dim=1)






