Files
icassp2027-experiment/third_party/EgoSpeak/MiniROAD/utils/metrics.py
T

227 lines
9.9 KiB
Python
Executable File

# Evaluation code from LSTR
from collections import OrderedDict
from multiprocessing import Pool
import numpy as np
from sklearn.metrics import average_precision_score
def calibrated_average_precision_score(y_true, y_score):
"""Compute calibrated average precision (cAP), which is particularly
proposed for the TVSeries dataset.
"""
y_true_sorted = y_true[np.argsort(-y_score)]
tp = y_true_sorted.astype(float)
fp = np.abs(y_true_sorted.astype(float) - 1)
tps = np.cumsum(tp)
fps = np.cumsum(fp)
ratio = np.sum(tp == 0) / np.sum(tp)
cprec = tps / (tps + fps / (ratio + np.finfo(float).eps) + np.finfo(float).eps)
cap = np.sum(cprec[tp == 1]) / np.sum(tp)
return cap
def perframe_average_precision(prediction, ground_truth, class_names, ignore_index,
postprocessing=None, metrics='AP'):
"""Compute (frame-level) average precision between ground truth and
predictions data frames.
"""
result = OrderedDict()
ground_truth = np.array(ground_truth)
prediction = np.array(prediction)
# Postprocessing
if postprocessing is not None:
ground_truth, prediction = postprocessing(ground_truth, prediction)
# Build metrics
if metrics == 'AP':
compute_score = average_precision_score
elif metrics == 'cAP':
# print('cAP')
compute_score = calibrated_average_precision_score
else:
raise RuntimeError('Unknown metrics: {}'.format(metrics))
# Ignore backgroud class
ignore_set = set([ignore_index])
# print('ignore_set = set([{ignore_index}])')
# Enable background according to ignore_index
# ignore_set = set([ignore_index])
# Compute average precision
result['per_class_AP'] = OrderedDict()
result['num'] = OrderedDict()
print(f"NUM FRAMES: {np.sum(ground_truth[:, 1:])}")
for idx, class_name in enumerate(class_names):
if idx not in ignore_set:
# ground_truth.shape [2901376, 2]
# prediction.shape [45334 ?]
if np.any(ground_truth[:, idx]):
ap_score = compute_score(ground_truth[:, idx], prediction[:, idx])
result['per_class_AP'][class_name] = ap_score
result['num'][class_name] = f'[true: {int(np.sum(ground_truth[:, idx]))}, pred:{int(np.sum(prediction[:,idx]))}, AP:{ap_score*100:.1f}]'
result['mean_AP'] = np.mean(list(result['per_class_AP'].values()))
print(f"Per_class_AP: {result['per_class_AP']}")
return result
def event_accuracy(pred_scores_dict, gt_targets_dict):
th_session_accuracies = {}
threshold = 0.5
# ERROR: something is wrong
# PLEASE EDIT:
# pred_scores_dict here has wrong dimension
# [64, 128, 10, 3] <- this is because I am using trainloader (do not evaluate on trainloader)
for threshold in np.arange(0.1, 1.0, 0.2): # 0.1, 0.3, 0.5, 0.7, 0.9
num_events = 0
th_total_speaking_start_correct = 0
th_total_speaking_end_correct = 0
for session in pred_scores_dict.keys():
th_session_speaking_start_correct = 0
th_session_speaking_start_predictions = 0
th_session_speaking_end_correct = 0
th_session_speaking_end_predictions = 0
argmax_session_speaking_start_correct = 0
argmax_session_speaking_end_correct = 0
pred_scores = pred_scores_dict[session]
# pred_scores: [num_feature_frames, anticipation_steps, num_classes]
# classes: background, target speaker speaking, other speaker speaking
# 12/06: To-do
# I forgot whether miniroad AA step 0 means anticipate 1 step future or current
# I did change something it would likely to be
ant_length = pred_scores.shape[1]
gt_targets = gt_targets_dict[session]
event_labels = np.load(f'/home/junhyeok/projects/turn-taking/datasets/EasyCom/target_perframe/events/{session}.npy')
num_speaking_start = 0
num_speaking_end = 0
print(f'{session} loaded, {len(event_labels)} events')
# 12/06:
# IndexError: index 8504 is out of bounds for axis 0 with size 8390
# why the event_idx has much higher value than the length of pred_scores?
# check how the event label is made
# event_label: [event_idx, event_class]
# ['18', 'target speaker speaking start']
for event_label in event_labels:
event_idx, event_class = event_label
event_idx = int(event_idx)
if event_class == 'target speaker speaking start':
num_speaking_start += 1
num_speaking_end += 1
# right before the event start, want to see how the model predict?
ant_prob = pred_scores[event_idx-1, 0, :]
if np.argmax(ant_prob) == 1:
argmax_session_speaking_start_correct += 1
ant_target_prob = pred_scores[event_idx-1, 0, 1]
if ant_target_prob >= threshold:
th_session_speaking_start_correct += 1
elif event_class == 'target speaker speaking end':
# right after the event end, want to see how the model predict?
ant_prob = pred_scores[event_idx, 0, :]
if np.argmax(ant_prob) == 0:
argmax_session_speaking_end_correct += 1
# target speaker will not speaker anymore
ant_target_prob = pred_scores[event_idx, 0, 1]
if ant_target_prob < threshold:
th_session_speaking_end_correct += 1
print(f'Threshold {threshold:.2f}) {session} speaking start accuracy: {(th_session_speaking_start_correct / (len(event_labels) / 2)):.4f}')
print(f'Threshold {threshold:.2f}) {session} speaking end accuracy: {(th_session_speaking_end_correct / (len(event_labels)/ 2 )):.4f}')
print(f'argmax {session} speaking start accuracy: {argmax_session_speaking_start_correct / (len(event_labels) / 2)}')
print(f'argmax {session} speaking end accuracy: {argmax_session_speaking_end_correct / (len(event_labels)/ 2 )}')
th_session_accuracies[session] = [th_session_speaking_start_correct / len(event_labels), th_session_speaking_end_correct / len(event_labels)]
th_total_speaking_start_correct += th_session_speaking_start_correct
th_total_speaking_end_correct += th_session_speaking_end_correct
num_events += len(event_labels)
th_total_speaking_start_accuracy = th_total_speaking_start_correct / len(event_labels)
th_total_speaking_end_accuracy = th_total_speaking_end_correct / len(event_labels)
th_total_accuracy = (th_total_speaking_start_correct + th_total_speaking_end_correct) / (num_events)
print(f'total speaking start accuracy: {th_total_accuracy}')
return th_total_accuracy
def get_stage_pred_scores(gt_targets, pred_scores, perc_s, perc_e):
starts = []
ends = []
stage_gt_targets = []
stage_pred_scores = []
for i in range(len(gt_targets)):
if gt_targets[i] == 0:
stage_gt_targets.append(gt_targets[i])
stage_pred_scores.append(pred_scores[i])
else:
if i == 0 or gt_targets[i - 1] == 0:
starts.append(i)
if i == len(gt_targets) - 1 or gt_targets[i + 1] == 0:
ends.append(i)
if len(starts) != len(ends):
raise ValueError('starts and ends cannot pair!')
action_lens = [ends[i] - starts[i] for i in range(len(starts))]
stage_starts = [starts[i] + int(action_lens[i] * perc_s) for i in range(len(starts))]
stage_ends = [max(stage_starts[i] + 1, starts[i] + int(action_lens[i] * perc_e)) for i in range(len(starts))]
for i in range(len(starts)):
stage_gt_targets.extend(gt_targets[stage_starts[i]: stage_ends[i]])
stage_pred_scores.extend(pred_scores[stage_starts[i]: stage_ends[i]])
return np.array(stage_gt_targets), np.array(stage_pred_scores)
def perstage_average_precision(prediction, ground_truth,
class_names, postprocessing,
metrics='cAP'):
result = OrderedDict()
ground_truth = np.array(ground_truth)
prediction = np.array(prediction)
# Postprocessing
if postprocessing is not None:
ground_truth, prediction = postprocessing(ground_truth, prediction)
# Build metrics
if metrics == 'AP':
compute_score = average_precision_score
elif metrics == 'cAP':
compute_score = calibrated_average_precision_score
else:
raise RuntimeError('Unknown metrics: {}'.format(metrics))
raise NotImplementedError('This function is not implemented yet.')
# Ignore backgroud class
# ignore_index = set([0])
# Compute average precision
for perc_s in range(10):
perc_e = perc_s + 1
stage_name = '{:2}%_{:3}%'.format(perc_s * 10, perc_e * 10)
result[stage_name] = OrderedDict({'per_class_AP': OrderedDict()})
for idx, class_name in enumerate(class_names):
if idx not in ignore_index:
stage_gt_targets, stage_pred_scores = get_stage_pred_scores(
(ground_truth[:, idx] == 1).astype(int),
prediction[:, idx],
perc_s / 10,
perc_e / 10,
)
result[stage_name]['per_class_AP'][class_name] = \
compute_score(stage_gt_targets, stage_pred_scores)
result[stage_name]['mean_AP'] = \
np.mean(list(result[stage_name]['per_class_AP'].values()))
return result