Person_reID_baseline_pytorch 源码解析之 evaluate_gpu.py

脚本 evaluate_gpu.py 被用来评估行人重识别模型的效果。evaluate_gpu.py 根据 test.py 提取到的图片特征,将图片按照相似度排序,然后根据排序结果计算 mAP 和 rank-k 等评估指标。

1. 计算 Cosine similarity

Cosine similarity 就是余弦相似度,有时也与余弦距离混用,用夹角余弦来度量两个向量方向上的差异。两个向量之间的夹角余弦越小,向量间的方向就越一致。当夹角为零时,两个向量同向。向量间的余弦相似度值越大,向量就越相似。

欧式距离也常被用来进行相似度度量,计算公式如下所示。
在这里插入图片描述
相比欧式距离,余弦相似度更注重方向上的差异而不是距离。

余弦相似度在高维的情况下依然保持“相同时为1,正交时为0,相反时为-1”的性质。
欧式距离体现数值上的绝对差异,而余弦距离体现方向上的相对差异。

因此,在深度学习中,经常使用余弦相似度计算向量间相似度。余弦相似度的计算方法如下:
在这里插入图片描述
evaluate_gpu.py 中就使用了余弦相似度计算向量间相似度。evaluate_gpu.py 使用通过 test.py 提取到的 L2 归一化的特征进行评估计算。因此 cos(θ) 的分母部分相当于为 1 ,只需要进行向量乘法。

下面给出相似度计算部分代码,并进行逐行解析。

# Evaluate
def evaluate(qf,ql,qc,gf,gl,gc):
	# 改变 query 特征的shape,将最后一维置为 1
    query = qf.view(-1,1)
    print(query.shape)
    # 计算 gallery 和 query 特征的余弦相似度,即计算矩阵 gf 和 query 的矩阵乘法
    score = torch.mm(gf,query)
    score = score.squeeze(1).cpu()
    score = score.numpy()
    # 向量按照余弦相似度值从小到大排序
    # argsort 返回从小到大排序的数组索引
    index = np.argsort(score)  #from small to large
    # 余弦相似度值越大,向量就越相似
    # 余弦相似度值倒序,按照向量间的相似性从大到小排列,
    # 数组中最开始的 gallery feature 和 query feature 最相似
    index = index[::-1]

2. 计算 mAP 和 rank-k

mAP 和 rank-k 是行人重识别领域常用的评估指标。其中,算法给出的被击中的靠前样本对 rank-k 影响非常大。
rank-k 其实只考虑了第一个被检索击中的样本在排序列表中的位置。

在这里插入图片描述

以 rank-k 中的 k 作为横轴, rank-k 的准确率作为纵轴,可以绘制出 CMC 曲线。CMC 曲线是单调上升的曲线,因为 rank-k 的准确率会大于 rank-k - 1 的准确率。例如:rank-10 的准确率会大于 rank-5 的准确率。
在这里插入图片描述

从某种意义上来说,rank-k 只考虑了最前面最简单的样本。因为最先被击中检索到的一般都是简单的样本,这样就无法考量行人重识别模型在困难样本上的表现。因此,引入 mAP 指标来综合评估行人重识别模型, mAP 可以全面衡量行人重识别模型的性能。

在这里插入图片描述
下面给出计算指标 mAP 和 rank-k 的代码,并进行逐行解析。
行人重识别是一个跨镜检索的问题,因此由拍摄到的 query 图片同一个摄像头捕获到的 gallery 图片,不能算作行人重识别的有效结果。
此外,拍摄不完整的 gallery 图片,也不能算作行人重识别的有效结果。
只有 query 图片和 gallery 图片来自不同的摄像头,且 label 相同,才可以被认为是行人重识别的有效结果。

