HMCLIP / retrieval_metric.py
SteveZeyuZhang's picture
Start resumable backup upload: root source files
1cc4fea
Raw History Blame Contribute Delete
17.8 kB
import torch
import numpy as np
def hard_retrieval(logits, gt_logits, k):
# treat ratescore > x as gt and calculate recall
max_indices = torch.argmax(gt_logits, dim=1)
gt_matrix = torch.zeros_like(gt_logits)
gt_matrix[torch.arange(gt_logits.size(0)), max_indices] = 1 # for each row, the position of max element is 1, others 0
# gt_matrix = torch.eye(logits.shape[0]).to(logits.device)
metrics = {}
top10 = {}
gt_ranks = {}
# a sample may have multiple gt, find the one with highest logits
gt_logit = gt_matrix * logits # [0.1, 0.3, 0.2] * [1, 0, 1] --> [0.1, 0, 0.2]
gt_rank = torch.argmax(gt_logit, dim=1).view(-1, 1) # --> 2
ranking = torch.argsort(logits, descending=True) # [0.1, 0.3, 0.2] --> [1, 2, 0] ranking logits along each row
preds = torch.where(ranking == gt_rank)[1] # [1, 2, 0] == [0] --> [2] where the gt lies in ranks
preds = preds.detach().cpu().numpy()
if isinstance(k, int):
return np.mean(preds < k)
else:
results = []
for current_k in k:
results.append(np.mean(preds < current_k))
return results
def hard_retrieval_exclude_self(logits, gt_logits, index_list, k, return_detail=False):
# treat ratescore > x as gt and calculate recall
if return_detail:
assert len(k)==1, 'must be a single k when return_detail is True'
# Exclude self elements using the provided index_list
logits = delete_elements_by_row(logits, index_list)
gt_logits = delete_elements_by_row(gt_logits, index_list)
logits = logits.detach().cpu()
gt_logits = gt_logits.detach().cpu()
# Ranking the logits in descending order for each row
ranking = torch.argsort(logits, descending=True)
# Define GT as all positions (excluding self) with gt_logits > 0.9
gt_mask = gt_logits > 0.9
# 过滤无正样本的行,只统计有GT的sample
valid_rows = gt_mask.any(dim=1)
if not valid_rows.any():
return -1 # 或无正样本时的处理
ranking = ranking[valid_rows]
gt_mask = gt_mask[valid_rows]
if return_detail:
k = k[0]
topk = ranking[:, :k]
# Check for each row if any of the topk indices is a valid GT
topk_gt = torch.gather(gt_mask, 1, topk)
hits = topk_gt.any(dim=1).float().numpy()
pred_topk_indices = ranking[:, :k].detach().cpu().numpy() # we delete them selves, so need to add 1
pred_topk_scores = logits.gather(1, ranking[:, :k]).detach().cpu().numpy()
gt_topk = torch.topk(gt_logits, k, dim=1)
gt_topk_indices = gt_topk.indices.detach().cpu().numpy()
gt_topk_scores = gt_topk.values.detach().cpu().numpy()
detail = {
"recall": [np.mean(hits)],
"prediction_topk_indices": pred_topk_indices,
"prediction_topk_scores": pred_topk_scores,
"gt_topk_indices": gt_topk_indices,
"gt_topk_scores": gt_topk_scores,
"row_recall": hits
}
return detail
else:
results = []
for current_k in k:
topk = ranking[:, :current_k]
topk_gt = torch.gather(gt_mask, 1, topk)
hits = topk_gt.any(dim=1).float().numpy()
results.append(np.mean(hits))
return results
def hard_retrieval_exclude_self_return_details(logits, gt_logits, index_list, k, return_detail=False):
# treat ratescore > x as gt and calculate recall
# to visualization, visualize retrieval details
assert len(k)==1, 'must be a single k when return_detail is True'
# Exclude self elements using the provided index_list
logits = delete_elements_by_row(logits, index_list)
gt_logits = delete_elements_by_row(gt_logits, index_list)
logits = logits.detach().cpu()
gt_logits = gt_logits.detach().cpu()
# Ranking the logits in descending order for each row
ranking = torch.argsort(logits, descending=True)
# Define GT as all positions (excluding self) with gt_logits > 0.9
gt_mask = gt_logits > 0.9
k = k[0]
topk = ranking[:, :k]
# Check for each row if any of the topk indices is a valid GT
topk_gt = torch.gather(gt_mask, 1, topk)
hits = topk_gt.any(dim=1).float().numpy()
pred_topk_indices = ranking[:, :k].detach().cpu().numpy() # we delete them selves, so need to add 1
pred_topk_scores = gt_logits.gather(1, ranking[:, :k]).detach().cpu().numpy()
gt_topk = torch.topk(gt_logits, k, dim=1)
gt_topk_indices = gt_topk.indices.detach().cpu().numpy()
gt_topk_scores = gt_topk.values.detach().cpu().numpy()
detail = {
"recall": [np.mean(hits)],
"prediction_topk_indices": pred_topk_indices,
"prediction_topk_scores": pred_topk_scores,
"gt_topk_indices": gt_topk_indices,
"gt_topk_scores": gt_topk_scores,
"row_recall": hits
}
return detail
def soft_retrieval(logits, similarity_matrix, k=[1, 5, 10]):
# calculate avg and upperbound ratescore of image 2 image retrieval results
# NOTE: do not have to exclude items themselves in logits or similarity_matrix
ranking = torch.argsort(logits, descending=True) # [0.1, 0.3, 0.2] --> [1, 2, 0] ranking logits along each row
pred_score = []
upperbound_score = []
for current_k in k:
top_ranking = ranking[:, 1:current_k+1] # [n, k] exclude themselves (top 1)
avg_similarities = torch.zeros(top_ranking.shape[0])
for i in range(top_ranking.shape[0]):
top_idx = top_ranking[i].tolist()
avg_similarities[i] = similarity_matrix[i, top_idx].mean()
pred_score.append(avg_similarities.mean().item())
for current_k in k:
actual_k = min(current_k + 1, similarity_matrix.size(1))
topk_values = torch.topk(similarity_matrix, actual_k, dim=1).values # shape: (n_samples, actual_k)
# 如果实际取出的topk不足以排除 self,则直接使用所有
if actual_k > 1:
topk_without_self = topk_values[:, 1:]
else:
topk_without_self = topk_values
upperbound_score.append(topk_without_self.mean().item())
return pred_score, upperbound_score
def soft_retrieval_uncon(logits, gt_logits, k):
# treat ratescore > x as gt and calculate recall
logits = logits.detach().cpu()
gt_logits = gt_logits.detach().cpu()
# Ranking the logits in descending order for each row
ranking = torch.argsort(logits, descending=True)
# Define GT as all positions (excluding self) with gt_logits > 0.9
gt_mask = gt_logits > 0.9
# 过滤无正样本的行,只统计有GT的sample
valid_rows = gt_mask.any(dim=1)
if not valid_rows.any():
return -1 # 或无正样本时的处理
ranking = ranking[valid_rows]
gt_mask = gt_mask[valid_rows]
if isinstance(k, int):
topk = ranking[:, :k]
# Check for each row if any of the topk indices is a valid GT
topk_gt = torch.gather(gt_mask, 1, topk)
hits = topk_gt.any(dim=1).float().numpy()
return np.mean(hits)
else:
results = []
for current_k in k:
topk = ranking[:, :current_k]
topk_gt = torch.gather(gt_mask, 1, topk)
hits = topk_gt.any(dim=1).float().numpy()
results.append(np.mean(hits))
return results
def soft_retrieval_exclude_self(logits, gt_logits, index_list, k):
# treat ratescore > x as gt and calculate recall
logits = delete_elements_by_row(logits, index_list)
gt_logits = delete_elements_by_row(gt_logits, index_list)
logits = logits.detach().cpu()
gt_logits = gt_logits.detach().cpu()
# Ranking the logits in descending order for each row
ranking = torch.argsort(logits, descending=True)
# Define GT as all positions (excluding self) with gt_logits > 0.9
gt_mask = gt_logits > 0.9
# 过滤无正样本的行,只统计有GT的sample
valid_rows = gt_mask.any(dim=1)
if not valid_rows.any():
return -1 # 或无正样本时的处理
ranking = ranking[valid_rows]
gt_mask = gt_mask[valid_rows]
if isinstance(k, int):
topk = ranking[:, :k]
# Check for each row if any of the topk indices is a valid GT
topk_gt = torch.gather(gt_mask, 1, topk)
hits = topk_gt.any(dim=1).float().numpy()
return np.mean(hits)
else:
results = []
for current_k in k:
topk = ranking[:, :current_k]
topk_gt = torch.gather(gt_mask, 1, topk)
hits = topk_gt.any(dim=1).float().numpy()
results.append(np.mean(hits))
print('results:', results)
return results
def RateScore_retrieval(logits, similarity_matrix, k=[1, 5, 10]):
# calculate avg and upperbound ratescore of retrieval
# NOTE: do not have to exclude items themselves in logits or similarity_matrix
ranking = torch.argsort(logits, descending=True) # [0.1, 0.3, 0.2] --> [1, 2, 0] ranking logits along each row
pred_score = []
upperbound_score = []
for current_k in k:
top_ranking = ranking[:, 1:current_k+1] # [n, k] exclude themselves (top 1)
avg_similarities = torch.zeros(top_ranking.shape[0])
for i in range(top_ranking.shape[0]):
top_idx = top_ranking[i].tolist()
avg_similarities[i] = similarity_matrix[i, top_idx].mean()
pred_score.append(avg_similarities.mean().item())
for current_k in k:
actual_k = min(current_k + 1, similarity_matrix.size(1))
topk_values = torch.topk(similarity_matrix, actual_k, dim=1).values # shape: (n_samples, actual_k)
# 如果实际取出的topk不足以排除 self,则直接使用所有
if actual_k > 1:
topk_without_self = topk_values[:, 1:]
else:
topk_without_self = topk_values
upperbound_score.append(topk_without_self.mean().item())
return pred_score, upperbound_score
def compute_ndcg_exclude_self(pred_scores, true_scores, index_list, k=None):
"""
计算NDCG。
当 k 是一个整数时,计算对应 k 值的 ndcg;当 k 是列表时,分别计算每个 k 的 ndcg,返回一个与 k 列表等长的列表,每个元素是对应的 score。
输入的 true_scores 和 pred_scores 为 tensor 类型。
:param true_scores: 真实相似度矩阵 (n_query, n_samples)
:param pred_scores: 模型预测的相似度矩阵 (n_query, n_samples)
:param index_list: 每行需要删除的元素的索引列表
:param k: 只考虑前 k 个结果。如果为 None,则考虑所有结果;如果为 list,则分别计算每个 k 的 ndcg
:return: 当 k 为 list 时返回一个 ndcg 值的列表,否则返回单个 ndcg 值
"""
true_scores = delete_elements_by_row(true_scores, index_list)
pred_scores = delete_elements_by_row(pred_scores, index_list)
# 转换为 numpy 数组以便后续排序和计算
true_scores = true_scores.detach().cpu().numpy()
pred_scores = pred_scores.detach().cpu().numpy()
n_query = true_scores.shape[0]
if k is None:
k_list = [true_scores.shape[1]]
elif isinstance(k, list):
k_list = k
else:
k_list = [k]
ndcg_results = []
for current_k in k_list:
ndcg_scores = []
for i in range(n_query):
true = true_scores[i]
pred = pred_scores[i]
pred_rank = np.argsort(pred)[::-1]
true_sorted_by_pred = true[pred_rank]
# 计算 DCG
if current_k is not None:
true_sorted_by_pred_k = true_sorted_by_pred[:current_k]
else:
true_sorted_by_pred_k = true_sorted_by_pred
dcg = compute_dcg(true_sorted_by_pred_k)
# 计算 IDCG
ideal_sorted_true = np.sort(true)[::-1]
if current_k is not None:
ideal_sorted_true = ideal_sorted_true[:current_k]
idcg = compute_dcg(ideal_sorted_true)
ndcg = dcg / idcg if idcg != 0 else 0
ndcg_scores.append(ndcg)
ndcg_results.append(np.mean(ndcg_scores))
# 如果原始 k 不是 list,则返回单个值
if not isinstance(k, list):
return ndcg_results[0]
return ndcg_results
def compute_ndcg_uncon(pred_scores, true_scores, k=None):
"""
计算NDCG。
当 k 是一个整数时,计算对应 k 值的 ndcg;当 k 是列表时,分别计算每个 k 的 ndcg,返回一个与 k 列表等长的列表,每个元素是对应的 score。
输入的 true_scores 和 pred_scores 为 tensor 类型。
:param true_scores: 真实相似度矩阵 (n_query, n_samples)
:param pred_scores: 模型预测的相似度矩阵 (n_query, n_samples)
:param k: 只考虑前 k 个结果。如果为 None,则考虑所有结果;如果为 list,则分别计算每个 k 的 ndcg
:return: 当 k 为 list 时返回一个 ndcg 值的列表,否则返回单个 ndcg 值
"""
# 转换为 numpy 数组以便后续排序和计算
true_scores = true_scores.detach().cpu().numpy()
pred_scores = pred_scores.detach().cpu().numpy()
n_query = true_scores.shape[0]
if k is None:
k_list = [true_scores.shape[1]]
elif isinstance(k, list):
k_list = k
else:
k_list = [k]
ndcg_results = []
for current_k in k_list:
ndcg_scores = []
for i in range(n_query):
true = true_scores[i]
pred = pred_scores[i]
pred_rank = np.argsort(pred)[::-1]
true_sorted_by_pred = true[pred_rank]
# 计算 DCG
if current_k is not None:
true_sorted_by_pred_k = true_sorted_by_pred[:current_k]
else:
true_sorted_by_pred_k = true_sorted_by_pred
dcg = compute_dcg(true_sorted_by_pred_k)
# 计算 IDCG
ideal_sorted_true = np.sort(true)[::-1]
if current_k is not None:
ideal_sorted_true = ideal_sorted_true[:current_k]
idcg = compute_dcg(ideal_sorted_true)
ndcg = dcg / idcg if idcg != 0 else 0
ndcg_scores.append(ndcg)
ndcg_results.append(np.mean(ndcg_scores))
# 如果原始 k 不是 list,则返回单个值
if not isinstance(k, list):
return ndcg_results[0]
return ndcg_results
def delete_elements_by_row(matrix, delete_list):
"""
根据给定的索引列表,删除每行中指定位置的元素
:param matrix: 输入的 n*m 维 tensor
:param delete_list: 一个 n 大小的 list,表示每行需要删除的元素的索引
:return: 处理后的 n*(m-1) 维 tensor
"""
n, m = matrix.size()
result = []
for i in range(n):
row = matrix[i]
delete_index = delete_list[i]
# 删除指定位置的元素
new_row = torch.cat([row[:delete_index], row[delete_index+1:]])
result.append(new_row)
return torch.stack(result)
def compute_dcg(scores):
"""计算DCG"""
return np.sum(scores / np.log2(np.arange(2, len(scores) + 2)))
def compute_ndcg(pred_scores, true_scores, index_list, k=None):
"""
计算NDCG。
当 k 是一个整数时,计算对应 k 值的 ndcg;当 k 是列表时,分别计算每个 k 的 ndcg,返回一个与 k 列表等长的列表,每个元素是对应的 score。
输入的 true_scores 和 pred_scores 为 tensor 类型。
:param true_scores: 真实相似度矩阵 (n_query, n_samples)
:param pred_scores: 模型预测的相似度矩阵 (n_query, n_samples)
:param index_list: 每行需要删除的元素的索引列表
:param k: 只考虑前 k 个结果。如果为 None,则考虑所有结果;如果为 list,则分别计算每个 k 的 ndcg
:return: 当 k 为 list 时返回一个 ndcg 值的列表,否则返回单个 ndcg 值
"""
true_scores = delete_elements_by_row(true_scores, index_list)
pred_scores = delete_elements_by_row(pred_scores, index_list)
# 转换为 numpy 数组以便后续排序和计算
true_scores = true_scores.detach().cpu().numpy()
pred_scores = pred_scores.detach().cpu().numpy()
n_query = true_scores.shape[0]
if isinstance(k, list):
k_list = k
else:
k_list = [k]
ndcg_results = []
for current_k in k_list:
ndcg_scores = []
for i in range(n_query):
true = true_scores[i]
pred = pred_scores[i]
pred_rank = np.argsort(pred)[::-1]
true_sorted_by_pred = true[pred_rank]
# 计算 DCG
if current_k is not None:
true_sorted_by_pred_k = true_sorted_by_pred[:current_k]
else:
true_sorted_by_pred_k = true_sorted_by_pred
dcg = compute_dcg(true_sorted_by_pred_k)
# 计算 IDCG
ideal_sorted_true = np.sort(true)[::-1]
if current_k is not None:
ideal_sorted_true = ideal_sorted_true[:current_k]
idcg = compute_dcg(ideal_sorted_true)
ndcg = dcg / idcg if idcg != 0 else 0
ndcg_scores.append(ndcg)
ndcg_results.append(np.mean(ndcg_scores))
# 如果原始 k 不是 list,则返回单个值
if not isinstance(k, list):
return ndcg_results[0]
return ndcg_results
if __name__ == '__main__':
logits = torch.rand(4, 4)
logits.fill_diagonal_(1)
gt_logits = torch.rand(4, 4)
gt_logits.fill_diagonal_(1)
index = [0, 1, 2, 3]
print('logits:', logits)
print('gt_logits:', gt_logits)
ndcg = compute_ndcg(gt_logits, logits, index, k=[1, 3])
print(ndcg)