CMC曲线
CMC曲线是算top-k的击中率,主要用来评估闭集中rank的正确率。举个简单的例子,例如在人脸识别中,底库中有100个人,现在来了一个待识别的人脸(假设label为m1),与底库中的人脸对比后按照从高到低的顺序排序,我们发现:
- 如果识别结果是 m1、m2、m3、m4、m5…,则此时rank-1是100%,rank-2是100%,rank-5也是100%
- 如果识别结果是 m2、m1、m3、m4、m5…,则此时rank-1是0%,rank-2是100%,rank-5是100%
- 如果识别结果是 m2、m3、m4、m5、m1…,则此时rank-1是0%,rank-2是0%,rank-5是100%
同理,如果待识别的人脸有很多时,则采取取平均的做法,例如待识别的人脸有三个,(假设label分别是m1、m2、m3),同样对每个人脸都有一个从高到低的得分,
4. 比如人脸1结果为m1、m2、m3、m4、m5……,人脸2结果为m2、m1、m3、m4、m5……,人脸3结果m3、m1、m2、m4、m5……,则此时rank-1的正确率为(1+1+1)/3=100%;rank-2的正确率也为(1+1+1)/3=100%;rank-5的正确率也为(1+1+1)/3=100%;
5. 比如人脸1结果为m4、m2、m3、m5、m6……,人脸2结果为m1、m2、m3、m4、m5……,人脸3结果m3、m1、m2、m4、m5……,则此时rank-1的正确率为(0+0+1)/3=33.33%;rank-2的正确率为(0+1+1)/3=66.66%;rank-5的正确率也为(0+1+1)/3=66.66%;
Rank-1就是CMC中的第一个元素
mAP
mAP要计算多次输入的综合AP结果, 每张测试图像计算如下:
返回图片 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
query | 1 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 1 | 1 |
query中为1表示寻找到了正确的图片
ap = ( 1 1 \frac{1}{1} 11 + 2 3 \frac{2}{3} 32 + 3 6 \frac{3}{6} 63 + 4 9 \frac{4}{9} 94 + 5 10 \frac{5}{10} 105 ) / 5 = 0.62
依次解释上面五个数字的来源:
1
1
\frac{1}{1}
11 : 第一个正确的图片,在第一个位置
2
3
\frac{2}{3}
32 : 第二个正确的图片,在第三个位置
3
6
\frac{3}{6}
63 : 第三个正确的图片,在第六个位置
4
9
\frac{4}{9}
94 : 第四个正确的图片,在第九个位置
5
10
\frac{5}{10}
105 : 第五个正确的图片,在第十个位置
计算CMC和mAP的代码实现
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