Rank-n
搜索结果中最靠前(置信度最高)的n张图有正确结果的概率
rank 1, 就是第一次命中
rank k,就是在第k次以内命中
计算过程:
- 计算rank-1的整个过程为:
步骤1:计算数据集中每个输入图像的类标签概率。
步骤2:原始标签与对应概率最大的标签进行比较,若相同为true,反之false
步骤3:统计步骤2为true的个数
rank-1准确度,即对应预测最高概率的标签与真实标签相同的个数占总个数的百分比——标签相同的个数 / 总数据个数。 - 计算rank-5的整个过程为:
步骤1: 计算数据集中每个输入图像的类标签概率。
步骤2: 对预测的类标签概率进行降序排序
步骤3: 判断真实的标签是否落在预测的top5标签里面,若存在,则标记为true,反之false
步骤4: 统计步骤3中为true的个数
CMC曲线
Cumulative Matching Characteristics,简称累计匹配特性。ReID模型的好坏可以通过CMC曲线来评价。
首先引入top-k击中概率:
top-k:第k次命中
在查询得到的结果中,与query同ID的结果第一次出现时的排名为F,那么显然,AccK在K=F时,值由0变为1。
CMC曲线的计算方法就是,把每个query的AccK曲线相加,再除以query的总数,即平均AccK
曲线。
举例:
题目:
水果emoji的数据集G:[🍍🍎🍎🍏🍍🍏🍎🍏🍍🍍🍍🍏🍎🍏🍍],数据集一共包含N=15张图片,共有M=3种ID。任意给定一组query: [🍎,🍏],从数据集G中找到和每个query最相近的5张图片。
现在有两个评价emoji之间相似程度的函数Similarity1()和Similarity2()。
对于query🍎,Similarity1()得到[🍍🍎🍎🍎🍏],Similarity2()得到[🍎🍍🍎🍎🍏]。
对于query🍏,Similarity1()得到[🍏🍎🍍🍏🍏],Similarity2()得到[🍎🍏🍏🍏🍍]。
得到的结果都按与query的相似度从左至右排列。那么Similarity1()和Similarity2()哪个得到的结果更好呢?
应该怎么度量Similarity()函数的性能?答案就是CMC和mAP。
用CMC进行评估,分别计算两个query的AccK曲线如下:
Similarity 1 2
query🍎: 0 1 1 1 1 | 1 1 1 1 1
query🍏: 1 1 1 1 1 | 0 1 1 1 1
计算:平均各个query的AccK曲线,由此我们得到这两种相似度函数的CMC曲线都是[0.5 1 1 1 1] 由于query只有两个,所以得到的CMC的值有些简单。
适合single-gallery-shot情形,因为每个gallery identity 可能存在多个instances,对于multi-gallery-shot多个真实匹配下,CMC曲线可能存在偏差。
mAP
mAP要计算多次输入的综合AP结果, 每张测试图像计算如下:
代码实现
query和gallery的距离矩阵计算:
# if resnet50, qf: [query_num, 2048], gf: [gallery_num, 2048]
m, n = qf.shape[0], gf.shape[0]
distmat = torch.pow(qf, 2).sum(dim=1, keepdim=True).expand(m, n) + \
torch.pow(gf, 2).sum(dim=1, keepdim=True).expand(n, m).t()
distmat.addmm_(mat1=qf, mat2=gf.T, beta=1, alpha=-2)
distmat = distmat.numpy()
def evaluate(distmat, q_pids, g_pids, q_camids, g_camids, max_rank):
"""
distmat: [m, n] query和gallery中两两距离矩阵
q_pids: [m, ] query中的行人id
g_pids: [n, ] gallery中的行人id
q_camids: [m, ] query中每个行人是由哪个相机拍摄的
g_camids: [n, ] gallery中每个行人是由哪个相机拍摄的
max_rank: int
"""
# dismat [m, n] 含义是query中有m张图片, 每一行共n个元素, 是query (m) 中每张图片和gallery (n)中每张图片算出的距离
num_q, num_g = distmat.shape
if num_g < max_rank:
max_rank = num_g
print(f'Note: number of gallery samples is quite small, got {num_g}')
# indices: [m, n] 输出按行进行排序的索引 (升序, 从小到大)
indices = np.argsort(distmat, axis=1)
# g_pids[indices] shape is [m, n]
# g_pids 原来是 [n, ], g_pids[indices]操作之后, 扩展到了 [m, n]
# 也就是每一行中的n个元素都按照 indices 中每一行的顺序进行了重排列
# q_pids[:, np.newaxis] shape is [m, 1]
g_pids_exp_dims, g_camids_exp_dims = g_pids[indices], g_camids[indices]
q_pids_exp_dims = np.expand_dims(q_pids, axis=1)
# matches中为 1 的表示query中和gallery中的行人id相同, 也就是表示同一个人
# matches中的结果就是和后续预测出的结果进行对比的正确label
matches = (g_pids_exp_dims == q_pids_exp_dims).astype(np.int32) # shape is [m, n]
# compute cmc curve for each query
all_cmc = []
all_ap = []
num_valid_q = 0. # number of valid query
# 遍历每一张query图片
for q_idx in range(num_q):
# q_pid, q_camid 分别都是一个数字
q_pid, q_camid = q_pids[q_idx], q_camids[q_idx]
# remove gallery samples that have the same pid and camid with query
# TODO: 这里要用 & ,因为前后都是np.ndarray类型, 如果前后都是list, 则可以使用 and
removed = (g_pids_exp_dims[q_idx] == q_pid) & (g_camids_exp_dims[q_idx] == q_camid) # [n, ]
# keep中为True的表示gallery中符合查找条件的行人图片,
# 这些为True的部分还需要借助matches才能完成正确的查找
# 且keep中从左到右就是当前查找图片和每一个gallery中图片的距离距离依次递增的顺序
keep = np.where(removed == 0, True, False) # [n, ]
# ===== compute cmc curve =====
# orig_cmc中为1的位置表示查找的图片匹配正确了
orig_cmc = matches[q_idx][keep]
# 如果orig_cmc全为0, 也就是待查询图片没有在gallery中匹配到
# 也就不计算top-n和ap值了
if np.all(orig_cmc == 0):
continue
cmc = orig_cmc.cumsum()
cmc = np.where(cmc >= 1, 1, 0)
all_cmc.append(cmc[:max_rank])
num_valid_q += 1
# ===== compute average precision =====
num_rel = orig_cmc.sum() # 在gallery的n中图片中,匹配对了多少张
tmp_cmc = orig_cmc.cumsum()
tmp_cmc = [(x / (i + 1)) for i, x in enumerate(tmp_cmc)]
tmp_cmc = np.asarray(tmp_cmc) * orig_cmc
ap = tmp_cmc.sum() / num_rel
all_ap.append(ap)
assert num_valid_q > 0, "Error: all query identity do not appear in gallery"
# all_cmc中一共有num_valid_q个元素, 其中每个元素又包含max_rank个值
# 将all_cmc按列求和, 可以得到 n 个值,然后除以 num_valid_q
all_cmc = np.asarray(all_cmc).astype(np.float32)
all_cmc = all_cmc.sum(axis=0) / num_valid_q
mAP = np.mean(all_ap)
return all_cmc, mAP