因此首先需要将向量按照相似度排序,区分有效索引和无效索引。有效索引是行人重识别的有效结果在相似度排序数组中的索引,无效索引是行人重识别的无效结果在相似度排序数组中的索引。

    # good index 有效的索引
    # query_index:gallery label 和 query label 相等
    query_index = np.argwhere(gl==ql)
    # camera_index:gallery 和 query 来自同一个摄像头
    camera_index = np.argwhere(gc==qc)
	# 有效的索引: gallery label 和 query label 相等,但 gallery 和 query 来自不同的摄像头
    good_index = np.setdiff1d(query_index, camera_index, assume_unique=True)
    # 无效的索引:gallery feature 提取不完全
    junk_index1 = np.argwhere(gl==-1)
    # 无效的索引:gallery label 和 query label 相等,但 gallery 和 query 来自相同的摄像头
    junk_index2 =  (query_index, camera_index)
    junk_index = np.append(junk_index2, junk_index1) #.flatten())
    # 计算 mAP 和 cmc
    CMC_tmp = compute_mAP(index, good_index, junk_index)
    return CMC_tmp

计算索引的过程中,涉及到了一些 numpy 函数,具体可以参见 np.intersect1d , np.setdiff1d ,np.argwhere, np.argsort 的使用
numpy.in1d、numpy.where的使用 。其中,np.setdiff1d 用来求 query_index 和 camera_index 的差集,如下图所示。从 gallery label 和 query label 相等的集合 query_index 中,去掉 gallery 和 query 来自相同摄像头的集合 camera_index 。
在这里插入图片描述
还记得之前计算得到的无效索引吗?在计算 mAP 和 rank-k 时,不考虑无效索引。因此需要去除无效索引。
有效索引就是被检索击中的样本索引,因此将 cmc 数组有效索引的值置为 1 :cmc[rows_good[0]:] = 1,cmc 数组其他值默认为 0 。

def compute_mAP(index, good_index, junk_index):
    ap = 0
    cmc = torch.IntTensor(len(index)).zero_()
    if good_index.size==0:   # if empty
        cmc[0] = -1
        return ap,cmc

    # remove junk_index
    mask = np.in1d(index, junk_index, invert=True)
    index = index[mask]

    # find good_index index
    ngood = len(good_index)
    mask = np.in1d(index, good_index)
    # argwhere 返回满足要求的索引
    rows_good = np.argwhere(mask==True)
    # flatten 展开成一维数组
    rows_good = rows_good.flatten()

上面的 np.in1d(index, good_index) 将返回一个和 index 大小相同的 bool 数组。index 和 good_index 相等的索引处,bool 数组值为 True,否则为 False 。

    cmc[rows_good[0]:] = 1
    for i in range(ngood):
        d_recall = 1.0/ngood
        precision = (i+1)*1.0/(rows_good[i]+1)
        if rows_good[i]!=0:
            old_precision = i*1.0/rows_good[i]
        else:
            old_precision=1.0
        ap = ap + d_recall*(old_precision + precision)/2

    return ap, cmc

上述代码给出了每个 query 样本的 ap 和 rank-k 值。
对所有 query 样本的 ap 和 rank-k 值做累加并求平均,得到评估指标 mAP 和 rank-k 。

CMC = torch.IntTensor(len(gallery_label)).zero_()
ap = 0.0
#print(query_label)
for i in range(len(query_label)):
    ap_tmp, CMC_tmp = evaluate(query_feature[i],query_label[i],query_cam[i],gallery_feature,gallery_label,gallery_cam)
    if CMC_tmp[0]==-1:
        continue
    CMC = CMC + CMC_tmp
    ap += ap_tmp
    #print(i, CMC_tmp[0])

CMC = CMC.float()
CMC = CMC/len(query_label) #average CMC
print('Rank@1:%f Rank@5:%f Rank@10:%f mAP:%f'%(CMC[0],CMC[4],CMC[9],ap/len(query_label)))

3. 评价模式

行人重识别的评价模式包括 single 模式和 multi 模式。其中,single shot single query 是最常用且最贴近实际的评价模式。
在这里插入图片描述
在这里插入图片描述

参考链接

  1. 为什么用余弦相似度,而不是欧式距离?
  2. 相似度度量:欧氏距离与余弦相似度(Similarity Measurement Euclidean Distance Cosine Similarity)
  3. B 站《深度学习和行人重识别》浙江大学罗浩博士
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值