基于Attention的行人重识别项目实战--Relation-Aware Global Attention for Person Re-identification
数据及代码链接见文末
1.项目环境与数据集配置
为本项目环境设置,建议我们先安装好pytorch库。项目建议采用港中文大学数据集,源码中包含经处理的港中文大学数据集第三版版本。

最初下载的文件仅包含release文件夹,其中仅有一个.Mat文件夹,我们需要从中提取其中的所有信息,以便全面了解项目状态。

在目标检测算法的作用下,detected文件夹和labeled文件夹分别代表两种类型的信息。其中,detected文件夹中的数据是通过目标检测算法自动识别出的行人图像,而labeled文件夹中的数据则是由人工标注完成的。
数据是由6组摄像头采集的,每位用户有10张图片,这些图片是通过一对摄像头获取的。其中,第一个数字1代表摄像头ID,001则表示该用户的编号。接着的1位数字表示这对摄像头的ID,最后的两位数字01则表示该用户图片的序号。

2.参数配置和整体架构分析
参数配置,参数配置如下,需要在训练时进行指定:
-a resnet50_rga 预训练模型
-b 16 batch_size
-d cuhk03labeled 数据集
-j 0 多线程,windows建议设置为0
--opt adam
--dropout 0
--combine-trainval
--seed 16
--num_gpu 1
--epochs 600
--features 2048
--start_save 300
--branch_name rgasc
--data-dir ./data
--logs-dir ./logs/RGA-SC/cuhk03labeled_b64f2048
AI助手
3.整体网络流程
(1)特征提取模块
该领域的研究结构如前所述,行人重识别论文致力于解决行人重识别数据所面临的各种挑战,该研究具有通用性。通过在resnet中引入了多种空间和通道注意力机制,以实现特征的更有效的提取。
在整体架构中,每个残差模块之后均采用注意力模块,从而从空间维度和通道层面突出关键特征。

注意力模块
该模块包含空间注意力和通道注意力,通过在空间域和通道域对原始特征图进行加权分配
空间注意力的整体流程如下:
通道注意力流程与空间注意力流程基本一致,仅在关注对象上存在差异:前者聚焦于通道维度,而后者则作用于空间维度。在通道维度上进行加权处理。

