行人重识别—Relation-Aware Global Attention模型介绍及代码实现

一、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 BlockConv 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 BlockIdentity 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
TransH嵌入模型是一种基于知识图谱的实体关系表示学习模型,它是在TransE模型的基础上发展而来的。下面是TransH嵌入模型的详细介绍及每一步骤的Python实现代码。 1. 模型介绍 TransH模型的主要思想是为每个关系定义一个超平面,将实体映射到该超平面上。这个超平面被称为关系空间,实体被映射到该空间中的一个点上。在关系空间中,实体之间的距离可以通过两个实体在该超平面上的投影点之间的欧氏距离来计算。TransH模型的目标是最小化实体和关系之间的距离,并将其映射到关系空间中。 2. 数据处理 在实现TransH模型之前,需要对原始数据进行处理。假设原始数据包含三元组(head,relation,tail),其中head和tail表示实体,relation表示实体之间的关系。首先,需要将每个实体和关系映射到一个唯一的数字ID上。然后,将三元组表示为数字ID的形式。最后,将数据分为三部分:训练集、验证集和测试集。 以下是Python代码实现: ```python import numpy as np # 将实体和关系映射到数字ID def get_entity_id(entity_list): entity_id = {} for entity in entity_list: if entity not in entity_id: entity_id[entity] = len(entity_id) return entity_id def get_relation_id(relation_list): relation_id = {} for relation in relation_list: if relation not in relation_id: relation_id[relation] = len(relation_id) return relation_id # 将三元组表示为数字ID的形式 def get_triple(triple_list, entity_id, relation_id): triple = [] for triple_str in triple_list: head, relation, tail = triple_str.strip().split('\t') triple.append((entity_id[head], relation_id[relation], entity_id[tail])) return np.array(triple) # 将数据分为训练集、验证集和测试集 def split_data(triple, ratio=(0.7, 0.2, 0.1)): train_ratio, valid_ratio, test_ratio = ratio train_size = int(len(triple) * train_ratio) valid_size = int(len(triple) * valid_ratio) test_size = len(triple) - train_size - valid_size train_triple = triple[:train_size] valid_triple = triple[train_size:train_size+valid_size] test_triple = triple[train_size+valid_size:] return train_triple, valid_triple, test_triple ``` 3. 模型训练 TransH模型的训练分为两个步骤:第一步是训练TransE模型,第二步是在TransE模型的基础上训练TransH模型。 3.1 训练TransE模型 TransE模型的目标是最小化三元组中实体和关系之间的距离。具体来说,对于三元组(h,r,t),TransE模型的目标是最小化以下损失函数: $ L = \sum_{(h,r,t) \in S} max(0, \gamma + d(h+r,t) - d(h,r,t)) $ 其中,S表示训练集,d表示欧氏距离,$\gamma$表示边界值,$d(h+r,t)$表示实体h与关系r的和向量与实体t的向量之间的欧氏距离,$d(h,r,t)$表示实体h、关系r和实体t之间的欧氏距离。 以下是Python代码实现: ```python import torch import torch.nn as nn import torch.nn.functional as F class TransE(nn.Module): def __init__(self, n_entity, n_relation, embedding_dim): super(TransE, self).__init__() self.entity_embedding = nn.Embedding(n_entity, embedding_dim) self.relation_embedding = nn.Embedding(n_relation, embedding_dim) def forward(self, head, relation, tail): head_embedding = self.entity_embedding(head) relation_embedding = self.relation_embedding(relation) tail_embedding = self.entity_embedding(tail) score = torch.norm(head_embedding + relation_embedding - tail_embedding, p=2, dim=1) return score def loss(self, score, margin=1.0): return torch.mean(F.relu(margin + score)) def predict(self, head, relation): head_embedding = self.entity_embedding(head) relation_embedding = self.relation_embedding(relation) tail_embedding = head_embedding + relation_embedding return tail_embedding ``` 3.2 训练TransH模型 在TransE模型的基础上,TransH模型为每个关系定义一个超平面,将实体映射到该超平面上。具体来说,在TransH模型中,每个关系r都有一个超平面$W_r$,该超平面由一个法向量$w_r$和一个点$p_r$确定。实体h和t在关系空间中的投影点分别为$h_r$和$t_r$。对于三元组(h,r,t),TransH模型的目标是最小化以下损失函数: $ L = \sum_{(h,r,t) \in S} max(0, \gamma + d(h_r + w_r,t_r) - d(h_r,t_r) + d(h+r,t) - d(h,t)) $ 其中,S表示训练集,$\gamma$表示边界值,$d(h_r + w_r,t_r)$表示实体h和关系r的超平面投影点的和向量与实体t的超平面投影点之间的欧氏距离,$d(h_r,t_r)$表示实体h和实体t在关系r的超平面上的投影点之间的欧氏距离,$d(h+r,t)$表示实体h与关系r的和向量与实体t的向量之间的欧氏距离,$d(h,t)$表示实体h和实体t之间的欧氏距离。 以下是Python代码实现: ```python class TransH(nn.Module): def __init__(self, n_entity, n_relation, embedding_dim): super(TransH, self).__init__() self.entity_embedding = nn.Embedding(n_entity, embedding_dim) self.relation_embedding = nn.Embedding(n_relation, embedding_dim) self.relation_hyperplane_normal = nn.Embedding(n_relation, embedding_dim) self.relation_hyperplane_point = nn.Embedding(n_relation, embedding_dim) def forward(self, head, relation, tail): head_embedding = self.entity_embedding(head) relation_embedding = self.relation_embedding(relation) tail_embedding = self.entity_embedding(tail) relation_hyperplane_normal = self.relation_hyperplane_normal(relation) relation_hyperplane_point = self.relation_hyperplane_point(relation) head_projection = head_embedding - torch.sum(head_embedding * relation_hyperplane_normal, dim=1, keepdims=True) * relation_hyperplane_normal tail_projection = tail_embedding - torch.sum(tail_embedding * relation_hyperplane_normal, dim=1, keepdims=True) * relation_hyperplane_normal relation_hyperplane_projection = relation_embedding - torch.sum(relation_embedding * relation_hyperplane_normal, dim=1, keepdims=True) * relation_hyperplane_normal score = torch.norm(head_projection + relation_hyperplane_projection - tail_projection, p=2, dim=1) return score def loss(self, score, margin=1.0): return torch.mean(F.relu(margin + score)) def predict(self, head, relation): head_embedding = self.entity_embedding(head) relation_embedding = self.relation_embedding(relation) relation_hyperplane_normal = self.relation_hyperplane_normal(relation) relation_hyperplane_point = self.relation_hyperplane_point(relation) head_projection = head_embedding - torch.sum(head_embedding * relation_hyperplane_normal, dim=1, keepdims=True) * relation_hyperplane_normal relation_hyperplane_projection = relation_embedding - torch.sum(relation_embedding * relation_hyperplane_normal, dim=1, keepdims=True) * relation_hyperplane_normal tail_embedding = head_projection + relation_hyperplane_projection return tail_embedding ``` 4. 模型评估 模型评估的主要目标是计算模型的预测准确率。具体来说,需要计算模型在验证集和测试集上的平均准确率(Mean Average Precision,MAP)和平均逆排名(Mean Reciprocal Rank,MRR)。 以下是Python代码实现: ```python def evaluate(model, triple, entity_id, relation_id, device): with torch.no_grad(): hits10 = 0 hits1 = 0 ranks = [] for head, relation, tail in triple: head = torch.tensor([head], dtype=torch.long).to(device) relation = torch.tensor([relation], dtype=torch.long).to(device) tail = torch.tensor([tail], dtype=torch.long).to(device) all_entity = torch.tensor(list(entity_id.values()), dtype=torch.long).to(device) all_entity = all_entity.unsqueeze(0).repeat(len(entity_id), 1) score = model(head, relation, all_entity) _, indices = torch.sort(score) indices = indices.cpu().numpy().tolist()[0] rank = indices.index(tail.item()) + 1 ranks.append(rank) if rank <= 10: hits10 += 1 if rank == 1: hits1 += 1 mrr = np.mean([1/rank for rank in ranks]) map = 0 for i, rank in enumerate(ranks): if rank <= 10: map += (i+1) / hits10 map /= len(ranks) return hits10/len(triple), hits1/len(triple), mrr, map ``` 以上就是TransH嵌入模型的详细介绍及每一步骤的Python实现代码
评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值