代码如下:
# ===================
# RGA Module
# ===================
class RGA_Module(nn.Module):
def __init__(self, in_channel, in_spatial, use_spatial=True, use_channel=True, \
cha_ratio=8, spa_ratio=8, down_ratio=8):
super(RGA_Module, self).__init__()
self.in_channel = in_channel
self.in_spatial = in_spatial
self.use_spatial = use_spatial
self.use_channel = use_channel
print ('Use_Spatial_Att: {};\tUse_Channel_Att: {}.'.format(self.use_spatial, self.use_channel))
self.inter_channel = in_channel // cha_ratio
self.inter_spatial = in_spatial // spa_ratio
# Embedding functions for original features
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, bias=False),
nn.BatchNorm2d(self.inter_spatial),
nn.ReLU()
)
# Embedding functions for relation features
if self.use_spatial:
self.gg_spatial = nn.Sequential(
nn.Conv2d(in_channels=self.in_spatial * 2, out_channels=self.inter_spatial,
kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(self.inter_spatial),
nn.ReLU()
)
if self.use_channel:
self.gg_channel = nn.Sequential(
nn.Conv2d(in_channels=self.in_channel*2, out_channels=self.inter_channel,
kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(self.inter_channel),
nn.ReLU()
)
# Networks for learning attention weights
if self.use_spatial:
num_channel_s = 1 + self.inter_spatial
self.W_spatial = nn.Sequential(
nn.Conv2d(in_channels=num_channel_s, out_channels=num_channel_s//down_ratio,
kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(num_channel_s//down_ratio),
nn.ReLU(),
nn.Conv2d(in_channels=num_channel_s//down_ratio, out_channels=1,
kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(1)
)
if self.use_channel:
num_channel_c = 1 + self.inter_channel
self.W_channel = nn.Sequential(
nn.Conv2d(in_channels=num_channel_c, out_channels=num_channel_c//down_ratio,
kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(num_channel_c//down_ratio),
nn.ReLU(),
nn.Conv2d(in_channels=num_channel_c//down_ratio, out_channels=1,
kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(1)
)
# Embedding functions for modeling relations
if self.use_spatial:
self.theta_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()
)
self.phi_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.theta_channel = nn.Sequential(
nn.Conv2d(in_channels=self.in_spatial, out_channels=self.inter_spatial,
kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(self.inter_spatial),
nn.ReLU()
)
self.phi_channel = nn.Sequential(
nn.Conv2d(in_channels=self.in_spatial, out_channels=self.inter_spatial,
kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(self.inter_spatial),
nn.ReLU()
)
def forward(self, x):
b, c, h, w = x.size()
if self.use_spatial:
# spatial attention
# 构建出两个特征图,即两个特征矩阵,以便于后续获得特征点关于全体特征点的关系
# 同时为了减少运算量,对通道数进行了降维
theta_xs = self.theta_spatial(x)#8 32 64 32
phi_xs = self.phi_spatial(x)#8 32 64 32
# 矩阵维度变换:B,H,W,C-->B,C,H*W
theta_xs = theta_xs.view(b, self.inter_channel, -1) #8 32 64*32
theta_xs = theta_xs.permute(0, 2, 1) # 8 64*32 32
phi_xs = phi_xs.view(b, self.inter_channel, -1) # 8 32 64*32
# 矩阵乘法获得了每个特征点关于所有特征点的全局关系向量(余弦相似度)
Gs = torch.matmul(theta_xs, phi_xs)# 8 2048 2048
# 调换下顺序,此时矩阵表示所有特征点关于某个特征点的关系向量,然后做拼接
Gs_in = Gs.permute(0, 2, 1).view(b, h*w, h, w) # 8 2048 64 32 调换下顺序
Gs_out = Gs.view(b, h*w, h, w) #8 2048 64 32
Gs_joint = torch.cat((Gs_in, Gs_out), 1) # 8 4096 64 32
# 使用1*1的卷积进行降维
Gs_joint = self.gg_spatial(Gs_joint) # 8 256 64 32
# 对原始特征图进行处理,求均值,将特征图维度变为1
g_xs = self.gx_spatial(x) # 8 32 64 32
g_xs = torch.mean(g_xs, dim=1, keepdim=True) # 8 1 64 32
# 关系向量与特征图进行拼接
ys = torch.cat((g_xs, Gs_joint), 1) # 8 257 64 32
# 最终,我们想要得到的是每个特征点的权值,也就是说,卷积把所有的特征点看成是相同的
# 重要性,而注意力机制是得到空间特征点的权重,以凸出重要的特征
# 首先是1*1的卷积,将通道数降为1维,然后通过sigmoid得到权重值
W_ys = self.W_spatial(ys) # 8 1 64 32
if not self.use_channel:
out = F.sigmoid(W_ys.expand_as(x)) * x # 位置特征,不同特征图,位置相同的
return out
else:
x = F.sigmoid(W_ys.expand_as(x)) * x
if self.use_channel:
# channel attention
xc = x.view(b, c, -1).permute(0, 2, 1).unsqueeze(-1) # 8 2048 256 1
theta_xc = self.theta_channel(xc).squeeze(-1).permute(0, 2, 1) # 8 256 256
phi_xc = self.phi_channel(xc).squeeze(-1) # 8 256 256
Gc = torch.matmul(theta_xc, phi_xc) # 8 256 256
Gc_in = Gc.permute(0, 2, 1).unsqueeze(-1) # 8 256 256 1
Gc_out = Gc.unsqueeze(-1) # 8 256 256 1
Gc_joint = torch.cat((Gc_in, Gc_out), 1)# 8 512 256 1
Gc_joint = self.gg_channel(Gc_joint)# 8 32 256 1
g_xc = self.gx_channel(xc)# 8 256 256 1
g_xc = torch.mean(g_xc, dim=1, keepdim=True)# 8 1 256 1
yc = torch.cat((g_xc, Gc_joint), 1)# 8 33 256 1
W_yc = self.W_channel(yc).transpose(1, 2)# 8 256 1 1 得到权重分配
out = F.sigmoid(W_yc) * x
return out
AI助手
(2)损失函数
该模型主要采用分类损失函数(交叉熵)与Triplet loss的结合,其主要目标是通过提升特征提取的质量来实现模型性能的优化。
分类损失不必多说,重点是Triplet loss。
Triplet loss要求我们从一个批次中获取三份数据,其中,P和A属于同一人,而N来自不同的人。Triplet loss的目标是使A与P的特征足够接近,同时与N的特征足够远离。通过这一机制,我们能够有效地区分同一个人与其他人的特征。

因此我们的初步的 公式为:(其中f表示通过网络进行编码)

但是,通过分析,当函数f的所有权重参数被设为0时,这一目标得以实现。进而提出优化方案。

其中,a表示间隔,表示同一个人特征差异和不同的人的特征差异的距离。
在实际使用中,我们常用以下式子:

即我们评估同一个人内部特征差异与不同个体之间的特征差异的距离,其损失值较小。然而,大多数数据都符合这一情况,那么,损失函数如何进行优化呢?
由此,我们在引入hard negative方法,也就是在选择样本的时候:
采用d(A,P)≈d(A,N)作为条件,当选择相同人物的正样本时,选取距离最远者作为正样本,选取不同人物的负样本时,选择距离最近者作为负样本。这样能给网络带来挑战,促使它进行学习。
三元组损失代码如下:
# ==============
# Triplet Loss
# ==============
class TripletHardLoss(object):
"""Modified from Tong Xiao's open-reid (https://github.com/Cysu/open-reid).
Related Triplet Loss theory can be found in paper 'In Defense of the Triplet
Loss for Person Re-Identification'."""
def __init__(self, margin=None, metric="euclidean"):
self.margin = margin
self.metric = metric
if margin is not None:
self.ranking_loss = nn.MarginRankingLoss(margin=margin)
else:
self.ranking_loss = nn.SoftMarginLoss()
def __call__(self, global_feat, labels, normalize_feature=False):
if normalize_feature:
global_feat = normalize(global_feat, axis=-1)
#print(global_feat.shape)
# 算出每个样本之间的距离,结果为8*8的矩阵
if self.metric == "euclidean":
dist_mat = euclidean_dist(global_feat, global_feat)
#print(dist_mat.shape)
elif self.metric == "cosine":
dist_mat = cosine_dist(global_feat, global_feat)
else:
raise NameError
# 难负样本挖掘
dist_ap, dist_an = hard_example_mining(
dist_mat, labels)
y = dist_an.new().resize_as_(dist_an).fill_(1)
# 三元组损失
if self.margin is not None:
loss = self.ranking_loss(dist_an, dist_ap, y)
else:
loss = self.ranking_loss(dist_an - dist_ap, y)
prec = (dist_an.data > dist_ap.data).sum() * 1. / y.size(0)
return loss
AI助手
3.测试流程
在测试阶段,首先获取一组q图像,即待查询的用户。接着,获取一组g图像,并从中识别出对应的用户。通过预训练的网络模型,从这两组图像中提取行人的特征向量。随后,计算两组图像的余弦相似度矩阵,该矩阵的大小为m×n。通过该矩阵,可以计算出对应的r1值和平均精度(AP)值。
链接:https://pan.baidu.com/s/1JD_9zea40JW--xbp-vRBoQ?pwd=6uop
提取码:6uop
